TinyModem
Header-only software audio modem library
Loading...
Searching...
No Matches
GGWaveDecoder.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 "GGWave.h"
8#include "ModemBase.h"
9#include "OFDM.h"
10
11namespace tinymodem {
12
40 public:
41 GGWaveDecoder() = default;
42 GGWaveDecoder(Print& out) { setOutput(out); }
43
46 GGWaveConfig result;
47 result.setMode("ggwave-fast");
48 return result;
49 }
50
52 void setAudioInfo(audio_tools::AudioInfo info) override {
53 if (info.sample_rate == cfg.sample_rate && info.channels == cfg.channels &&
54 info.bits_per_sample == cfg.bits_per_sample)
55 return;
56 cfg.sample_rate = info.sample_rate;
57 cfg.channels = info.channels;
58 cfg.bits_per_sample = info.bits_per_sample;
59 if (is_active) begin();
60 }
61
62 audio_tools::AudioInfo audioInfo() override { return cfg; }
63
64 using audio_tools::AudioDecoder::begin;
65
68 cfg = config;
69 return begin();
70 }
71
72 bool begin() override {
73 is_active = false;
74 if (!cfg.isValid()) return false;
75 if (!input.begin(cfg.bits_per_sample, cfg.channels, cfg.channel))
76 return false;
77 frame_size = cfg.frameSize();
79 const GGWaveProtocol& selected = cfg.protocolInfo();
80 freq_start = selected.freq_start;
81 // all protocols with the same tones (they differ only in the speed)
82 nprotocols = 0;
83 for (int j = 0; j < GGWaveConfig::protocolCount(); j++) {
85 if (p.freq_start != freq_start) continue;
86 if (!cfg.rx_any_speed && p.id != selected.id) continue;
88 }
89 data_bins = 16 * selected.tones();
90 // the marker test also needs the neighbor of the highest marker bin
92 ? data_bins
94 table.begin(frame_size);
95 table_idx.resize(nbins);
96 block_re.resize(nbins);
97 block_im.resize(nbins);
98 ring_re.resize(WINDOW * nbins);
99 ring_im.resize(WINDOW * nbins);
100 ring_power.resize(RING * nbins);
102 for (int j = 0; j < (int)candidates.size(); j++)
103 candidates[j].acc.resize(data_bins);
104 // score of a marker whose tones are marker_threshold times stronger
105 float thr = cfg.marker_threshold;
106 detect_score = GGWaveConfig::MARKER_TONES * (thr - 1) / (thr + 1);
107 resetState();
108 is_active = true;
109 return true;
110 }
111
113 void end() override {
114 if (!is_active) return;
115 flush();
116 is_active = false;
117 }
118
120 void flush() override {
121 if (!is_active) return;
122 // feed silence, so that a pending message is completed
123 size_t n = (size_t)(GGWaveConfig::MARKER_FRAMES + 4) * frame_size;
124 for (size_t j = 0; j < n && !is_finished; j++) {
125 if (!run_active && !isCarrier() && sample_pos == 0) break;
126 addSample(0.0f, false);
127 }
128 if (isCarrier()) failed();
129 bool finished = is_finished;
130 resetState();
131 is_finished = finished;
132 }
133
135 size_t write(const uint8_t* data, size_t len) override {
136 if (!is_active) return 0;
137 input.write(data, len, [this](float sample) { addSample(sample, true); });
138 return len;
139 }
140
142 bool isCarrier() override { return nalive > 0; }
143
145 bool isFinished() override { return is_finished; }
146
148 size_t messageCount() { return messages; }
149
151 GGWaveConfig& config() { return cfg; }
152
153 operator bool() override { return is_active; }
154
155 protected:
157 static constexpr int RING = 24;
159 static constexpr int SCORE_RING = 128;
161 static constexpr int WINDOW = 4;
163 static constexpr int BLOCKS = 4;
165 static constexpr int NOFFSETS = 3;
167 static constexpr int MAX_MISSES = 8;
169 static constexpr int MIN_DETECTIONS = 24;
171 static constexpr int MARKER_WINDOWS =
175 2 * (GGWaveConfig::MAX_PAYLOAD / 5) + 3;
176
178 struct Candidate {
179 bool alive = false;
181 uint32_t start = 0; // first block of the data
182 uint32_t next = 0; // next block to process
183 int symbol = 0;
184 int nsymbols = 0; // 0 = length not known yet
185 int length = 0;
186 bool data_done = false;
187 float end_score = 0;
188 audio_tools::Vector<float> acc; // power of the bins
190 };
191
193 bool is_active = false;
194 bool is_finished = false;
197 uint32_t noise_seed = 1;
198 size_t messages = 0;
199 int frame_size = 1024;
200 int block_size = 256;
201 int freq_start = 40;
202 int data_bins = 96;
203 int nbins = 96;
205 int nprotocols = 0;
206
207 // DFT of the current block
208 audio_tools::Vector<int> table_idx;
209 audio_tools::Vector<float> block_re, block_im;
210 int sample_pos = 0;
211 // DFT of the last WINDOW blocks
212 audio_tools::Vector<float> ring_re, ring_im;
213 // power of the window which ends in a block
214 audio_tools::Vector<float> ring_power;
215 float scores[SCORE_RING]; // start marker score of each block
216 float detect_score = 8; // min score of a detected marker
217 uint32_t block_no = 0;
218
219 // start marker
220 bool run_active = false;
221 uint32_t run_first = 0;
223 int run_misses = 0;
224
225 audio_tools::Vector<Candidate> candidates;
226 int nalive = 0;
227
228 void resetState() {
229 for (int b = 0; b < nbins; b++) {
230 table_idx[b] = 0;
231 block_re[b] = block_im[b] = 0;
232 }
233 sample_pos = 0;
234 block_no = 0;
235 run_active = false;
237 is_finished = false;
238 input.reset();
239 }
240
242 for (int j = 0; j < (int)candidates.size(); j++)
243 candidates[j].alive = false;
244 nalive = 0;
245 }
246
248 float noise() {
249 noise_seed = noise_seed * 1103515245u + 12345u;
250 return ((noise_seed >> 8) & 0xFFFF) / 65535.0f - 0.5f;
251 }
252
253 void addSample(float x, bool add_noise) {
254 if (is_finished) return;
255 if (add_noise && cfg.rx_noise != 0.0f)
256 x += noise() * cfg.rx_noise * 2.0f;
257 // DFT of the block: the index of the sample is counted modulo the frame
258 // size, so the blocks of a frame can be added
259 const int n = frame_size;
260 for (int b = 0; b < nbins; b++) {
261 int idx = table_idx[b];
262 block_re[b] += x * table.cosine(idx);
263 block_im[b] -= x * table.sine(idx);
264 idx += freq_start + b;
265 if (idx >= n) idx -= n;
266 table_idx[b] = idx;
267 }
268 if (++sample_pos == block_size) {
269 sample_pos = 0;
270 endBlock();
271 }
272 }
273
274 float* ringRe(uint32_t block) { return &ring_re[(block % WINDOW) * nbins]; }
275 float* ringIm(uint32_t block) { return &ring_im[(block % WINDOW) * nbins]; }
276 float* ringPower(uint32_t block) {
277 return &ring_power[(block % RING) * nbins];
278 }
279
280 void endBlock() {
281 uint32_t t = block_no;
282 float* re = ringRe(t);
283 float* im = ringIm(t);
284 for (int b = 0; b < nbins; b++) {
285 re[b] = block_re[b];
286 im[b] = block_im[b];
287 block_re[b] = block_im[b] = 0;
288 }
289 // power in the window of the last frame
290 float* power = ringPower(t);
291 float score = 0;
292 if (t + 1 >= (uint32_t)WINDOW) {
293 for (int b = 0; b < nbins; b++) {
294 float sum_re = 0, sum_im = 0;
295 for (int k = 0; k < WINDOW; k++) {
296 sum_re += ringRe(t - k)[b];
297 sum_im += ringIm(t - k)[b];
298 }
299 power[b] = sum_re * sum_re + sum_im * sum_im;
300 }
301 score = markerScore(power, nbins);
302 } else {
303 for (int b = 0; b < nbins; b++) power[b] = 0;
304 }
305 scores[t % SCORE_RING] = score;
307 block_no++;
309 }
310
315 float markerScore(const float* power, int n) {
316 float max_power = 0;
317 for (int b = 0; b < n; b++)
318 if (power[b] > max_power) max_power = power[b];
319 float floor = max_power * 1e-4f;
320 if (floor <= 0) return 0;
321 float score = 0;
322 for (int i = 0; i < GGWaveConfig::MARKER_TONES; i++) {
323 float on = power[2 * i + i % 2];
324 float off = power[2 * i + 1 - i % 2];
325 float sum = on + off;
326 if (sum > floor) score += (on - off) / sum;
327 }
328 return score;
329 }
330
331 void trackStartMarker(bool detected) {
332 uint32_t t = block_no;
333 if (detected) {
334 if (!run_active) {
335 run_active = true;
336 run_first = t;
337 run_detections = 0;
338 }
340 run_misses = 0;
341 return;
342 }
343 if (!run_active || ++run_misses <= MAX_MISSES) return;
344 run_active = false;
345 if (run_detections < MIN_DETECTIONS) return;
346 // find the windows which are completely inside the marker: they have
347 // the highest sum of the scores
348 uint32_t oldest = t + 1 > (uint32_t)SCORE_RING ? t + 1 - SCORE_RING : 0;
349 uint32_t from = run_first > oldest + 2 * BLOCKS ? run_first - 2 * BLOCKS
350 : oldest;
351 if (t + 1 < from + MARKER_WINDOWS) return;
352 uint32_t to = t + 1 - MARKER_WINDOWS;
353 float sum = 0;
354 for (int k = 0; k < MARKER_WINDOWS; k++) sum += scores[(from + k) % SCORE_RING];
355 float best = sum;
356 uint32_t best_pos = from;
357 for (uint32_t pos = from + 1; pos <= to; pos++) {
358 sum += scores[(pos + MARKER_WINDOWS - 1) % SCORE_RING] -
359 scores[(pos - 1) % SCORE_RING];
360 if (sum > best) {
361 best = sum;
362 best_pos = pos;
363 }
364 }
365 // the first window inside the marker ends in its block WINDOW - 1
367 }
368
369 void startCandidates(uint32_t data_start) {
370 if (nalive > 0) failed();
371 // the oldest block which is still in the history
372 uint32_t oldest = block_no + 1 > (uint32_t)RING ? block_no + 1 - RING : 0;
373 int idx = 0;
374 for (int j = 0; j < nprotocols; j++) {
375 for (int k = 0; k < NOFFSETS; k++) {
376 Candidate& c = candidates[idx++];
377 uint32_t start = data_start + k - NOFFSETS / 2;
378 c.alive = start >= oldest;
379 if (!c.alive) {
380 LOGW("ggwave: start of the data is not in the history");
381 continue;
382 }
383 c.protocol = rx_protocols[j];
384 c.start = c.next = start;
385 c.symbol = 0;
386 c.nsymbols = 0;
387 c.length = 0;
388 c.data_done = false;
389 c.end_score = 0;
391 nalive++;
392 }
393 }
394 if (nalive > 0) {
395 status("### CARRIER ggwave %.0f-%.0f Hz ###\n",
396 (double)cfg.binFrequency(freq_start),
397 (double)cfg.binFrequency(freq_start + data_bins - 1));
398 }
399 }
400
402 for (int b = 0; b < data_bins; b++) c.acc[b] = 0;
403 }
404
406 void processCandidates(uint32_t t) {
407 for (int j = 0; j < (int)candidates.size() && nalive > 0; j++) {
408 Candidate& c = candidates[j];
409 while (c.alive && (int32_t)(c.next - t) <= 0) {
410 processBlock(c, c.next);
411 c.next++;
412 if (is_finished || nalive == 0) return;
413 }
414 }
415 }
416
417 void processBlock(Candidate& c, uint32_t t) {
418 int symbol_blocks = c.protocol.frames_per_tx * BLOCKS;
419 uint32_t rel = t - c.start;
420 if (!c.data_done) {
421 int within = rel % symbol_blocks;
422 // windows inside the symbol without its first and last block
423 // (envelope and timing)
424 if (within >= WINDOW && within < symbol_blocks - 1) {
425 const float* power = ringPower(t);
426 for (int b = 0; b < data_bins; b++) c.acc[b] += power[b];
427 }
428 if (within == symbol_blocks - 1) endSymbol(c);
429 return;
430 }
431 // the end marker must follow the data
432 uint32_t end_start = (uint32_t)c.nsymbols * symbol_blocks;
433 uint32_t first = end_start + 2 * BLOCKS;
434 uint32_t last = end_start + (GGWaveConfig::MARKER_FRAMES - 2) * BLOCKS;
435 if (rel >= first && rel < last) c.end_score -= scores[t % SCORE_RING];
436 if (rel == last - 1) {
437 if (c.end_score >= 0.5f * detect_score * (last - first)) {
438 accept(c);
439 } else {
440 kill(c);
441 }
442 }
443 }
444
446 int bpt = c.protocol.bytes_per_tx;
447 for (int i = 0; i < 2 * bpt; i++) {
448 int best = 0;
449 float max_power = -1;
450 for (int k = 0; k < 16; k++) {
451 int b = 16 * i + k;
452 if (c.acc[b] > max_power) {
453 max_power = c.acc[b];
454 best = k;
455 }
456 }
457 uint8_t& value = c.bytes[c.symbol * bpt + i / 2];
458 if (i % 2 == 0) {
459 value = best;
460 } else {
461 value |= best << 4;
462 }
463 }
464 c.symbol++;
466
467 if (c.nsymbols == 0 && c.symbol * bpt >= GGWaveConfig::LENGTH_BYTES) {
468 uint8_t tmp[GGWaveConfig::LENGTH_BYTES];
469 memcpy(tmp, c.bytes, sizeof(tmp));
471 tmp[0] == 0 || tmp[0] > GGWaveConfig::MAX_PAYLOAD) {
472 kill(c);
473 return;
474 }
475 c.length = tmp[0];
476 int total = GGWaveConfig::encodedBytes(c.length);
477 c.nsymbols = (total + bpt - 1) / bpt;
478 }
479 if (c.nsymbols > 0 && c.symbol == c.nsymbols) {
480 uint8_t* data = c.bytes + GGWaveConfig::LENGTH_BYTES;
482 kill(c);
483 return;
484 }
485 c.data_done = true;
486 }
487 }
488
489 void kill(Candidate& c) {
490 c.alive = false;
491 if (--nalive == 0) failed();
492 }
493
494 void failed() {
496 status("### NOCARRIER ndata=0 (no valid message) ###\n");
497 }
498
499 void accept(Candidate& c) {
500 uint8_t* data = c.bytes + GGWaveConfig::LENGTH_BYTES;
501 for (int i = 0; i < c.length; i++) {
502 uint8_t value = data[i] ^ (cfg.dss ? GGWaveConfig::dssMagic(i) : 0);
503 if (p_print != nullptr) p_print->write(value);
504 }
505 messages++;
506 status("\n### NOCARRIER ndata=%d %s ###\n", c.length, c.protocol.mode);
508 if (cfg.rx_one) is_finished = true;
509 }
510};
511
512} // namespace tinymodem
audio_tools::Vector< float > block_re
Definition GGWaveDecoder.h:209
GGWaveDecoder(Print &out)
Definition GGWaveDecoder.h:42
float markerScore(const float *power, int n)
Definition GGWaveDecoder.h:315
GGWaveConfig cfg
Definition GGWaveDecoder.h:192
static constexpr int MARKER_WINDOWS
number of windows which are completely inside a marker
Definition GGWaveDecoder.h:171
static constexpr int RING
number of blocks of the history of the window power
Definition GGWaveDecoder.h:157
float noise()
uniform random value in the range -0.5 to 0.5
Definition GGWaveDecoder.h:248
static constexpr int SCORE_RING
number of blocks of the history of the marker score
Definition GGWaveDecoder.h:159
void clearCandidates()
Definition GGWaveDecoder.h:241
static constexpr int MAX_ENCODED
Definition GGWaveDecoder.h:173
GGWaveConfig defaultConfig()
Provides a default configuration: ggwave-fast.
Definition GGWaveDecoder.h:45
void addSample(float x, bool add_noise)
Definition GGWaveDecoder.h:253
void resetState()
Definition GGWaveDecoder.h:228
bool isFinished() override
true if the decoding was stopped (rx_one)
Definition GGWaveDecoder.h:145
static constexpr int MIN_DETECTIONS
a marker must be detected in this number of blocks (of 64)
Definition GGWaveDecoder.h:169
audio_tools::Vector< float > ring_im
Definition GGWaveDecoder.h:212
bool run_active
Definition GGWaveDecoder.h:220
bool begin(GGWaveConfig config)
Starts the processing with the indicated configuration.
Definition GGWaveDecoder.h:67
bool is_active
Definition GGWaveDecoder.h:193
static constexpr int BLOCKS
blocks per frame
Definition GGWaveDecoder.h:163
void accept(Candidate &c)
Definition GGWaveDecoder.h:499
PcmInput input
Definition GGWaveDecoder.h:195
void trackStartMarker(bool detected)
Definition GGWaveDecoder.h:331
uint32_t noise_seed
Definition GGWaveDecoder.h:197
float scores[SCORE_RING]
Definition GGWaveDecoder.h:215
audio_tools::Vector< float > ring_power
Definition GGWaveDecoder.h:214
int sample_pos
Definition GGWaveDecoder.h:210
int run_detections
Definition GGWaveDecoder.h:222
void kill(Candidate &c)
Definition GGWaveDecoder.h:489
float detect_score
Definition GGWaveDecoder.h:216
void processCandidates(uint32_t t)
Provides the blocks up to t to all candidates.
Definition GGWaveDecoder.h:406
void startCandidates(uint32_t data_start)
Definition GGWaveDecoder.h:369
int block_size
Definition GGWaveDecoder.h:200
static constexpr int MAX_MISSES
the marker is lost after this number of blocks without detection
Definition GGWaveDecoder.h:167
float * ringRe(uint32_t block)
Definition GGWaveDecoder.h:274
void setAudioInfo(audio_tools::AudioInfo info) override
Defines the format of the input audio data.
Definition GGWaveDecoder.h:52
GGWaveProtocol rx_protocols[3]
Definition GGWaveDecoder.h:204
GGWaveConfig & config()
The configuration which was provided in begin().
Definition GGWaveDecoder.h:151
bool is_finished
Definition GGWaveDecoder.h:194
void processBlock(Candidate &c, uint32_t t)
Definition GGWaveDecoder.h:417
audio_tools::Vector< Candidate > candidates
Definition GGWaveDecoder.h:225
uint32_t run_first
Definition GGWaveDecoder.h:221
audio_tools::AudioInfo audioInfo() override
Definition GGWaveDecoder.h:62
uint32_t block_no
Definition GGWaveDecoder.h:217
int nbins
Definition GGWaveDecoder.h:203
bool isCarrier() override
true while a message is received
Definition GGWaveDecoder.h:142
size_t write(const uint8_t *data, size_t len) override
Provide PCM data to the decoder.
Definition GGWaveDecoder.h:135
float * ringIm(uint32_t block)
Definition GGWaveDecoder.h:275
OFDMTable table
Definition GGWaveDecoder.h:196
int frame_size
Definition GGWaveDecoder.h:199
int nprotocols
Definition GGWaveDecoder.h:205
audio_tools::Vector< float > ring_re
Definition GGWaveDecoder.h:212
void endSymbol(Candidate &c)
Definition GGWaveDecoder.h:445
audio_tools::Vector< int > table_idx
Definition GGWaveDecoder.h:208
void endBlock()
Definition GGWaveDecoder.h:280
int nalive
Definition GGWaveDecoder.h:226
static constexpr int WINDOW
number of blocks of the marker detection window (one frame)
Definition GGWaveDecoder.h:161
int freq_start
Definition GGWaveDecoder.h:201
float * ringPower(uint32_t block)
Definition GGWaveDecoder.h:276
size_t messages
Definition GGWaveDecoder.h:198
int data_bins
Definition GGWaveDecoder.h:202
void failed()
Definition GGWaveDecoder.h:494
audio_tools::Vector< float > block_im
Definition GGWaveDecoder.h:209
void clearAccumulator(Candidate &c)
Definition GGWaveDecoder.h:401
size_t messageCount()
Number of received messages.
Definition GGWaveDecoder.h:148
static constexpr int NOFFSETS
timing offsets of the candidates: -1, 0, +1 block
Definition GGWaveDecoder.h:165
int run_misses
Definition GGWaveDecoder.h:223
void end() override
Processes the remaining samples.
Definition GGWaveDecoder.h:113
void flush() override
Processes the remaining samples (e.g. at the end of the input).
Definition GGWaveDecoder.h:120
bool begin() override
Definition GGWaveDecoder.h:72
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
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
static bool decode(uint8_t *data, int len, int ecc, int fcr=0)
Definition ReedSolomon.h:51
TinyModem: software audio modems and packets with error correction.
Definition AX25.h:11
Configuration of the ggwave compatible modem (GGWaveEncoder, GGWaveDecoder). Each message (up to 140 ...
Definition GGWave.h:74
static uint8_t dssMagic(int i)
Value of the ggwave DSS sequence for the indicated payload byte.
Definition GGWave.h:220
static constexpr int MARKER_TONES
number of tones of a marker
Definition GGWave.h:89
static constexpr int MARKER_FRAMES
number of frames of the start and end marker
Definition GGWave.h:87
static int encodedBytes(int len)
Total number of encoded bytes of a message (length, payload, parity).
Definition GGWave.h:169
static constexpr int LENGTH_BYTES
bytes of the Reed-Solomon coded length at the start of a message
Definition GGWave.h:91
static const GGWaveProtocol * protocols()
Provides the supported protocols.
Definition GGWave.h:132
static int eccBytes(int len)
Number of Reed-Solomon parity bytes for a payload of len bytes.
Definition GGWave.h:164
bool setMode(const char *mode)
Selects the protocol by mode name: returns false if invalid.
Definition GGWave.h:112
static int protocolCount()
Number of supported protocols.
Definition GGWave.h:129
static constexpr int MAX_PAYLOAD
max payload of a message (variable length format)
Definition GGWave.h:85
decoder for one protocol and timing offset
Definition GGWaveDecoder.h:178
int symbol
Definition GGWaveDecoder.h:183
audio_tools::Vector< float > acc
Definition GGWaveDecoder.h:188
bool data_done
Definition GGWaveDecoder.h:186
bool alive
Definition GGWaveDecoder.h:179
GGWaveProtocol protocol
Definition GGWaveDecoder.h:180
int length
Definition GGWaveDecoder.h:185
float end_score
Definition GGWaveDecoder.h:187
uint32_t next
Definition GGWaveDecoder.h:182
uint32_t start
Definition GGWaveDecoder.h:181
uint8_t bytes[MAX_ENCODED]
Definition GGWaveDecoder.h:189
int nsymbols
Definition GGWaveDecoder.h:184
Transmission protocol of ggwave (https://github.com/ggerganov/ggwave): the ids and parameters are ide...
Definition GGWave.h:36
const char * mode
TinyModem mode name.
Definition GGWave.h:40
int tones() const
number of simultaneous tones in a data symbol
Definition GGWave.h:49
GGWaveProtocolId id
ggwave protocol id
Definition GGWave.h:38
int frames_per_tx
number of frames per transmitted chunk (symbol)
Definition GGWave.h:44
int freq_start
FFT bin of the lowest tone.
Definition GGWave.h:42
int bytes_per_tx
number of bytes per chunk: 2 tones per byte
Definition GGWave.h:46