TinyModem
Header-only software audio modem library
Loading...
Searching...
No Matches
MFSKDecoder.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 "AudioTools/CoreAudio/Analysis/GoerzelStream.h"
8#include "MFSKConfig.h"
9#include "MFSKEncoder.h"
10#include "ModemBase.h"
11
12namespace tinymodem {
13
31class MFSKDecoder : public ModemDecoder {
32 public:
33 MFSKDecoder() = default;
34 MFSKDecoder(Print& out) { setOutput(out); }
35
38 MFSKConfig result;
39 result.setMode("mfsk16");
40 return result;
41 }
42
44 void setAudioInfo(audio_tools::AudioInfo info) override {
45 if (info.sample_rate == cfg.sample_rate && info.channels == cfg.channels &&
46 info.bits_per_sample == cfg.bits_per_sample)
47 return;
48 cfg.sample_rate = info.sample_rate;
49 cfg.channels = info.channels;
50 cfg.bits_per_sample = info.bits_per_sample;
51 if (is_active) begin();
52 }
53
54 audio_tools::AudioInfo audioInfo() override { return cfg; }
55
56 using audio_tools::AudioDecoder::begin;
57
60 cfg = config;
61 return begin();
62 }
63
64 bool begin() override {
65 is_active = false;
66 if (!cfg.isValid()) return false;
67 if (!input.begin(cfg.bits_per_sample, cfg.channels, cfg.channel))
68 return false;
69 ntones = cfg.tones;
70 bits_per_symbol = cfg.bitsPerSymbol();
71 detectors.resize(ntones);
72 magnitudes.resize(ntones);
73 tune(1.0f);
74 // the frequency estimation is only needed (and reliable) if the clock
75 // error can shift a tone by a relevant part of the tone spacing
76 estimate_clock = cfg.toneFrequency(ntones - 1) * MAX_CLOCK_ERROR >
77 cfg.spacing() / 4;
78 search_detectors.resize(estimate_clock ? 2 * SEARCH_STEPS + 1 : 0);
79 for (int k = 0; k < (int)search_detectors.size(); k++) {
80 float error = (k - SEARCH_STEPS) * SEARCH_STEP;
81 setupDetector(search_detectors[k], cfg.toneFrequency(0) * (1.0f + error));
82 }
83
84 // room for the preamble acquisition and a symbol with the timing gate
85 float max_nsamples = nsamples * (1.0f + MAX_CLOCK_ERROR);
86 size_t size = (ACQUIRE_SYMBOLS + 4) * max_nsamples + 2;
87 samples.resize(size);
88 resetState();
89 is_active = true;
90 return true;
91 }
92
94 void end() override {
95 if (!is_active) return;
96 flush();
97 is_active = false;
98 }
99
101 void flush() override {
102 if (!is_active) return;
103 // feed silence, so that the end of the transmission is detected
104 size_t n = samples.size();
105 for (size_t j = 0; j < n && !is_finished; j++) addSample(0.0f, false);
107 // with rx_one the decoding stays stopped until begin()
108 bool finished = is_finished;
109 resetState();
110 is_finished = finished;
111 }
112
114 size_t write(const uint8_t* data, size_t len) override {
115 if (!is_active) return 0;
116 input.write(data, len, [this](float sample) { addSample(sample, true); });
117 return len;
118 }
119
121 bool isCarrier() override { return state == State::DATA; }
122
124 bool isFinished() override { return is_finished; }
125
127 MFSKConfig& config() { return cfg; }
128
129 operator bool() override { return is_active; }
130
131 protected:
132 enum class State { SEARCH, PREAMBLE, DATA };
134 static constexpr int ACQUIRE_SYMBOLS = 4;
136 static constexpr int MAX_PREAMBLE = 64;
138 static constexpr float MAX_CLOCK_ERROR = 0.03f;
139 static constexpr float CLOCK_ERROR_STEP = 0.001f;
141 static constexpr float MIN_CLOCK_GAIN = 1.2f;
143 static constexpr float TIMING_GAIN = 0.25f;
144 static constexpr float DRIFT_GAIN = 0.05f;
146 static constexpr float LOST_LEVEL = 0.4f;
149 static constexpr float NEW_SIGNAL_LEVEL = 4.0f;
152 static constexpr float CLEAR_RATIO = 1.5f;
155 static constexpr float HIGH_TONE_LEVEL = 0.25f;
157 static constexpr float MAX_CONFIDENCE = 100.0f;
159 static constexpr int SEARCH_STEPS = 3;
160 static constexpr float SEARCH_STEP = 0.01f;
161
163 bool is_active = false;
164 bool is_finished = false;
166 audio_tools::Vector<audio_tools::GoertzelDetector> detectors;
167 audio_tools::Vector<audio_tools::GoertzelDetector> search_detectors;
168 audio_tools::Vector<float> magnitudes;
169 audio_tools::Vector<float> samples;
170 size_t nvalid = 0;
171 size_t needed = 0;
172 int ntones = 16;
174 float nsamples = 0; // samples per symbol
175 int window = 0; // analysis window
176 int delta = 1; // offset of the early/late gate
178 int hop = 1;
179 uint32_t noise_seed = 1;
181 float scale = 1.0f;
183 float drift = 0.0f;
185 bool estimate_clock = true;
186
187 // receiver state
189 float pos = 0; // start of the next symbol in samples
192 int sync_stage = 0;
193 bool has_pending = false;
195 uint32_t bit_buffer = 0;
196 int bit_count = 0;
197 // statistics
198 unsigned ndata = 0;
199 unsigned nsymbols = 0;
202 double data_nsamples = 0;
203
207 void tune(float new_scale) {
208 scale = new_scale;
209 nsamples = cfg.samplesPerSymbol() / scale;
210 window = lroundf(nsamples);
211 delta = lroundf(nsamples / 8);
212 if (delta < 1) delta = 1;
213 acquire_step = window / 16;
214 if (acquire_step < 1) acquire_step = 1;
215 hop = window / 2;
216 for (int j = 0; j < ntones; j++)
217 setupDetector(detectors[j], cfg.toneFrequency(j) * scale);
218 }
219
220 void setupDetector(audio_tools::GoertzelDetector& det, float freq) {
221 audio_tools::GoertzelConfig gcfg;
222 gcfg.sample_rate = cfg.sample_rate;
223 gcfg.channels = 1;
224 gcfg.target_frequency = freq;
225 gcfg.block_size = window;
226 det.begin(gcfg);
227 }
228
230 void startSearch() {
232 if (scale != 1.0f) tune(1.0f);
233 }
234
235 void resetState() {
236 nvalid = 0;
237 needed = 0;
238 startSearch();
239 pos = 0;
240 is_finished = false;
241 input.reset();
242 }
243
245 float noise() {
246 noise_seed = noise_seed * 1103515245u + 12345u;
247 return ((noise_seed >> 8) & 0xFFFF) / 65535.0f - 0.5f;
248 }
249
250 void addSample(float sample, bool add_noise) {
251 if (is_finished) return;
252 if (add_noise && cfg.rx_noise != 0.0f)
253 sample += noise() * cfg.rx_noise * 2.0f;
254 if (nvalid == samples.size()) discard(hop); // never overflow
255 samples[nvalid++] = sample;
256 if (nvalid >= needed || nvalid == samples.size()) {
257 while (!is_finished && step()) {
258 }
259 }
260 }
261
263 void discard(size_t n) {
264 if (n == 0) return;
265 if (n > nvalid) n = nvalid;
266 memmove(samples.data(), samples.data() + n,
267 (nvalid - n) * sizeof(float));
268 nvalid -= n;
269 pos -= n;
270 }
271
273 float magnitude(int tone, int start) {
274 return magnitude(detectors[tone], start);
275 }
276
278 float magnitude(audio_tools::GoertzelDetector& det, int start) {
279 det.reset();
280 const float* data = samples.data() + start;
281 for (int j = 0; j < window; j++) det.processSample(data[j]);
282 return det.getMagnitude() * 2.0f / window;
283 }
284
286 float windowAmplitude(int start) {
287 const float* data = samples.data() + start;
288 float sum = 0;
289 for (int j = 0; j < window; j++) sum += data[j] * data[j];
290 return sqrtf(2.0f * sum / window);
291 }
292
294 bool step() {
295 return state == State::SEARCH ? searchStep() : symbolStep();
296 }
297
299 bool searchStep() {
300 size_t need = (size_t)((ACQUIRE_SYMBOLS + 1) * nsamples) + window + 1;
301 if (nvalid < need) {
302 needed = need;
303 return false;
304 }
305 // quick check for silence
306 float rms_amplitude = windowAmplitude(0);
307 if (rms_amplitude < cfg.min_amplitude) {
308 discard(hop);
309 return true;
310 }
311 // look for the lowest preamble tone with all possible clock errors
312 float best = 0, best_error = 0, nominal = 0;
313 if (!estimate_clock) best = nominal = magnitude(0, 0);
314 for (int k = 0; k < (int)search_detectors.size(); k++) {
315 float a = magnitude(search_detectors[k], 0);
316 if (k == SEARCH_STEPS) nominal = a;
317 if (a > best) {
318 best = a;
319 best_error = (k - SEARCH_STEPS) * SEARCH_STEP;
320 }
321 }
322 // only assume a clock error if this is clearly better
323 if (best < MIN_CLOCK_GAIN * nominal) best_error = 0;
324 if (best >= cfg.min_amplitude && best > 0.5f * rms_amplitude &&
325 hasHighTone(best_error, best) && acquire(best_error)) {
327 preamble_count = 0;
328 sync_stage = 0;
329 return true;
330 }
331 discard(hop);
332 return true;
333 }
334
340 bool hasHighTone(float clock_error, float low) {
341 audio_tools::GoertzelDetector det;
342 setupDetector(det, cfg.toneFrequency(ntones - 1) * (1.0f + clock_error));
343 for (int k = 0; k <= ACQUIRE_SYMBOLS; k++) {
344 if (magnitude(det, lroundf(k * nsamples)) >= HIGH_TONE_LEVEL * low)
345 return true;
346 }
347 return false;
348 }
349
353 bool acquire(float coarse_error) {
354 if (coarse_error != 0.0f) tune(1.0f + coarse_error);
355 int offset = findTiming();
356 if (offset >= 0 && estimate_clock && estimateClock(offset)) {
357 // the timing changes with the clock
358 offset = findTiming();
359 }
360 if (offset < 0) {
361 startSearch();
362 return false;
363 }
364 // the tones must alternate and be clearly distinguishable
365 float amplitude = 0, other = 0;
366 int last = -1;
367 for (int k = 0; k < ACQUIRE_SYMBOLS; k++) {
368 int start = lroundf(offset + k * nsamples);
369 float a0 = magnitude(0, start);
370 float a1 = magnitude(ntones - 1, start);
371 int tone = a0 > a1 ? 0 : 1;
372 if (tone == last) {
373 startSearch();
374 return false;
375 }
376 last = tone;
377 amplitude += a0 > a1 ? a0 : a1;
378 other += a0 > a1 ? a1 : a0;
379 }
380 amplitude /= ACQUIRE_SYMBOLS;
381 other /= ACQUIRE_SYMBOLS;
382 if (amplitude < cfg.min_amplitude || other > 0.5f * amplitude) {
383 startSearch();
384 return false;
385 }
386 pos = offset;
387 drift = 0.0f;
388 track_amplitude = amplitude;
389 return true;
390 }
391
395 int best_offset = -1;
396 float best_score = 0;
397 for (int offset = 0; offset < (int)nsamples; offset += acquire_step) {
398 float score = 0;
399 for (int k = 0; k < ACQUIRE_SYMBOLS; k++) {
400 int start = lroundf(offset + k * nsamples);
401 score += fabsf(magnitude(0, start) - magnitude(ntones - 1, start));
402 }
403 if (score > best_score) {
404 best_score = score;
405 best_offset = offset;
406 }
407 }
408 return best_offset;
409 }
410
413 bool estimateClock(int offset) {
414 // the windows with the highest tone have little energy in the lowest
415 int starts[ACQUIRE_SYMBOLS];
416 float low[ACQUIRE_SYMBOLS];
417 float max_low = 0;
418 for (int k = 0; k < ACQUIRE_SYMBOLS; k++) {
419 starts[k] = lroundf(offset + k * nsamples);
420 low[k] = magnitude(0, starts[k]);
421 if (low[k] > max_low) max_low = low[k];
422 }
423 audio_tools::GoertzelDetector det;
424 float high = cfg.toneFrequency(ntones - 1);
425 float current_error = scale - 1.0f;
426 float best_error = current_error, best_mag = -1, current_mag = 0;
427 int steps = lroundf(MAX_CLOCK_ERROR / CLOCK_ERROR_STEP);
428 for (int step = -steps; step <= steps + 1; step++) {
429 // the last step measures the current setting
430 bool is_current = step == steps + 1;
431 float error = is_current ? current_error : step * CLOCK_ERROR_STEP;
432 setupDetector(det, high * (1.0f + error));
433 float sum = 0;
434 for (int k = 0; k < ACQUIRE_SYMBOLS; k++)
435 if (low[k] < 0.5f * max_low) sum += magnitude(det, starts[k]);
436 if (is_current) {
437 current_mag = sum;
438 } else if (sum > best_mag) {
439 best_mag = sum;
440 best_error = error;
441 }
442 }
443 // only retune if this is clearly better than the current setting
444 if (best_mag < MIN_CLOCK_GAIN * current_mag) return false;
445 tune(1.0f + best_error);
446 return true;
447 }
448
450 bool symbolStep() {
451 int p = lroundf(pos);
452 if (p < delta) p = delta; // room for the early gate
453 size_t need = p + delta + window + 1;
454 if (nvalid < need) {
455 needed = need;
456 return false;
457 }
458
459 // find the strongest and the second strongest tone
460 int best = 0;
461 for (int j = 0; j < ntones; j++) {
462 magnitudes[j] = magnitude(j, p);
463 if (magnitudes[j] > magnitudes[best]) best = j;
464 }
465 float second = 0;
466 for (int j = 0; j < ntones; j++)
467 if (j != best && magnitudes[j] > second) second = magnitudes[j];
468 float amplitude = magnitudes[best];
469 bool strong = amplitude >= cfg.min_amplitude &&
470 amplitude >= LOST_LEVEL * track_amplitude;
471
472 // early/late gate: if the symbol starts tau samples later than expected,
473 // the early window contains window - delta - tau and the late window
474 // window - delta + tau samples of the tone, so we can estimate tau. A
475 // second order loop corrects a part of tau and learns the drift, which
476 // results from different clocks of the sender and the receiver.
477 float correction = drift;
478 if (strong) {
479 float early = magnitude(best, p - delta);
480 float late = magnitude(best, p + delta);
481 if (early + late > 0) {
482 float tau = (window - delta) * (late - early) / (late + early);
483 drift += DRIFT_GAIN * tau;
484 float max_drift = MAX_CLOCK_ERROR * nsamples;
485 if (drift > max_drift) drift = max_drift;
486 if (drift < -max_drift) drift = -max_drift;
487 correction = drift + TIMING_GAIN * tau;
488 }
489 }
490 if (correction > delta) correction = delta;
491 if (correction < -delta) correction = -delta;
492 pos += nsamples + correction;
493
494 if (state == State::PREAMBLE)
495 preambleSymbol(best, amplitude, strong && amplitude >= CLEAR_RATIO * second);
496 else
497 dataSymbol(best, amplitude, second, strong, nsamples + correction);
498
499 // keep the samples which are needed for the early gate
500 int keep_from = lroundf(pos) - delta;
501 if (state == State::SEARCH) keep_from = p;
502 if (keep_from > 0) discard(keep_from);
503 return true;
504 }
505
506 void preambleSymbol(int symbol, float amplitude, bool strong) {
507 // a much stronger signal: we were locked to noise, so we search again
508 if (amplitude > NEW_SIGNAL_LEVEL * track_amplitude) {
509 startSearch();
510 return;
511 }
512 // the preamble and sync symbols must be clear (strong and unambiguous)
513 bool ok = strong;
514 if (ok && sync_stage == 0 && (symbol == 0 || symbol == ntones - 1)) {
515 track_amplitude = (track_amplitude + amplitude) / 2;
517 } else if (ok && symbol == cfg.syncSymbol(sync_stage)) {
518 if (++sync_stage == cfg.syncLength()) startOfCarrier();
519 } else {
520 ok = false;
521 }
522 if (!ok) startSearch();
523 }
524
525 void dataSymbol(int symbol, float amplitude, float second, bool strong,
526 float symbol_nsamples) {
527 if (!strong) {
528 // the first weak symbol might be a fade: we decide with the next one
529 if (has_pending) {
530 endOfCarrier();
531 return;
532 }
533 has_pending = true;
534 pending_symbol = symbol;
535 return;
536 }
537 if (has_pending) {
539 has_pending = false;
540 }
541 outputSymbol(symbol);
542 track_amplitude = (track_amplitude + amplitude) / 2;
543 float confidence = second > 0 ? amplitude / second : MAX_CONFIDENCE;
544 confidence_total += confidence < MAX_CONFIDENCE ? confidence : MAX_CONFIDENCE;
545 amplitude_total += amplitude;
546 nsymbols++;
547 data_nsamples += symbol_nsamples;
548 }
549
550 void outputSymbol(int symbol) {
553 while (bit_count >= 8) {
554 uint8_t byte = (bit_buffer >> (bit_count - 8)) & 0xFF;
555 bit_count -= 8;
556 bit_buffer &= (1u << bit_count) - 1;
557 if (p_print != nullptr) p_print->write(byte);
558 ndata++;
559 }
560 }
561
564 has_pending = false;
565 bit_buffer = 0;
566 bit_count = 0;
567 ndata = 0;
568 nsymbols = 0;
570 amplitude_total = 0;
571 data_nsamples = 0;
572 status("### CARRIER mfsk%d %.2f baud @ %.1f Hz ###\n", ntones,
573 (double)cfg.symbol_rate, (double)cfg.base_freq);
574 }
575
577 startSearch();
578 has_pending = false;
579 float n = nsymbols > 0 ? nsymbols : 1;
580 double seconds = data_nsamples / cfg.sample_rate;
581 double bps = seconds > 0 ? nsymbols * bits_per_symbol / seconds : 0;
582 status(
583 "\n### NOCARRIER ndata=%u confidence=%.3f ampl=%.3f bps=%.2f ###\n",
584 ndata, (double)(confidence_total / n), (double)(amplitude_total / n),
585 bps);
586 if (cfg.rx_one) is_finished = true;
587 }
588};
589
590} // namespace tinymodem
int acquire_step
Definition MFSKDecoder.h:177
void startSearch()
Back to the search for the next preamble.
Definition MFSKDecoder.h:230
float pos
Definition MFSKDecoder.h:189
uint32_t noise_seed
Definition MFSKDecoder.h:179
bool step()
One processing step: returns false if more data is needed.
Definition MFSKDecoder.h:294
void flush() override
Processes all buffered samples (e.g. at the end of the input).
Definition MFSKDecoder.h:101
int hop
Definition MFSKDecoder.h:178
void setAudioInfo(audio_tools::AudioInfo info) override
Defines the format of the input audio data.
Definition MFSKDecoder.h:44
float drift
learned timing drift in samples per symbol
Definition MFSKDecoder.h:183
bool isFinished() override
true if the decoding was stopped (rx_one)
Definition MFSKDecoder.h:124
static constexpr float MAX_CONFIDENCE
the confidence of a symbol is limited to this value
Definition MFSKDecoder.h:157
void dataSymbol(int symbol, float amplitude, float second, bool strong, float symbol_nsamples)
Definition MFSKDecoder.h:525
static constexpr int MAX_PREAMBLE
max number of preamble symbols before we give up
Definition MFSKDecoder.h:136
PcmInput input
Definition MFSKDecoder.h:165
float scale
frequency scale which results from the different sample rates
Definition MFSKDecoder.h:181
bool isCarrier() override
true while a transmission is received
Definition MFSKDecoder.h:121
float magnitude(audio_tools::GoertzelDetector &det, int start)
Magnitude (amplitude) of the detector's frequency in the window.
Definition MFSKDecoder.h:278
static constexpr float HIGH_TONE_LEVEL
Definition MFSKDecoder.h:155
static constexpr float MIN_CLOCK_GAIN
a different clock must provide this much more energy
Definition MFSKDecoder.h:141
static constexpr float CLOCK_ERROR_STEP
Definition MFSKDecoder.h:139
MFSKConfig defaultConfig()
Provides a default configuration: mfsk16.
Definition MFSKDecoder.h:37
static constexpr float NEW_SIGNAL_LEVEL
Definition MFSKDecoder.h:149
size_t write(const uint8_t *data, size_t len) override
Provide PCM data to the decoder.
Definition MFSKDecoder.h:114
int window
Definition MFSKDecoder.h:175
unsigned nsymbols
Definition MFSKDecoder.h:199
void end() override
Processes the remaining samples and reports the end of the carrier.
Definition MFSKDecoder.h:94
void resetState()
Definition MFSKDecoder.h:235
State state
Definition MFSKDecoder.h:188
bool symbolStep()
Analyzes the next symbol.
Definition MFSKDecoder.h:450
static constexpr int SEARCH_STEPS
the search checks the clock errors -3% to +3% in steps of 1%
Definition MFSKDecoder.h:159
int bit_count
Definition MFSKDecoder.h:196
bool estimate_clock
true if the clock of the sender is determined from the tones
Definition MFSKDecoder.h:185
audio_tools::Vector< audio_tools::GoertzelDetector > detectors
Definition MFSKDecoder.h:166
void preambleSymbol(int symbol, float amplitude, bool strong)
Definition MFSKDecoder.h:506
State
Definition MFSKDecoder.h:132
@ SEARCH
Definition MFSKDecoder.h:132
@ PREAMBLE
Definition MFSKDecoder.h:132
@ DATA
Definition MFSKDecoder.h:132
bool is_active
Definition MFSKDecoder.h:163
int preamble_count
Definition MFSKDecoder.h:191
static constexpr float SEARCH_STEP
Definition MFSKDecoder.h:160
float magnitude(int tone, int start)
Magnitude (amplitude) of the tone in the window starting at start.
Definition MFSKDecoder.h:273
bool hasHighTone(float clock_error, float low)
Definition MFSKDecoder.h:340
float confidence_total
Definition MFSKDecoder.h:200
bool searchStep()
Searches for the preamble at the start of the buffer.
Definition MFSKDecoder.h:299
void endOfCarrier()
Definition MFSKDecoder.h:576
static constexpr float CLEAR_RATIO
Definition MFSKDecoder.h:152
int ntones
Definition MFSKDecoder.h:172
bool begin() override
Definition MFSKDecoder.h:64
float noise()
uniform random value in the range -0.5 to 0.5
Definition MFSKDecoder.h:245
uint32_t bit_buffer
Definition MFSKDecoder.h:195
bool has_pending
Definition MFSKDecoder.h:193
bool acquire(float coarse_error)
Definition MFSKDecoder.h:353
MFSKConfig & config()
The configuration which was provided in begin().
Definition MFSKDecoder.h:127
MFSKDecoder(Print &out)
Definition MFSKDecoder.h:34
static constexpr float DRIFT_GAIN
Definition MFSKDecoder.h:144
static constexpr float TIMING_GAIN
gains of the timing loop
Definition MFSKDecoder.h:143
float nsamples
Definition MFSKDecoder.h:174
unsigned ndata
Definition MFSKDecoder.h:198
size_t nvalid
Definition MFSKDecoder.h:170
int bits_per_symbol
Definition MFSKDecoder.h:173
bool estimateClock(int offset)
Definition MFSKDecoder.h:413
double data_nsamples
Definition MFSKDecoder.h:202
size_t needed
Definition MFSKDecoder.h:171
float track_amplitude
Definition MFSKDecoder.h:190
int pending_symbol
Definition MFSKDecoder.h:194
void outputSymbol(int symbol)
Definition MFSKDecoder.h:550
static constexpr float MAX_CLOCK_ERROR
max difference of the sample rates of sender and receiver
Definition MFSKDecoder.h:138
audio_tools::Vector< audio_tools::GoertzelDetector > search_detectors
Definition MFSKDecoder.h:167
int delta
Definition MFSKDecoder.h:176
float amplitude_total
Definition MFSKDecoder.h:201
bool begin(MFSKConfig config)
Starts the processing with the indicated configuration.
Definition MFSKDecoder.h:59
void startOfCarrier()
Definition MFSKDecoder.h:562
void setupDetector(audio_tools::GoertzelDetector &det, float freq)
Definition MFSKDecoder.h:220
int sync_stage
Definition MFSKDecoder.h:192
int findTiming()
Definition MFSKDecoder.h:394
static constexpr float LOST_LEVEL
a symbol below this part of the tracked amplitude is lost
Definition MFSKDecoder.h:146
float windowAmplitude(int start)
Amplitude of a sine with the same energy as the window.
Definition MFSKDecoder.h:286
audio_tools::Vector< float > samples
Definition MFSKDecoder.h:169
void addSample(float sample, bool add_noise)
Definition MFSKDecoder.h:250
audio_tools::AudioInfo audioInfo() override
Definition MFSKDecoder.h:54
void tune(float new_scale)
Definition MFSKDecoder.h:207
void discard(size_t n)
Removes the processed samples at the start of the buffer.
Definition MFSKDecoder.h:263
audio_tools::Vector< float > magnitudes
Definition MFSKDecoder.h:168
bool is_finished
Definition MFSKDecoder.h:164
static constexpr int ACQUIRE_SYMBOLS
number of preamble symbols which are used for the timing acquisition
Definition MFSKDecoder.h:134
MFSKConfig cfg
Definition MFSKDecoder.h:162
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
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
unsigned grayDecode(unsigned gray)
Inverse of grayEncode().
Definition MFSKEncoder.h:15
Configuration of the multi-tone FSK modem (MFSKEncoder, MFSKDecoder). Each symbol is one of tones ton...
Definition MFSKConfig.h:23
bool setMode(const char *mode)
Definition MFSKConfig.h:53