TinyModem
Header-only software audio modem library
Loading...
Searching...
No Matches
PSKDecoder.h
Go to the documentation of this file.
1#pragma once
2
3#include <math.h>
4#include <string.h>
5
6#include "AudioTools.h"
7#include "ModemBase.h"
8#include "PSK.h"
9
10namespace tinymodem {
11
29class PSKDecoder : public ModemDecoder {
30 public:
31 PSKDecoder() = default;
32 PSKDecoder(Print& out) { setOutput(out); }
33
36 PSKConfig result;
37 result.setMode("psk31");
38 return result;
39 }
40
42 void setAudioInfo(audio_tools::AudioInfo info) override {
43 if (info.sample_rate == cfg.sample_rate && info.channels == cfg.channels &&
44 info.bits_per_sample == cfg.bits_per_sample)
45 return;
46 cfg.sample_rate = info.sample_rate;
47 cfg.channels = info.channels;
48 cfg.bits_per_sample = info.bits_per_sample;
49 if (is_active) begin();
50 }
51
52 audio_tools::AudioInfo audioInfo() override { return cfg; }
53
54 using audio_tools::AudioDecoder::begin;
55
58 cfg = config;
59 return begin();
60 }
61
62 bool begin() override {
63 is_active = false;
64 if (!cfg.isValid()) return false;
65 if (!input.begin(cfg.bits_per_sample, cfg.channels, cfg.channel))
66 return false;
67 samples_per_symbol = cfg.samplesPerSymbol();
68 // 8PSK: a shorter integration reduces the interference with the
69 // neighboring symbols (the transitions take a full symbol)
70 window = lroundf(cfg.modulation == PSKModulation::PSK8
73 history.resize(window * 3);
74 resetState();
75 is_active = true;
76 return true;
77 }
78
80 void end() override {
81 if (!is_active) return;
82 flush();
83 is_active = false;
84 }
85
87 void flush() override {
88 if (!is_active) return;
89 // feed silence, so that the end of the transmission is detected
90 size_t n = 4 * window;
91 for (size_t j = 0; j < n && !is_finished; j++) addSample(0.0f, false);
92 if (carrier) endOfCarrier();
93 bool finished = is_finished;
94 resetState();
95 is_finished = finished;
96 }
97
99 size_t write(const uint8_t* data, size_t len) override {
100 if (!is_active) return 0;
101 input.write(data, len,
102 [this](float sample) { addSample(sample, true); });
103 return len;
104 }
105
107 bool isCarrier() override { return carrier; }
108
110 bool isFinished() override { return is_finished; }
111
113 float frequencyOffset() { return freq_offset; }
114
116 PSKConfig& config() { return cfg; }
117
118 operator bool() override { return is_active; }
119
120 protected:
121 static constexpr int BINS = 16;
122 static constexpr int MIN_PREAMBLE = 8;
123 static constexpr int MAX_ONES = 20;
125 static constexpr int MAX_WEAK = 3;
126 static constexpr float LOST_LEVEL = 0.3f;
127 static constexpr float AFC_GAIN = 0.1f;
128 static constexpr float TIMING_SMOOTHING = 0.15f;
129 static constexpr float TIMING_GAIN = 0.5f;
130 static constexpr float MAX_CORRECTION = 0.125f;
131
133 bool is_active = false;
134 bool is_finished = false;
136 uint32_t noise_seed = 1;
138 int window = 0;
139
140 // mixer and integration over one symbol
141 float nco_re = 1, nco_im = 0, step_re = 1, step_im = 0;
142 float freq_offset = 0;
143 audio_tools::Vector<float> history; // i, q, x^2
144 int pos = 0;
145 float sum_i = 0, sum_q = 0, sum_power = 0;
146 uint32_t sample_count = 0;
147
148 // symbol timing
149 float phase = 0;
150 float target = 0.5f;
151 float countdown = 0; // samples until the next decision
152 float bin_sum[BINS];
154 float hist[BINS];
155
156 // decisions
157 float prev_i = 0, prev_q = 0;
158 float quality = 0; // cos(2 * phase difference): preamble and BPSK
159 float quality_m = 0; // cos(m * phase difference) for m positions
160 bool carrier = false;
161 int zeros = 0;
162 int ones = 0;
163 int weak = 0;
164 int last_bit = 1;
165 uint16_t code = 0;
167 uint32_t mfsk_code = 0;
168 // 8PSK: characters of a steady carrier, which might be the postamble
169 uint8_t held[MAX_ONES];
170 int nheld = 0;
172 // statistics
173 unsigned ndata = 0;
174 unsigned nsymbols = 0;
175 float quality_total = 0;
177
178 void resetState() {
179 for (size_t j = 0; j < history.size(); j++) history[j] = 0;
180 pos = 0;
181 sum_i = sum_q = sum_power = 0;
182 for (int b = 0; b < BINS; b++) {
183 bin_sum[b] = 0;
184 bin_count[b] = 0;
185 hist[b] = 0;
186 }
187 phase = 0;
188 target = 0.5f;
190 prev_i = prev_q = 0;
191 quality = quality_m = 0;
192 carrier = false;
193 zeros = ones = weak = 0;
194 code = 0;
195 is_finished = false;
197 input.reset();
198 }
199
200 void setFrequencyOffset(float offset) {
201 freq_offset = offset;
202 double w = 2.0 * M_PI * (cfg.carrier_freq + offset) / cfg.sample_rate;
203 step_re = cos(w);
204 step_im = sin(w);
205 }
206
208 float noise() {
209 noise_seed = noise_seed * 1103515245u + 12345u;
210 return ((noise_seed >> 8) & 0xFFFF) / 65535.0f - 0.5f;
211 }
212
213 void addSample(float x, bool add_noise) {
214 if (is_finished) return;
215 if (add_noise && cfg.rx_noise != 0.0f)
216 x += noise() * cfg.rx_noise * 2.0f;
217
218 // mix to the baseband and integrate over one symbol
219 float values[3] = {x * nco_re, -x * nco_im, x * x};
220 float* old = history.data() + pos * 3;
221 sum_i += values[0] - old[0];
222 sum_q += values[1] - old[1];
223 sum_power += values[2] - old[2];
224 old[0] = values[0];
225 old[1] = values[1];
226 old[2] = values[2];
227 if (++pos == window) pos = 0;
228 float r = nco_re * step_re - nco_im * step_im;
230 nco_re = r;
231 if (++sample_count % 1024 == 0) recalculate();
232
233 // energy distribution over the symbol period
234 float energy = sum_i * sum_i + sum_q * sum_q;
235 float prev_phase = phase;
236 phase += 1.0f / samples_per_symbol;
237 int bin = (int)(prev_phase * BINS);
238 if (bin >= BINS) bin = BINS - 1;
239 bin_sum[bin] += energy;
240 bin_count[bin]++;
241 if (phase >= 1.0f) {
242 phase -= 1.0f;
243 updateTiming();
244 }
245 // one decision per symbol: the next decision moves towards the time with
246 // the highest energy
247 if (--countdown <= 0) {
248 decide();
249 float error = target - phase; // in symbols
250 if (error > 0.5f) error -= 1.0f;
251 if (error < -0.5f) error += 1.0f;
252 float correction = TIMING_GAIN * error;
253 if (correction > MAX_CORRECTION) correction = MAX_CORRECTION;
254 if (correction < -MAX_CORRECTION) correction = -MAX_CORRECTION;
255 countdown += samples_per_symbol * (1.0f + correction);
256 }
257 }
258
260 void recalculate() {
261 sum_i = sum_q = sum_power = 0;
262 for (int k = 0; k < window; k++) {
263 sum_i += history[k * 3];
264 sum_q += history[k * 3 + 1];
265 sum_power += history[k * 3 + 2];
266 }
267 float mag = sqrtf(nco_re * nco_re + nco_im * nco_im);
268 nco_re /= mag;
269 nco_im /= mag;
270 }
271
274 float min = 0;
275 for (int b = 0; b < BINS; b++) {
276 if (bin_count[b] > 0) {
277 float value = bin_sum[b] / bin_count[b];
278 hist[b] += TIMING_SMOOTHING * (value - hist[b]);
279 }
280 bin_sum[b] = 0;
281 bin_count[b] = 0;
282 if (b == 0 || hist[b] < min) min = hist[b];
283 }
284 float x = 0, y = 0;
285 for (int b = 0; b < BINS; b++) {
286 float angle = 2.0f * (float)M_PI * (b + 0.5f) / BINS;
287 float weight = hist[b] - min;
288 x += weight * cosf(angle);
289 y += weight * sinf(angle);
290 }
291 if (x == 0 && y == 0) return;
292 float t = atan2f(y, x) / (2.0f * (float)M_PI);
293 target = t < 0 ? t + 1.0f : t;
294 }
295
297 int positions() { return 2 << (cfg.bitsPerSymbol() - 1); }
298
300 void decide() {
301 float zi = sum_i, zq = sum_q;
302 float dr = zi * prev_i + zq * prev_q; // z * conj(prev)
303 float di = zq * prev_i - zi * prev_q;
304 prev_i = zi;
305 prev_q = zq;
306 float amplitude = 2.0f * sqrtf(zi * zi + zq * zq) / window;
307 float rms_amplitude = sqrtf(2.0f * sum_power / window);
308 // close to 1 for a carrier, small for noise
309 bool present = amplitude >= cfg.min_amplitude &&
310 amplitude > 0.5f * rms_amplitude;
311 float mag2 = dr * dr + di * di;
312 if (mag2 <= 0) {
313 // digital silence: the signal is lost
314 if (carrier && ++weak >= MAX_WEAK) endOfCarrier();
315 return;
316 }
317 // the mixer provides the conjugate: phase step like fldigi (0 - 2 pi)
318 float raw = atan2f(di, dr);
319 float step = -raw;
320 if (step < 0) step += 2.0f * (float)M_PI;
321 int m = positions();
322 // cos(2 * phase difference): 1 for a clean BPSK signal and the preamble
323 quality += 0.1f * ((dr * dr - di * di) / mag2 - quality);
324 // cos(m * phase difference): QPSK and 8PSK are slowly averaged, because
325 // the FEC of QPSK works with a lot of phase noise
326 quality_m += (m == 2 ? 0.1f : 0.03f) * (cosf(m * raw) - quality_m);
327 int bit = dr > 0 ? 1 : 0;
328
329 if (present && cfg.afc) {
330 // before the carrier is detected we expect the preamble (phase
331 // reversals): this doubles the capture range to half the symbol rate
332 float error;
333 if (!carrier || m == 2) {
334 bool reversal = carrier ? bit == 0 : true;
335 error = reversal ? atan2f(-di, -dr) : atan2f(di, dr);
336 } else {
337 // distance to the nearest phase position
338 float spacing = 2.0f * (float)M_PI / m;
339 error = raw - spacing * lroundf(raw / spacing);
340 }
341 // a steady carrier is not a preamble with a frequency offset
342 if (!carrier && fabsf(error) > 0.75f * (float)M_PI) error = 0;
343 float offset = freq_offset + AFC_GAIN * error * cfg.symbol_rate /
344 (2.0f * (float)M_PI);
345 float max_offset = cfg.symbol_rate / 2;
346 if (offset > max_offset) offset = max_offset;
347 if (offset < -max_offset) offset = -max_offset;
348 setFrequencyOffset(offset);
349 }
350
351 if (!carrier) {
352 // start with a run of phase reversals
353 zeros = (present && bit == 0 && quality > 0.5f) ? zeros + 1 : 0;
354 if (zeros >= MIN_PREAMBLE) startOfCarrier(amplitude);
355 return;
356 }
357
358 // end of the transmission: signal lost or long steady carrier
359 bool weak_symbol = amplitude < cfg.min_amplitude ||
360 amplitude < LOST_LEVEL * track_amplitude;
361 weak = weak_symbol ? weak + 1 : 0;
362 // a steady carrier ends BPSK and 8PSK (QPSK sends data with 0 degrees)
363 bool steady = fabsf(raw) < (float)M_PI / m;
364 ones = steady && cfg.modulation != PSKModulation::QPSK ? ones + 1 : 0;
365 if (ones >= MAX_ONES) nheld = 0; // postamble
366 if (!steady) releaseHeld();
367 float min_quality = cfg.modulation == PSKModulation::BPSK ? 0.2f
368 : cfg.modulation == PSKModulation::QPSK ? 0.03f
369 : 0.1f;
370 if (weak >= MAX_WEAK || ones >= MAX_ONES || quality_m < min_quality) {
371 endOfCarrier();
372 return;
373 }
374 if (!weak_symbol) {
375 track_amplitude += 0.1f * (amplitude - track_amplitude);
377 amplitude_total += amplitude;
378 nsymbols++;
379 }
380 switch (cfg.modulation) {
381 case PSKModulation::QPSK: {
382 // soft decision: correlation with the 4 possible phase steps
383 float metric[4];
384 for (int sym = 0; sym < 4; sym++) {
385 float expected = (float)M_PI + (float)M_PI / 2 * ((4 - sym) & 3);
386 metric[sym] = cosf(step - expected);
387 }
388 int decoded = qpsk.decode(metric);
389 if (decoded >= 0) varicode(decoded);
390 break;
391 }
392 case PSKModulation::PSK8: {
393 float pos = (step - (float)M_PI) / ((float)M_PI / 4);
394 int sym = ((int)lroundf(pos) % 8 + 8) % 8;
395 for (int b = 0; b < 3; b++) mfskVaricode((sym >> b) & 1);
396 break;
397 }
398 default:
399 varicode(bit);
400 break;
401 }
402 }
403
405 void mfskVaricode(int bit) {
406 mfsk_code = (mfsk_code << 1) | bit;
407 if ((mfsk_code & 7) != 1) return;
408 uint32_t value = mfsk_code >> 1;
409 int ch = value <= 0xFFFF ? MFSKVaricode::decode(value) : -1;
410 // the character 0 is the idle signal
411 if (ch > 0) {
412 if (ones > 0 && nheld < MAX_ONES) {
413 // a steady carrier sends spaces: the postamble is not provided
414 held[nheld++] = ch;
415 } else {
416 output(ch);
417 }
418 }
419 mfsk_code = 1;
420 }
421
422 void releaseHeld() {
423 for (int j = 0; j < nheld; j++) output(held[j]);
424 nheld = 0;
425 }
426
427 void output(uint8_t ch) {
428 ndata++;
429 if (p_print != nullptr) p_print->write(ch);
430 }
431
433 void varicode(int bit) {
434 if (bit == 0 && last_bit == 0) {
435 if ((code >> 1) != 0) {
436 int ch = Varicode::decode(code >> 1);
437 if (ch >= 0 && p_print != nullptr) {
438 p_print->write((uint8_t)ch);
439 ndata++;
440 }
441 }
442 code = 0;
443 } else {
444 code = (code << 1) | bit;
445 if (code & 0x8000) code = 0; // too long: no valid character
446 }
447 last_bit = bit;
448 }
449
450 void startOfCarrier(float amplitude) {
451 carrier = true;
452 zeros = ones = weak = 0;
453 code = 0;
454 last_bit = 0;
455 qpsk.reset();
456 mfsk_code = 0;
457 nheld = 0;
458 quality_m = quality; // the preamble has the quality of a BPSK signal
459 track_amplitude = amplitude;
460 ndata = 0;
461 nsymbols = 0;
462 quality_total = 0;
463 amplitude_total = 0;
464 const char* name = cfg.modulation == PSKModulation::QPSK ? "qpsk"
465 : cfg.modulation == PSKModulation::PSK8 ? "8psk"
466 : "psk";
467 status("### CARRIER %s %.2f baud @ %.1f Hz ###\n", name,
468 (double)cfg.symbol_rate, (double)(cfg.carrier_freq + freq_offset));
469 }
470
472 // the bits in the Viterbi decoder
473 if (cfg.modulation == PSKModulation::QPSK)
474 qpsk.flush([this](int bit) { varicode(bit); });
475 releaseHeld();
476 carrier = false;
477 zeros = 0;
478 float n = nsymbols > 0 ? nsymbols : 1;
479 status(
480 "\n### NOCARRIER ndata=%u quality=%.3f ampl=%.3f freq=%.1f Hz ###\n",
481 ndata, (double)(quality_total / n), (double)(amplitude_total / n),
482 (double)(cfg.carrier_freq + freq_offset));
484 if (cfg.rx_one) is_finished = true;
485 }
486};
487
488} // namespace tinymodem
static int decode(uint16_t bits)
Byte of the indicated code: returns -1 if the code is unknown.
Definition PSK.h:239
Common API of the demodulators (FSKDecoder, MFSKDecoder): PCM audio data is written to the decoder an...
Definition ModemBase.h:344
void status(const char *fmt,...)
Definition ModemBase.h:367
void addSample(float x, bool add_noise)
Definition PSKDecoder.h:213
PSKConfig & config()
The configuration which was provided in begin().
Definition PSKDecoder.h:116
static constexpr int MAX_ONES
Definition PSKDecoder.h:123
void setAudioInfo(audio_tools::AudioInfo info) override
Defines the format of the input audio data.
Definition PSKDecoder.h:42
void output(uint8_t ch)
Definition PSKDecoder.h:427
float step_im
Definition PSKDecoder.h:141
PSKDecoder(Print &out)
Definition PSKDecoder.h:32
uint8_t held[MAX_ONES]
Definition PSKDecoder.h:169
float freq_offset
Definition PSKDecoder.h:142
PSKConfig cfg
Definition PSKDecoder.h:132
int pos
Definition PSKDecoder.h:144
static constexpr int BINS
Definition PSKDecoder.h:121
float prev_i
Definition PSKDecoder.h:157
float track_amplitude
Definition PSKDecoder.h:171
float phase
Definition PSKDecoder.h:149
static constexpr float MAX_CORRECTION
Definition PSKDecoder.h:130
bool begin() override
Definition PSKDecoder.h:62
uint32_t sample_count
Definition PSKDecoder.h:146
PSKConfig defaultConfig()
Provides a default configuration: psk31.
Definition PSKDecoder.h:35
static constexpr float TIMING_GAIN
Definition PSKDecoder.h:129
PcmInput input
Definition PSKDecoder.h:135
void varicode(int bit)
Collects the bits of a character: characters are separated by "00".
Definition PSKDecoder.h:433
float nco_re
Definition PSKDecoder.h:141
float step_re
Definition PSKDecoder.h:141
bool carrier
Definition PSKDecoder.h:160
float quality
Definition PSKDecoder.h:158
unsigned ndata
Definition PSKDecoder.h:173
int ones
Definition PSKDecoder.h:162
audio_tools::AudioInfo audioInfo() override
Definition PSKDecoder.h:52
int bin_count[BINS]
Definition PSKDecoder.h:153
int weak
Definition PSKDecoder.h:163
static constexpr float TIMING_SMOOTHING
Definition PSKDecoder.h:128
uint32_t mfsk_code
Definition PSKDecoder.h:167
float sum_i
Definition PSKDecoder.h:145
float hist[BINS]
Definition PSKDecoder.h:154
float nco_im
Definition PSKDecoder.h:141
float quality_m
Definition PSKDecoder.h:159
float prev_q
Definition PSKDecoder.h:157
int positions()
number of phase positions
Definition PSKDecoder.h:297
audio_tools::Vector< float > history
Definition PSKDecoder.h:143
QPSKCode qpsk
Definition PSKDecoder.h:166
int nheld
Definition PSKDecoder.h:170
bool is_finished
Definition PSKDecoder.h:134
float noise()
uniform random value in the range -0.5 to 0.5
Definition PSKDecoder.h:208
float samples_per_symbol
Definition PSKDecoder.h:137
void endOfCarrier()
Definition PSKDecoder.h:471
void setFrequencyOffset(float offset)
Definition PSKDecoder.h:200
static constexpr float AFC_GAIN
Definition PSKDecoder.h:127
float countdown
Definition PSKDecoder.h:151
unsigned nsymbols
Definition PSKDecoder.h:174
void startOfCarrier(float amplitude)
Definition PSKDecoder.h:450
bool isFinished() override
true if the decoding was stopped (rx_one)
Definition PSKDecoder.h:110
static constexpr int MIN_PREAMBLE
Definition PSKDecoder.h:122
float bin_sum[BINS]
Definition PSKDecoder.h:152
int window
Definition PSKDecoder.h:138
float sum_q
Definition PSKDecoder.h:145
float target
Definition PSKDecoder.h:150
float amplitude_total
Definition PSKDecoder.h:176
float frequencyOffset()
Current frequency offset of the received signal in Hz.
Definition PSKDecoder.h:113
void mfskVaricode(int bit)
MFSK Varicode (8PSK): a character ends with "001".
Definition PSKDecoder.h:405
void resetState()
Definition PSKDecoder.h:178
void decide()
Bit decision from the phase difference to the previous symbol.
Definition PSKDecoder.h:300
bool isCarrier() override
true while a transmission is received
Definition PSKDecoder.h:107
size_t write(const uint8_t *data, size_t len) override
Provide PCM data to the decoder.
Definition PSKDecoder.h:99
static constexpr int MAX_WEAK
the signal is lost after MAX_WEAK symbols below LOST_LEVEL
Definition PSKDecoder.h:125
void updateTiming()
The decision time is the circular center of the energy distribution.
Definition PSKDecoder.h:273
void flush() override
Processes the remaining samples (e.g. at the end of the input).
Definition PSKDecoder.h:87
float sum_power
Definition PSKDecoder.h:145
uint32_t noise_seed
Definition PSKDecoder.h:136
float quality_total
Definition PSKDecoder.h:175
static constexpr float LOST_LEVEL
Definition PSKDecoder.h:126
int last_bit
Definition PSKDecoder.h:164
bool begin(PSKConfig config)
Starts the processing with the indicated configuration.
Definition PSKDecoder.h:57
void releaseHeld()
Definition PSKDecoder.h:422
int zeros
Definition PSKDecoder.h:161
bool is_active
Definition PSKDecoder.h:133
void recalculate()
Avoids the accumulation of rounding errors.
Definition PSKDecoder.h:260
void end() override
Processes the remaining samples and reports the end of the carrier.
Definition PSKDecoder.h:80
uint16_t code
Definition PSKDecoder.h:165
Assembles the PCM frames from the written bytes and provides the samples of the selected channel as f...
Definition ModemBase.h:170
Rate 1/2 convolutional code with K=5 (polynomials 0x17 and 0x19) of QPSK31 (fldigi) and a soft decisi...
Definition PSK.h:302
static int decode(uint16_t bits)
Byte of the indicated code: returns -1 if the code is unknown.
Definition PSK.h:167
TinyModem: software audio modems and packets with error correction.
Definition AX25.h:11
@ QPSK
Definition PSK.h:18
@ PSK8
3 bits per symbol without FEC, MFSK Varicode
Definition PSK.h:20
@ BPSK
1 bit per symbol, PSK31 Varicode
Definition PSK.h:15
Configuration of the PSK modem (PSKEncoder, PSKDecoder): differential PSK with cosine shaped transiti...
Definition PSK.h:34
bool setMode(const char *mode)
Definition PSK.h:66