TinyModem
Header-only software audio modem library
Loading...
Searching...
No Matches
FSKDecoder.h
Go to the documentation of this file.
1#pragma once
2
3#include <math.h>
4#include <stdarg.h>
5#include <stdio.h>
6#include <string.h>
7
8#include "AudioTools.h"
9#include "DataCodecs.h"
10#include "FSKDemodulator.h"
11#include "ModemBase.h"
12#include "ModemConfig.h"
13
14namespace tinymodem {
15
27class FSKDecoder : public ModemDecoder {
28 public:
29 FSKDecoder() = default;
30 FSKDecoder(Print& out) { setOutput(out); }
31
34 ModemConfig result;
35 result.setMode("1200");
36 return result;
37 }
38
40 void setAudioInfo(audio_tools::AudioInfo info) override {
41 if (info.sample_rate == cfg.sample_rate && info.channels == cfg.channels &&
42 info.bits_per_sample == cfg.bits_per_sample)
43 return;
44 cfg.sample_rate = info.sample_rate;
45 cfg.channels = info.channels;
46 cfg.bits_per_sample = info.bits_per_sample;
47 if (is_active) begin();
48 }
49
50 audio_tools::AudioInfo audioInfo() override { return cfg; }
51
52 using audio_tools::AudioDecoder::begin;
53
56 cfg = config;
57 return begin();
58 }
59
60 bool begin() override {
61 is_active = false;
64 if (!c.resolve()) {
65 LOGE("FSKDecoder: invalid configuration (max 64 bits per frame)");
66 return false;
67 }
68 if (!input.begin(c.bits_per_sample, c.channels, c.channel)) return false;
69 if (!plan.begin(c.sample_rate, c.mark_freq, c.space_freq, c.bandwidth)) {
70 LOGE("FSKDecoder: frequencies not supported with sample rate %d",
71 (int)c.sample_rate);
72 return false;
73 }
74 setupCodec();
75
76 nsamples_per_bit = c.sample_rate / c.baud_rate;
77
78 // For 8-bit frames with prev/start/stop we need 11 data-bits worth of
79 // samples, and we will scan through one bits worth at a time, hence we
80 // need a minimum total input buffer size of 12 data-bits.
81 unsigned nbits = 1 + c.start_bits + c.data_bits + 1;
82 size_t samplebuf_size = ceilf(nsamples_per_bit) * (nbits + 1);
83 samplebuf_size *= 2; // account for the half-buf filling method
84 if (c.buffer_divisor > 0 &&
85 samplebuf_size < c.sample_rate / (unsigned)c.buffer_divisor)
86 samplebuf_size = c.sample_rate / c.buffer_divisor;
87
88 // ensure that we overscan at least a single sample
91
94
95 if (c.expect_bits != nullptr) {
99 } else {
101 }
102 if (c.rx_sync && c.sync_byte >= 0) {
104 } else {
106 }
108
109 // we process the data when we have enough samples for the frame search
110 unsigned try_max_nsamples = nsamples_per_bit + nsamples_overscan;
111 size_t needed = try_max_nsamples + expect_nsamples +
112 ceilf(nsamples_per_bit) + 1;
113 process_threshold = samplebuf_size / 2;
114 if (process_threshold < needed) process_threshold = needed;
115 if (samplebuf_size < process_threshold) samplebuf_size = process_threshold;
116 samplebuf.resize(samplebuf_size);
117
118 resetState();
119 is_active = true;
120 return true;
121 }
122
124 void end() override {
125 if (!is_active) return;
126 flush();
127 is_active = false;
128 }
129
131 void flush() override {
132 if (!is_active) return;
133 if (!is_finished) {
134 while (step(true)) {
135 }
137 }
138 // with rx_one the decoding stays stopped until begin()
139 bool finished = is_finished;
140 resetState();
141 is_finished = finished;
142 }
143
145 size_t write(const uint8_t* data, size_t len) override {
146 if (!is_active) return 0;
147 input.write(data, len, [this](float sample) { addSample(sample); });
148 return len;
149 }
150
152 bool isCarrier() override { return carrier; }
153
155 bool isFinished() override { return is_finished; }
156
159 ModemConfig& config() { return cfg; }
160
162 const ModemConfig& activeConfig() const { return active_cfg; }
163
165 float markFrequency() { return plan.markBand() * plan.bandWidth(); }
166
167 operator bool() override { return is_active; }
168
169 protected:
170 static constexpr int MAX_EXPECT = 80;
171 static constexpr int FSK_ANALYZE_NSTEPS = 3;
172 static constexpr int FSK_ANALYZE_NSTEPS_FINE = 8;
173 static constexpr unsigned FSK_MAX_NOCONFIDENCE_BITS = 20;
174 // Fraction of nsamples_per_bit that we will "overscan"; range (0.0 .. 1.0)
175 static constexpr float FRAME_OVERSCAN = 0.5f;
176
179 bool is_active = false;
180 bool is_finished = false;
189
190 audio_tools::Vector<float> samplebuf;
191 size_t samples_nvalid = 0;
193 size_t advance = 0;
194 size_t skip = 0;
196 unsigned nsamples_overscan = 0;
197 float frame_n_bits = 0;
198 unsigned frame_nsamples = 0;
201 unsigned expect_n_bits = 0;
202 unsigned expect_nsamples = 0;
203
205 uint32_t noise_seed = 1;
206
207 // receiver state
208 bool carrier = false;
209 int carrier_band = -1;
212 unsigned nframes_decoded = 0;
214 unsigned noconfidence = 0;
215 float track_amplitude = 0.0f;
216 float peak_confidence = 0.0f;
217
218 void setupCodec() {
219 switch (active_cfg.format) {
221 baudot.setUSOS(active_cfg.baudot_usos);
222 p_codec = &baudot;
223 break;
225 p_codec = &binary;
226 break;
228 p_codec = &callerid;
229 break;
231 uic.setTrainToGround(true);
232 p_codec = &uic;
233 break;
235 uic.setTrainToGround(false);
236 p_codec = &uic;
237 break;
238 default:
239 p_codec = &ascii;
240 break;
241 }
242 }
243
244 void resetState() {
245 samples_nvalid = 0;
246 advance = 0;
247 skip = 0;
248 input.reset();
249 carrier = false;
250 carrier_band = -1;
252 amplitude_total = 0;
253 nframes_decoded = 0;
255 noconfidence = 0;
256 track_amplitude = 0.0f;
257 peak_confidence = 0.0f;
258 is_finished = false;
259 }
260
263 int buildExpectBits(char* str, bool use_expect_bits, uint64_t expect_bits) {
265 char start_bit_value = c.invert_start_stop ? '1' : '0';
266 char stop_bit_value = c.invert_start_stop ? '0' : '1';
267 int j = 0;
268 if (c.stop_bits != 0.0f) str[j++] = stop_bit_value;
269 for (int i = 0; i < c.start_bits; i++) str[j++] = start_bit_value;
270 for (int i = 0; i < c.data_bits; i++, j++) {
271 str[j] = use_expect_bits ? ((expect_bits >> i) & 1) + '0' : 'd';
272 }
273 if (c.stop_bits != 0.0f) str[j++] = stop_bit_value;
274 str[j] = 0;
275 return j;
276 }
277
279 float noise() {
280 noise_seed = noise_seed * 1103515245u + 12345u;
281 return ((noise_seed >> 8) & 0xFFFF) / 65535.0f - 0.5f;
282 }
283
284 void addSample(float sample) {
285 if (is_finished) return;
286 if (skip > 0) {
287 skip--;
288 return;
289 }
290 if (active_cfg.rx_noise != 0.0f)
291 sample += noise() * active_cfg.rx_noise * 2.0f;
292 samplebuf[samples_nvalid++] = sample;
294 while (step(false)) {
295 }
296 }
297 }
298
300 if (advance == 0) return;
301 if (advance >= samples_nvalid) {
303 samples_nvalid = 0;
304 } else {
305 memmove(samplebuf.data(), samplebuf.data() + advance,
306 (samples_nvalid - advance) * sizeof(float));
308 }
309 advance = 0;
310 }
311
314 bool step(bool eof) {
316 applyAdvance();
317 if (eof && skip > 0) return false;
318 if (!eof && samples_nvalid < process_threshold) return false;
319 if (samples_nvalid == 0) return false;
320 float* samples = samplebuf.data();
321 if (eof) {
322 // pad the incomplete data with silence
323 for (size_t j = samples_nvalid; j < samplebuf.size(); j++)
324 samples[j] = 0.0f;
325 }
326
327 // Auto-detect carrier frequency
328 if (c.auto_carrier && carrier_band < 0) {
329 unsigned i;
330 float nsamples_per_scan = nsamples_per_bit;
331 if (nsamples_per_scan > plan.fftSize()) nsamples_per_scan = plan.fftSize();
332 for (i = 0; i + nsamples_per_scan <= samples_nvalid;
333 i += nsamples_per_scan) {
334 carrier_band = plan.detectCarrier(samples + i, nsamples_per_scan,
336 if (carrier_band >= 0) break;
337 }
338 advance = i + nsamples_per_scan;
340 if (carrier_band < 0) return true;
341
342 // default negative shift
343 float bw = plan.bandWidth();
344 int b_shift = -(float)(c.autodetect_shift + bw / 2.0f) / bw;
345 if (c.inverted) b_shift *= -1;
346 // only accept a carrier as b_mark if it will not result in a b_space
347 // band which is "too low".
348 int b_space = carrier_band + b_shift;
349 if (b_space < 1 || b_space >= (int)plan.bandCount()) {
350 carrier_band = -1;
351 return true;
352 }
353 plan.setTonesByBandShift(carrier_band, b_shift);
354 }
355
356 if (samples_nvalid < expect_nsamples) return false;
357
358 // try_max_nsamples
359 // 1. avoids finding a non-optimal first frame
360 // 2. allows us to track slightly slow signals
361 unsigned try_max_nsamples = carrier ? nsamples_per_bit * 0.75f + 0.5f
363 try_max_nsamples += nsamples_overscan;
364
365 unsigned try_step_nsamples = try_max_nsamples / FSK_ANALYZE_NSTEPS;
366 if (try_step_nsamples == 0) try_step_nsamples = 1;
367
368 float confidence, amplitude;
369 uint64_t bits = 0;
370 // frame_start_sample is the sample where the prev_stop bit begins
371 unsigned frame_start_sample = 0;
372 unsigned try_first_sample = carrier ? nsamples_overscan : 0;
373 float try_confidence_search_limit = c.search_limit;
374
375 confidence = plan.findFrame(
376 samples, expect_nsamples, try_first_sample, try_max_nsamples,
377 try_step_nsamples, try_confidence_search_limit,
378 carrier ? expect_data_string : expect_sync_string, &bits, &amplitude,
379 &frame_start_sample);
380
381 bool do_refine_frame = false;
382 if (confidence < peak_confidence * 0.75f) {
383 do_refine_frame = true;
384 peak_confidence = 0;
385 }
386
387 // no-confidence if amplitude drops abruptly to < 25% of the
388 // track_amplitude, which follows amplitude with hysteresis
389 if (amplitude < track_amplitude * 0.25f) confidence = 0;
390
391 if (confidence <= c.confidence) {
393 carrier_band = -1;
394 if (carrier) {
396 carrier = false;
399 amplitude_total = 0;
400 nframes_decoded = 0;
401 track_amplitude = 0.0f;
402 if (c.rx_one) {
403 is_finished = true;
404 return false;
405 }
406 }
407 }
408 // continue searching from where we left off this time
409 advance = try_max_nsamples;
410 return true;
411 }
412
413 // Add a frame's worth of samples to the sample count
415
416 if (carrier) {
417 // If we already had carrier, adjust sample count +start -overscan
418 carrier_nsamples += frame_start_sample;
420 } else {
421 // We just acquired carrier.
423 carrier = true;
424 p_codec->reset();
425 do_refine_frame = true;
426 }
427
428 if (do_refine_frame && confidence < INFINITY && try_step_nsamples > 1) {
429 // Scan again, but try harder to find the best frame.
430 try_step_nsamples = try_max_nsamples / FSK_ANALYZE_NSTEPS_FINE;
431 if (try_step_nsamples == 0) try_step_nsamples = 1;
432 float confidence2, amplitude2;
433 uint64_t bits2;
434 unsigned frame_start_sample2;
435 confidence2 = plan.findFrame(
436 samples, expect_nsamples, try_first_sample, try_max_nsamples,
437 try_step_nsamples, INFINITY,
439 &amplitude2, &frame_start_sample2);
440 if (confidence2 > confidence) {
441 bits = bits2;
442 amplitude = amplitude2;
443 frame_start_sample = frame_start_sample2;
444 }
445 }
446
447 track_amplitude = (track_amplitude + amplitude) / 2;
448 if (peak_confidence < confidence) peak_confidence = confidence;
449 confidence_total += confidence;
450 amplitude_total += amplitude;
452 noconfidence = 0;
453
454 // Advance past the decoded frame, but a bit less than that to allow for
455 // tracking slightly fast signals, hence - nsamples_overscan.
456 advance = frame_start_sample + frame_nsamples - nsamples_overscan;
457
458 // chop off the prev_stop bit
459 if (c.stop_bits != 0.0f) bits = bits >> 1;
460 // chop off framing bits
461 bits = bitWindow(bits, c.start_bits, c.data_bits);
462 if (c.msb_first) bits = bitReverse(bits, c.data_bits);
463
464 // suppress printing of sync bytes
465 if (c.rx_sync && (long long)bits == c.sync_byte) return true;
466
467 if (p_print != nullptr) {
468 if (c.print_filter) {
469 print_filter.setOutput(p_print);
470 p_codec->decode(print_filter, bits, c.data_bits);
471 } else {
472 p_codec->decode(*p_print, bits, c.data_bits);
473 }
474 }
475 return true;
476 }
477
479 float rate = active_cfg.baud_rate;
480 if (rate >= 100)
481 status("### CARRIER %u @ %.1f Hz ###\n", (unsigned)(rate + 0.5f),
482 (double)markFrequency());
483 else
484 status("### CARRIER %.2f @ %.1f Hz ###\n", (double)rate,
485 (double)markFrequency());
486 }
487
489 float sample_rate = active_cfg.sample_rate;
490 float rate = active_cfg.baud_rate;
491 float nbits_decoded = nframes_decoded * frame_n_bits;
492 float throughput_rate =
493 nbits_decoded * sample_rate / (float)carrier_nsamples;
494 status("\n### NOCARRIER ndata=%u confidence=%.3f ampl=%.3f bps=%.2f",
497 (double)throughput_rate);
498 if ((unsigned long long)(nbits_decoded * sample_rate + 0.5f) ==
499 (unsigned long long)(rate * carrier_nsamples)) {
500 status(" (rate perfect) ###\n");
501 } else {
502 float skew = (throughput_rate - rate) / rate;
503 status(" (%.1f%% %s) ###\n", (double)(fabsf(skew) * 100.0f),
504 signbit(skew) ? "slow" : "fast");
505 }
506 }
507};
508
509} // namespace tinymodem
ASCII 8-bit data (passthrough).
Definition DataCodecs.h:47
Baudot (ITA2) 5-bit data.
Definition DataCodecs.h:65
Raw N-bit binary data: prints the bits as '0' and '1' followed by newline.
Definition DataCodecs.h:55
Caller-ID (USA SDMF/MDMF) decoder. See http://melabs.com/resources/callerid.htm.
Definition DataCodecs.h:171
Converts between characters and the data bits of a frame.
Definition DataCodecs.h:32
void setupCodec()
Definition FSKDecoder.h:218
unsigned nsamples_overscan
Definition FSKDecoder.h:196
size_t carrier_nsamples
Definition FSKDecoder.h:213
AsciiCodec ascii
Definition FSKDecoder.h:182
size_t write(const uint8_t *data, size_t len) override
Provide PCM data to the decoder.
Definition FSKDecoder.h:145
unsigned expect_n_bits
Definition FSKDecoder.h:201
static constexpr float FRAME_OVERSCAN
Definition FSKDecoder.h:175
ModemConfig active_cfg
Definition FSKDecoder.h:178
CallerIDCodec callerid
Definition FSKDecoder.h:185
float amplitude_total
Definition FSKDecoder.h:211
float peak_confidence
Definition FSKDecoder.h:216
PcmInput input
Definition FSKDecoder.h:204
void end() override
Processes the remaining samples and reports the end of the carrier.
Definition FSKDecoder.h:124
ModemConfig & config()
Definition FSKDecoder.h:159
audio_tools::Vector< float > samplebuf
Definition FSKDecoder.h:190
uint32_t noise_seed
Definition FSKDecoder.h:205
int buildExpectBits(char *str, bool use_expect_bits, uint64_t expect_bits)
Definition FSKDecoder.h:263
float markFrequency()
Actual mark frequency (e.g. after the carrier auto detection).
Definition FSKDecoder.h:165
static constexpr int FSK_ANALYZE_NSTEPS
Definition FSKDecoder.h:171
int carrier_band
Definition FSKDecoder.h:209
void addSample(float sample)
Definition FSKDecoder.h:284
bool step(bool eof)
Definition FSKDecoder.h:314
float noise()
uniform random value in the range -0.5 to 0.5
Definition FSKDecoder.h:279
float nsamples_per_bit
Definition FSKDecoder.h:195
unsigned expect_nsamples
Definition FSKDecoder.h:202
bool isFinished() override
true if the decoding was stopped (rx_one)
Definition FSKDecoder.h:155
void setAudioInfo(audio_tools::AudioInfo info) override
Defines the format of the input audio data.
Definition FSKDecoder.h:40
unsigned noconfidence
Definition FSKDecoder.h:214
static constexpr unsigned FSK_MAX_NOCONFIDENCE_BITS
Definition FSKDecoder.h:173
size_t skip
Definition FSKDecoder.h:194
void reportCarrier()
Definition FSKDecoder.h:478
float track_amplitude
Definition FSKDecoder.h:215
unsigned frame_nsamples
Definition FSKDecoder.h:198
FSKDemodulator plan
Definition FSKDecoder.h:181
BaudotCodec baudot
Definition FSKDecoder.h:183
bool is_active
Definition FSKDecoder.h:179
char expect_sync_string[MAX_EXPECT]
Definition FSKDecoder.h:200
ModemConfig defaultConfig()
Provides a default configuration for a 1200 baud Bell 202 modem.
Definition FSKDecoder.h:33
DataCodec * p_codec
Definition FSKDecoder.h:187
UICCodec uic
Definition FSKDecoder.h:186
const ModemConfig & activeConfig() const
The active configuration with all derived values (e.g. frequencies).
Definition FSKDecoder.h:162
float frame_n_bits
Definition FSKDecoder.h:197
void flush() override
Processes all buffered samples (e.g. at the end of the input).
Definition FSKDecoder.h:131
static constexpr int MAX_EXPECT
Definition FSKDecoder.h:170
unsigned nframes_decoded
Definition FSKDecoder.h:212
bool carrier
Definition FSKDecoder.h:208
char expect_data_string[MAX_EXPECT]
Definition FSKDecoder.h:199
void reportNoCarrier()
Definition FSKDecoder.h:488
PrintFilter print_filter
Definition FSKDecoder.h:188
bool begin(ModemConfig config)
Starts the processing with the indicated modem configuration.
Definition FSKDecoder.h:55
FSKDecoder(Print &out)
Definition FSKDecoder.h:30
static constexpr int FSK_ANALYZE_NSTEPS_FINE
Definition FSKDecoder.h:172
bool isCarrier() override
true while a carrier is detected
Definition FSKDecoder.h:152
size_t advance
Definition FSKDecoder.h:193
void applyAdvance()
Definition FSKDecoder.h:299
bool is_finished
Definition FSKDecoder.h:180
float confidence_total
Definition FSKDecoder.h:210
size_t process_threshold
Definition FSKDecoder.h:192
size_t samples_nvalid
Definition FSKDecoder.h:191
void resetState()
Definition FSKDecoder.h:244
bool begin() override
Definition FSKDecoder.h:60
BinaryCodec binary
Definition FSKDecoder.h:184
audio_tools::AudioInfo audioInfo() override
Definition FSKDecoder.h:50
ModemConfig cfg
Definition FSKDecoder.h:177
FSK bit and frame analysis (port of minimodem's fsk.c). Instead of a full FFT we use the Goertzel alg...
Definition FSKDemodulator.h:19
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
Print which replaces non printable characters with '.'.
Definition DataCodecs.h:361
UIC-751-3 ground-train or train-ground decoder.
Definition DataCodecs.h:309
TinyModem: software audio modems and packets with error correction.
Definition AX25.h:11
uint64_t bitReverse(uint64_t value, unsigned bits)
Reverses the ordering of the bits on an integer.
Definition DataCodecs.h:13
@ UIC_GROUND
UIC-751-3 ground to train (receive only).
Definition ModemConfig.h:19
@ CALLERID
Bell 202 Caller-ID (SDMF/MDMF, receive only).
Definition ModemConfig.h:17
@ UIC_TRAIN
UIC-751-3 train to ground (receive only).
Definition ModemConfig.h:18
@ BINARY
print the raw data bits as '0'/'1' (receive only)
Definition ModemConfig.h:16
@ BAUDOT
Baudot 5 bit (RTTY, TDD).
Definition ModemConfig.h:15
uint64_t bitWindow(uint64_t value, unsigned offset, unsigned bits)
Gets "bits" bits from "value" starting "offset" bits from the start.
Definition DataCodecs.h:23
Configuration of the FSK modem. The fields correspond to the command line options of minimodem....
Definition ModemConfig.h:32
bool rx_one
receiver: stop after the first carrier was lost
Definition ModemConfig.h:80
bool auto_carrier
automatically detect the carrier frequency
Definition ModemConfig.h:70
float auto_carrier_threshold
minimum magnitude for the carrier auto detection
Definition ModemConfig.h:72
bool print_filter
receiver: replace non printable characters with '.'
Definition ModemConfig.h:88
int start_bits
number of start bits (-1 = auto)
Definition ModemConfig.h:50
float search_limit
stop searching for a better frame when this confidence was reached
Definition ModemConfig.h:68
float stop_bits
number of stop bits (-1 = auto)
Definition ModemConfig.h:52
bool rx_sync
receiver: synchronize on sync_byte
Definition ModemConfig.h:60
bool setMode(const char *mode, bool keep_options=false)
Definition ModemConfig.h:109
long long sync_byte
sync byte value (-1 = none)
Definition ModemConfig.h:64
float space_freq
space frequency in Hz (0 = auto)
Definition ModemConfig.h:44
bool invert_start_stop
invert the start and stop bits
Definition ModemConfig.h:54
uint8_t channel
receiver: input channel which is used for the decoding
Definition ModemConfig.h:94
const char * expect_bits
custom expected frame pattern ('0','1','d')
Definition ModemConfig.h:92
float confidence
minimum confidence threshold (signal-to-noise squelch)
Definition ModemConfig.h:66
int autodetect_shift
derived: shift which is used for the carrier auto detection
Definition ModemConfig.h:101
bool inverted
swap the mark and space frequencies
Definition ModemConfig.h:56
unsigned frameBits() const
number of bits of a frame: start + data + stop bits
Definition ModemConfig.h:230
int data_bits
number of data bits per frame (0 = auto)
Definition ModemConfig.h:48
float baud_rate
baud rate in bits per second (default: 1200 baud Bell 202)
Definition ModemConfig.h:40
float mark_freq
mark frequency in Hz (0 = auto)
Definition ModemConfig.h:42
bool msb_first
transmit the most significant data bit first
Definition ModemConfig.h:58
int buffer_divisor
Definition ModemConfig.h:99
bool resolve()
Definition ModemConfig.h:171
float bandwidth
receive filter bandwidth in Hz (0 = auto)
Definition ModemConfig.h:46