TinyModem
Header-only software audio modem library
Loading...
Searching...
No Matches
OFDMDecoder.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 "OFDM.h"
9
10namespace tinymodem {
11
25class OFDMDecoder : public ModemDecoder {
26 public:
27 OFDMDecoder() = default;
28 OFDMDecoder(Print& out) { setOutput(out); }
29
32 OFDMConfig result;
33 result.setMode("ofdm32");
34 return result;
35 }
36
38 void setAudioInfo(audio_tools::AudioInfo info) override {
39 if (info.sample_rate == cfg.sample_rate && info.channels == cfg.channels &&
40 info.bits_per_sample == cfg.bits_per_sample)
41 return;
42 cfg.sample_rate = info.sample_rate;
43 cfg.channels = info.channels;
44 cfg.bits_per_sample = info.bits_per_sample;
45 if (is_active) begin();
46 }
47
48 audio_tools::AudioInfo audioInfo() override { return cfg; }
49
50 using audio_tools::AudioDecoder::begin;
51
54 cfg = config;
55 return begin();
56 }
57
58 bool begin() override {
59 is_active = false;
60 if (!cfg.isValid()) return false;
61 if (!input.begin(cfg.bits_per_sample, cfg.channels, cfg.channel))
62 return false;
63 fft_size = cfg.fftSize();
64 prefix_size = cfg.prefixSize();
66 half = fft_size / 2;
67 first_bin = cfg.firstBin();
68 ncarriers = cfg.carriers;
69 table.begin(fft_size);
70 z_re.resize(ncarriers);
71 z_im.resize(ncarriers);
72 prev_re.resize(ncarriers);
73 prev_im.resize(ncarriers);
74 buffer.resize(4 * symbol_size + 2 * fft_size);
75 plateau.resize(2 * fft_size);
76 smoothed.resize(2 * fft_size);
77 resetState();
78 is_active = true;
79 return true;
80 }
81
83 void end() override {
84 if (!is_active) return;
85 flush();
86 is_active = false;
87 }
88
90 void flush() override {
91 if (!is_active) return;
92 // feed silence, so that the last frame is complete
93 size_t n = 2 * symbol_size + 2 * fft_size;
94 for (size_t j = 0; j < n && !is_finished; j++) addSample(0.0f, false);
95 if (state == State::FRAME) endFrame(false);
96 bool finished = is_finished;
97 resetState();
98 is_finished = finished;
99 }
100
102 size_t write(const uint8_t* data, size_t len) override {
103 if (!is_active) return 0;
104 input.write(data, len,
105 [this](float sample) { addSample(sample, true); });
106 return len;
107 }
108
110 bool isCarrier() override { return state == State::FRAME; }
111
113 bool isFinished() override { return is_finished; }
114
116 size_t frameCount() { return frames; }
117
119 OFDMConfig& config() { return cfg; }
120
121 operator bool() override { return is_active; }
122
123 protected:
124 enum class State { SEARCH, FRAME };
125 static constexpr float TRACKING_GAIN = 0.5f;
126 static constexpr float MIN_POWER = 1e-6f;
127
129 bool is_active = false;
130 bool is_finished = false;
133 uint32_t noise_seed = 1;
134 size_t frames = 0;
135 int fft_size = 0, prefix_size = 0, symbol_size = 0, half = 0;
136 int first_bin = 0, ncarriers = 0;
137
138 // sample buffer: buffer[0] is the sample with the global index base. The
139 // indexes wrap around, so they are only compared by their difference.
140 audio_tools::Vector<float> buffer;
141 size_t nvalid = 0;
142 uint32_t base = 0;
143 uint32_t now = 0; // global index of the next sample
144 uint32_t filled = 0; // number of samples since the reset (saturated)
145
146 // Schmidl & Cox autocorrelation
147 float corr = 0, power = 0;
148 bool in_plateau = false;
149 uint32_t plateau_start = 0;
150 audio_tools::Vector<float> plateau; // metric values of the plateau
151 audio_tools::Vector<float> smoothed;
152 int plateau_len = 0;
153
154 // frame
156 uint32_t window = 0; // global index of the DFT window of the next symbol
157 int symbol = 0;
158 audio_tools::Vector<float> z_re, z_im, prev_re, prev_im;
160 float drift = 0;
162 float header_soft[32];
164 int frame_len = -1;
165 int ndata = 0;
166 uint8_t current = 0;
168 float error_total = 0;
169 int error_count = 0;
170
171 void resetState() {
172 nvalid = 0;
173 base = now = filled = 0;
174 corr = power = 0;
175 in_plateau = false;
177 frame_len = -1;
178 is_finished = false;
179 input.reset();
180 }
181
183 float noise() {
184 noise_seed = noise_seed * 1103515245u + 12345u;
185 return ((noise_seed >> 8) & 0xFFFF) / 65535.0f - 0.5f;
186 }
187
188 float sample(uint32_t idx) { return buffer[idx - base]; }
189
190 void addSample(float x, bool add_noise) {
191 if (is_finished) return;
192 if (add_noise && cfg.rx_noise != 0.0f)
193 x += noise() * cfg.rx_noise * 2.0f;
194 if (nvalid == buffer.size()) compact();
195 buffer[nvalid++] = x;
196 uint32_t t = now++;
197 if (filled <= (uint32_t)(2 * half)) filled++;
198
199 // sliding autocorrelation between the two halves of a window of
200 // fft_size samples which starts at d = t - fft_size + 1:
201 // corr = sum r(m - half) * r(m), power = sum r(m)^2 for the last half
202 power += x * x;
203 if (filled > (uint32_t)half) {
204 float a = sample(t - half);
205 corr += a * x;
206 power -= a * a;
207 if (filled > (uint32_t)(2 * half)) corr -= sample(t - 2 * half) * a;
208 }
209 if (filled >= (uint32_t)(2 * half)) {
210 if (t % 4096 == 0) recalculate(t);
211 uint32_t d = t - 2 * half + 1;
212 if (state == State::SEARCH) search(d);
213 }
214
215 while (state == State::FRAME && isReached(now, window + fft_size)) {
217 }
218 }
219
221 static bool isReached(uint32_t a, uint32_t b) { return (int32_t)(a - b) >= 0; }
222
223 void recalculate(uint32_t t) {
224 corr = power = 0;
225 for (int j = half - 1; j >= 0; j--) {
226 uint32_t m = t - j;
227 float b = sample(m);
228 corr += sample(m - half) * b;
229 power += b * b;
230 }
231 }
232
234 void compact() {
235 uint32_t keep = now - 2 * half - 1;
236 // the window of the sync symbol is in the plateau
237 if (in_plateau && !isReached(plateau_start, keep)) keep = plateau_start;
238 if (state == State::FRAME && !isReached(window, keep)) keep = window;
239 int32_t n = keep - base;
240 if (n <= 0) n = 1; // never overflow
241 memmove(buffer.data(), buffer.data() + n, (nvalid - n) * sizeof(float));
242 nvalid -= n;
243 base += n;
244 }
245
248 void search(uint32_t d) {
249 float metric = power > MIN_POWER * half ? corr / power : 0;
250 if (metric > cfg.sync_threshold) {
251 if (!in_plateau) {
252 in_plateau = true;
253 plateau_start = d;
254 plateau_len = 0;
255 }
256 // the plateau of a sync symbol is about as long as the cyclic prefix:
257 // a much longer one is a periodic signal (e.g. a steady tone), which
258 // would prevent that the samples of the plateau are dropped
259 if (plateau_len == (int)plateau.size()) {
260 in_plateau = false;
261 return;
262 }
263 plateau[plateau_len++] = metric;
264 return;
265 }
266 if (!in_plateau) return;
267 in_plateau = false;
268 // smooth the metric against noise and use the middle of the range with
269 // at least 80% of the peak
270 int w = prefix_size / 4;
271 if (w < 1) w = 1;
272 float peak = 0;
273 for (int j = 0; j < plateau_len; j++) {
274 float sum = 0;
275 int n = 0;
276 for (int k = j - w / 2; k <= j + w / 2; k++) {
277 if (k < 0 || k >= plateau_len) continue;
278 sum += plateau[k];
279 n++;
280 }
281 smoothed[j] = sum / n;
282 if (smoothed[j] > peak) peak = smoothed[j];
283 }
284 int first = -1, last = -1;
285 for (int j = 0; j < plateau_len; j++) {
286 if (smoothed[j] >= 0.8f * peak) {
287 if (first < 0) first = j;
288 last = j;
289 }
290 }
291 if (first < 0 || last - first < prefix_size / 4) return;
292 window = plateau_start + (first + last) / 2;
294 symbol = 0;
295 drift = 0;
296 slope_adjust = 0;
297 }
298
300 void dft() {
301 for (int i = 0; i < ncarriers; i++) {
302 int bin = first_bin + i;
303 float re = 0, im = 0;
304 int idx = 0;
305 for (int n = 0; n < fft_size; n++) {
306 float x = sample(window + n);
307 re += x * table.cosine(idx);
308 im -= x * table.sine(idx);
309 idx += bin;
310 if (idx >= fft_size) idx -= fft_size;
311 }
312 z_re[i] = re;
313 z_im[i] = im;
314 }
315 }
316
317 static float wrap(float angle) {
318 while (angle > (float)M_PI) angle -= 2.0f * (float)M_PI;
319 while (angle <= -(float)M_PI) angle += 2.0f * (float)M_PI;
320 return angle;
321 }
322
324 static void fit(const float* angle, const float* weight, int n,
325 float& common, float& slope) {
326 float sw = 0, sx = 0, sy = 0, sxx = 0, sxy = 0;
327 for (int i = 0; i < n; i++) {
328 float w = weight[i];
329 if (w <= 0) continue;
330 sw += w;
331 sx += w * i;
332 sy += w * angle[i];
333 sxx += w * i * i;
334 sxy += w * i * angle[i];
335 }
336 float det = sw * sxx - sx * sx;
337 if (sw <= 0 || fabsf(det) < 1e-12f) {
338 common = sw > 0 ? sy / sw : 0;
339 slope = 0;
340 return;
341 }
342 slope = (sw * sxy - sx * sy) / det;
343 common = (sy - slope * sx) / sw;
344 }
345
347 dft();
348 if (symbol == 0) {
349 // sync symbol: keep it for the reference symbol
350 for (int i = 0; i < ncarriers; i++) {
351 prev_re[i] = z_re[i];
352 prev_im[i] = z_im[i];
353 }
354 } else if (symbol == 1) {
356 } else {
357 dataSymbol();
358 }
359 if (state != State::FRAME) return;
360 symbol++;
361 // timing: keep the window in the middle of the cyclic prefix
363 slope_adjust = 0;
364 if (drift >= 1.0f) {
365 window += 1;
366 drift -= 1.0f;
367 slope_adjust = 2.0f * (float)M_PI / fft_size;
368 } else if (drift <= -1.0f) {
369 window -= 1;
370 drift += 1.0f;
371 slope_adjust = -2.0f * (float)M_PI / fft_size;
372 }
373 }
374
378 float angle[128], weight[128];
379 reference_power = 0;
380 for (int i = 0; i < ncarriers; i++) {
381 float power_i = z_re[i] * z_re[i] + z_im[i] * z_im[i];
382 reference_power += power_i;
383 weight[i] = 0;
384 angle[i] = 0;
385 if ((first_bin + i) & 1) continue;
386 // z * conj(prev) rotated by the known phase difference
387 float dr = z_re[i] * prev_re[i] + z_im[i] * prev_im[i];
388 float di = z_im[i] * prev_re[i] - z_re[i] * prev_im[i];
389 int known = OFDMConfig::knownPhase(1, i) - OFDMConfig::knownPhase(0, i);
390 angle[i] = wrap(atan2f(di, dr) - known * (float)M_PI / 2);
391 weight[i] = sqrtf(dr * dr + di * di);
392 }
394 fit(angle, weight, ncarriers, pred_common, pred_slope);
395 drift = 0;
396 for (int b = 0; b < 32; b++) header_soft[b] = 0;
397 header_symbol = 0;
398 frame_len = -1;
399 ndata = 0;
400 current = 0;
401 current_dibits = 0;
402 error_total = 0;
403 error_count = 0;
404 for (int i = 0; i < ncarriers; i++) {
405 prev_re[i] = z_re[i];
406 prev_im[i] = z_im[i];
407 }
408 }
409
410 void dataSymbol() {
411 float angle[128], weight[128];
412 float symbol_power = 0;
413 int dibits[128];
414 bool is_header = frame_len < 0;
415 // header: 0 or 180 degrees, data: 0, 90, 180 or 270 degrees
416 float step = is_header ? (float)M_PI : (float)M_PI / 2;
417 for (int i = 0; i < ncarriers; i++) {
418 symbol_power += z_re[i] * z_re[i] + z_im[i] * z_im[i];
419 float dr = z_re[i] * prev_re[i] + z_im[i] * prev_im[i];
420 float di = z_im[i] * prev_re[i] - z_re[i] * prev_im[i];
421 float a = wrap(atan2f(di, dr) - pred_common -
422 (pred_slope + slope_adjust) * i);
423 int q = lroundf(a / step);
424 angle[i] = a - q * step; // residual error
425 weight[i] = sqrtf(dr * dr + di * di);
426 if (is_header) {
427 // soft decision: sum of all copies of the header bit
428 header_soft[(header_symbol * ncarriers + i) % 32] += cosf(a);
429 } else {
430 dibits[i] = phaseToGray(q & 3);
431 }
432 prev_re[i] = z_re[i];
433 prev_im[i] = z_im[i];
434 error_total += fabsf(angle[i]);
435 error_count++;
436 }
437 // the signal is lost
438 if (symbol_power / ncarriers < 0.1f * reference_power) {
439 endFrame(false);
440 return;
441 }
442 // track the frequency offset and the timing drift
443 float common, slope;
444 fit(angle, weight, ncarriers, common, slope);
445 pred_common += TRACKING_GAIN * common;
446 pred_slope += TRACKING_GAIN * slope;
447 // timing drift in samples per symbol
448 drift -= pred_slope * fft_size / (2.0f * (float)M_PI);
449
450 if (is_header) {
452 checkHeader();
453 return;
454 }
455 for (int i = 0; i < ncarriers && state == State::FRAME; i++)
456 dataBits(dibits[i]);
457 }
458
460 static int phaseToGray(int phase) {
461 static const int dibit[4] = {0, 1, 3, 2};
462 return dibit[phase];
463 }
464
465 void checkHeader() {
466 uint32_t header = 0;
467 for (int b = 0; b < 32; b++) header = (header << 1) | (header_soft[b] < 0);
468 uint16_t len = header >> 16;
469 uint16_t check = header & 0xFFFF;
470 if ((uint16_t)~len != check || len == 0) {
471 // not a valid frame: search again
473 in_plateau = false;
474 return;
475 }
476 frame_len = len;
477 status("### CARRIER ofdm%d %.1f-%.1f Hz len=%d ###\n", ncarriers,
478 (double)cfg.carrierFrequency(0),
479 (double)cfg.carrierFrequency(ncarriers - 1), frame_len);
480 }
481
482 void dataBits(int dibit) {
483 current = (current << 2) | dibit;
484 if (++current_dibits == 4) {
485 if (p_print != nullptr) p_print->write(current);
486 ndata++;
487 current = 0;
488 current_dibits = 0;
489 if (ndata == frame_len) endFrame(true);
490 }
491 }
492
493 void endFrame(bool complete) {
495 in_plateau = false;
496 if (frame_len < 0) return; // no valid header
497 if (complete) frames++;
498 float error = error_count > 0 ? error_total / error_count : 0;
499 status("\n### NOCARRIER ndata=%d phase_error=%.1f deg%s ###\n", ndata,
500 (double)(error * 180.0f / (float)M_PI),
501 complete ? "" : " (incomplete)");
502 frame_len = -1;
503 if (cfg.rx_one) is_finished = true;
504 }
505};
506
507} // namespace tinymodem
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
OFDMConfig defaultConfig()
Provides a default configuration: ofdm32.
Definition OFDMDecoder.h:31
void processSymbol()
Definition OFDMDecoder.h:346
void recalculate(uint32_t t)
Definition OFDMDecoder.h:223
int error_count
Definition OFDMDecoder.h:169
void compact()
Drops the samples which are not needed any more.
Definition OFDMDecoder.h:234
static constexpr float TRACKING_GAIN
Definition OFDMDecoder.h:125
int first_bin
Definition OFDMDecoder.h:136
int frame_len
Definition OFDMDecoder.h:164
OFDMTable table
Definition OFDMDecoder.h:132
int prefix_size
Definition OFDMDecoder.h:135
uint32_t filled
Definition OFDMDecoder.h:144
audio_tools::Vector< float > buffer
Definition OFDMDecoder.h:140
void endFrame(bool complete)
Definition OFDMDecoder.h:493
float header_soft[32]
Definition OFDMDecoder.h:162
void addSample(float x, bool add_noise)
Definition OFDMDecoder.h:190
static int phaseToGray(int phase)
0 -> 00, 90 -> 01, 180 -> 11, 270 -> 10 degrees
Definition OFDMDecoder.h:460
State state
Definition OFDMDecoder.h:155
int ncarriers
Definition OFDMDecoder.h:136
PcmInput input
Definition OFDMDecoder.h:131
State
Definition OFDMDecoder.h:124
@ SEARCH
Definition OFDMDecoder.h:124
@ FRAME
Definition OFDMDecoder.h:124
float sample(uint32_t idx)
Definition OFDMDecoder.h:188
int ndata
Definition OFDMDecoder.h:165
bool begin(OFDMConfig config)
Starts the processing with the indicated configuration.
Definition OFDMDecoder.h:53
audio_tools::Vector< float > z_re
Definition OFDMDecoder.h:158
OFDMConfig cfg
Definition OFDMDecoder.h:128
int symbol
Definition OFDMDecoder.h:157
void checkHeader()
Definition OFDMDecoder.h:465
static constexpr float MIN_POWER
Definition OFDMDecoder.h:126
float power
Definition OFDMDecoder.h:147
bool isFinished() override
true if the decoding was stopped (rx_one)
Definition OFDMDecoder.h:113
audio_tools::Vector< float > smoothed
Definition OFDMDecoder.h:151
float noise()
uniform random value in the range -0.5 to 0.5
Definition OFDMDecoder.h:183
float slope_adjust
Definition OFDMDecoder.h:159
audio_tools::Vector< float > prev_re
Definition OFDMDecoder.h:158
int current_dibits
Definition OFDMDecoder.h:167
void referenceSymbol()
Definition OFDMDecoder.h:377
audio_tools::Vector< float > prev_im
Definition OFDMDecoder.h:158
bool is_finished
Definition OFDMDecoder.h:130
OFDMDecoder(Print &out)
Definition OFDMDecoder.h:28
uint8_t current
Definition OFDMDecoder.h:166
size_t frameCount()
Number of received frames.
Definition OFDMDecoder.h:116
static bool isReached(uint32_t a, uint32_t b)
true if the global index a is at or after b (with wrap around)
Definition OFDMDecoder.h:221
static void fit(const float *angle, const float *weight, int n, float &common, float &slope)
Weighted linear fit: angle = common + slope * carrier.
Definition OFDMDecoder.h:324
int half
Definition OFDMDecoder.h:135
int symbol_size
Definition OFDMDecoder.h:135
size_t nvalid
Definition OFDMDecoder.h:141
int plateau_len
Definition OFDMDecoder.h:152
bool in_plateau
Definition OFDMDecoder.h:148
OFDMConfig & config()
The configuration which was provided in begin().
Definition OFDMDecoder.h:119
uint32_t now
Definition OFDMDecoder.h:143
audio_tools::Vector< float > plateau
Definition OFDMDecoder.h:150
float reference_power
Definition OFDMDecoder.h:161
void dataSymbol()
Definition OFDMDecoder.h:410
uint32_t base
Definition OFDMDecoder.h:142
uint32_t window
Definition OFDMDecoder.h:156
bool isCarrier() override
true while a frame is received
Definition OFDMDecoder.h:110
void search(uint32_t d)
Definition OFDMDecoder.h:248
float pred_common
Definition OFDMDecoder.h:159
void setAudioInfo(audio_tools::AudioInfo info) override
Defines the format of the input audio data.
Definition OFDMDecoder.h:38
uint32_t plateau_start
Definition OFDMDecoder.h:149
void dataBits(int dibit)
Definition OFDMDecoder.h:482
int fft_size
Definition OFDMDecoder.h:135
float pred_slope
Definition OFDMDecoder.h:159
void flush() override
Processes the remaining samples (e.g. at the end of the input).
Definition OFDMDecoder.h:90
float corr
Definition OFDMDecoder.h:147
audio_tools::AudioInfo audioInfo() override
Definition OFDMDecoder.h:48
static float wrap(float angle)
Definition OFDMDecoder.h:317
audio_tools::Vector< float > z_im
Definition OFDMDecoder.h:158
uint32_t noise_seed
Definition OFDMDecoder.h:133
void dft()
DFT of the window at the carrier frequencies.
Definition OFDMDecoder.h:300
bool is_active
Definition OFDMDecoder.h:129
float drift
Definition OFDMDecoder.h:160
float error_total
Definition OFDMDecoder.h:168
size_t frames
Definition OFDMDecoder.h:134
bool begin() override
Definition OFDMDecoder.h:58
int header_symbol
Definition OFDMDecoder.h:163
size_t write(const uint8_t *data, size_t len) override
Provide PCM data to the decoder.
Definition OFDMDecoder.h:102
void resetState()
Definition OFDMDecoder.h:171
void end() override
Processes the remaining samples.
Definition OFDMDecoder.h:83
Sine and cosine of 2 * PI * i / size.
Definition OFDM.h:140
Assembles the PCM frames from the written bytes and provides the samples of the selected channel as f...
Definition ModemBase.h:170
TinyModem: software audio modems and packets with error correction.
Definition AX25.h:11
Configuration of the OFDM modem (OFDMEncoder, OFDMDecoder): the data is transmitted in parallel on ca...
Definition OFDM.h:23
static int knownPhase(int symbol, int carrier)
Definition OFDM.h:120
static int headerSymbols(int carriers)
Number of symbols of the header (32 bits, one bit per carrier).
Definition OFDM.h:116
bool setMode(const char *mode)
Selects "ofdm16", "ofdm32" or "ofdm64": returns false if invalid.
Definition OFDM.h:52