TinyModem
Header-only software audio modem library
Loading...
Searching...
No Matches
OliviaDecoder.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 "Olivia.h"
9
10namespace tinymodem {
11
34 public:
35 OliviaDecoder() = default;
36 OliviaDecoder(Print& out) { setOutput(out); }
37
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 converter.begin(cfg.sample_rate, OliviaConfig::BASE_RATE);
68 bits = cfg.bitsPerSymbol();
69 tones = 1 << bits;
70 codec.begin(bits, cfg.contestia);
71 block_len = codec.blockLen();
72 symbol_len = cfg.symbolLen();
73 // the search range is limited by the audio band
74 margin = cfg.sync_margin;
75 int first = cfg.firstCarrier();
76 if (margin > first - 1) margin = first - 1;
77 int upper = symbol_len / 2 - 1 - (first + 2 * tones);
78 if (margin > upper) margin = upper;
79 if (margin < 0) margin = 0;
80 offsets = 2 * margin + 1;
82 first_bin = cfg.firstCarrier() - margin;
83 width = 2 * tones - 1 + 2 * margin;
84 if (!fft.begin(symbol_len)) return false;
85 samples.resize(symbol_len);
86 window.resize(symbol_len);
87 for (int j = 0; j < symbol_len; j++)
88 window[j] = (1.0f - cosf(2.0f * (float)M_PI * j / symbol_len)) / symbol_len;
89 fft_re.resize(symbol_len);
90 fft_im.resize(symbol_len);
91 energy.resize(width);
92 soft.resize(SLICES * offsets * block_len * bits);
93 sync_signal.resize(phases * offsets);
94 sync_noise.resize(phases * offsets);
95 blocks.resize(phases);
96 block_snr.resize(phases);
97 resetState();
98 is_active = true;
99 return true;
100 }
101
103 void end() override {
104 if (!is_active) return;
105 flush();
106 is_active = false;
107 }
108
110 void flush() override {
111 if (!is_active) return;
112 // feed silence, so that the last block is provided
113 int n = (block_len + 4) * symbol_len / 2 + 32;
114 for (int j = 0; j < n && !is_finished; j++) addSample8k(0.0f);
115 if (carrier) endOfCarrier();
116 bool finished = is_finished;
117 resetState();
118 is_finished = finished;
119 }
120
122 size_t write(const uint8_t* data, size_t len) override {
123 if (!is_active) return 0;
124 input.write(data, len, [this](float x) {
125 if (cfg.rx_noise != 0.0f) x += noise() * cfg.rx_noise * 2.0f;
126 converter.write(x, [this](float y) { addSample8k(y); });
127 });
128 return len;
129 }
130
132 bool isCarrier() override { return carrier; }
133
135 bool isFinished() override { return is_finished; }
136
138 float signalToNoise() { return snr; }
139
144
146 OliviaConfig& config() { return cfg; }
147
148 operator bool() override { return is_active; }
149
150 protected:
151 static constexpr int SLICES = 2; // FFTs per symbol
153 static constexpr float BLOCK_THRESHOLD = 1.0f;
155 bool is_active = false;
156 bool is_finished = false;
161 uint32_t noise_seed = 1;
162 int bits = 5, tones = 32, block_len = 64, symbol_len = 512;
163 int margin = 8, offsets = 17, phases = 128, first_bin = 0, width = 0;
164
165 // input samples (8000 Hz) and spectrum
166 audio_tools::Vector<float> samples, window, fft_re, fft_im, energy;
167 int sample_pos = 0;
169 int slice = 0;
170 // soft bits of the last block per slice and offset (circular by symbol)
171 audio_tools::Vector<int8_t> soft;
172 int soft_pos = 0; // symbol which is overwritten next
173 // synchronizer per block phase and offset
174 audio_tools::Vector<float> sync_signal, sync_noise;
175 audio_tools::Vector<uint64_t> blocks; // decoded block per phase
176 audio_tools::Vector<float> block_snr; // its signal to noise ratio
177 int phase = 0;
178 float best_signal = 0, best_noise = 0;
180 float snr = 0;
181 // output
182 bool carrier = false;
183 bool escape = false;
184 unsigned ndata = 0;
185 unsigned nblocks = 0;
186 float snr_total = 0;
187
188 void resetState() {
189 for (int j = 0; j < symbol_len; j++) samples[j] = 0;
190 for (size_t j = 0; j < soft.size(); j++) soft[j] = 0;
191 resetSync();
192 sample_pos = 0;
194 slice = 0;
195 soft_pos = 0;
196 phase = 0;
197 snr = 0;
198 carrier = false;
199 escape = false;
200 is_finished = false;
201 converter.reset();
202 input.reset();
203 }
204
206 void resetSync() {
207 for (size_t j = 0; j < sync_signal.size(); j++)
208 sync_signal[j] = sync_noise[j] = 0;
209 for (int j = 0; j < phases; j++) {
210 blocks[j] = 0;
211 block_snr[j] = 0;
212 }
215 }
216
218 float noise() {
219 noise_seed = noise_seed * 1103515245u + 12345u;
220 return ((noise_seed >> 8) & 0xFFFF) / 65535.0f - 0.5f;
221 }
222
223 void addSample8k(float x) {
224 if (is_finished) return;
225 samples[sample_pos] = x;
226 if (++sample_pos == symbol_len) sample_pos = 0;
227 if (--slice_countdown == 0) {
229 processSlice();
230 }
231 }
232
235 for (int j = 0; j < symbol_len; j++) {
236 int idx = sample_pos + j;
237 if (idx >= symbol_len) idx -= symbol_len;
238 fft_re[j] = samples[idx] * window[j];
239 fft_im[j] = 0;
240 }
241 fft.process(fft_re.data(), fft_im.data());
242 for (int j = 0; j < width; j++) {
243 int bin = first_bin + j;
244 energy[j] = fft_re[bin] * fft_re[bin] + fft_im[bin] * fft_im[bin];
245 }
246
247 int8_t block_bits[64 * 8];
248 float slice_best = 0;
249 int slice_best_offset = 0;
250 uint64_t slice_block = 0;
251 float slice_noise = 0, slice_snr = 0;
252 for (int o = 0; o < offsets; o++) {
253 int8_t* buffer = soft.data() + (slice * offsets + o) * block_len * bits;
254 softDecode(energy.data() + o, buffer + soft_pos * bits);
255 // decode the block which ends with this symbol
256 linearize(buffer, block_bits);
257 float signal = 0, noise_energy = 0;
258 uint64_t block = 0;
259 for (int b = 0; b < bits; b++) {
260 float peak, noise;
261 uint8_t ch = codec.decodeChar(block_bits, b, peak, noise);
262 block |= (uint64_t)ch << (8 * b);
263 signal += peak;
264 noise_energy += noise;
265 }
266 signal /= bits;
267 noise_energy /= bits;
268 float weight = 1.0f / cfg.sync_integration;
269 float& s = sync_signal[phase * offsets + o];
270 float& n = sync_noise[phase * offsets + o];
271 s += weight * (signal - s);
272 n += weight * (noise_energy - n);
273 if (s > slice_best) {
274 slice_best = s;
275 slice_best_offset = o;
276 slice_block = block;
277 slice_noise = n;
278 slice_snr = signal / sqrtf(noise_energy > 0 ? noise_energy : 1e-9f);
279 }
280 }
281 // steady tones (e.g. the start and stop tones) are not a valid block
282 int8_t* best_buffer =
283 soft.data() + (slice * offsets + slice_best_offset) * block_len * bits;
284 linearize(best_buffer, block_bits);
285 if (repeatedSymbols(block_bits) > block_len / tones + block_len / 4)
286 slice_snr = 0;
287 blocks[phase] = slice_block;
288 block_snr[phase] = slice_snr;
289 if (phase == best_phase || slice_best > best_signal) {
290 best_signal = slice_best;
292 best_offset = slice_best_offset;
293 best_noise = slice_noise;
294 }
295 // a block is provided half a block after the best timing
296 int dist = phase - best_phase;
297 if (dist < 0) dist += phases;
298 if (dist == phases / 2) evaluateBlock();
299
300 if (++phase == phases) phase = 0;
301 if (++slice == SLICES) {
302 slice = 0;
303 if (++soft_pos == block_len) soft_pos = 0;
304 }
305 }
306
308 void softDecode(const float* tone_energy, int8_t* out) {
309 float sum[8] = {0};
310 float total = 0;
311 for (int k = 0; k < tones; k++) {
312 uint8_t symbol = OliviaCodec::binaryCode(k);
313 float e = tone_energy[2 * k];
314 e *= e;
315 total += e;
316 for (int b = 0; b < bits; b++) sum[b] += (symbol >> b) & 1 ? -e : e;
317 }
318 for (int b = 0; b < bits; b++)
319 out[b] = total > 0 ? (int8_t)lroundf(127.0f * sum[b] / total) : 0;
320 }
321
323 int repeatedSymbols(const int8_t* block) {
324 int result = 0;
325 int prev = -1;
326 for (int t = 0; t < block_len; t++) {
327 int symbol = 0;
328 for (int b = 0; b < bits; b++)
329 if (block[t * bits + b] < 0) symbol |= 1 << b;
330 if (symbol == prev) result++;
331 prev = symbol;
332 }
333 return result;
334 }
335
337 void linearize(const int8_t* buffer, int8_t* block) {
338 int start = soft_pos + 1; // oldest symbol
339 if (start == block_len) start = 0;
340 int n1 = (block_len - start) * bits;
341 memcpy(block, buffer + start * bits, n1);
342 memcpy(block + n1, buffer, start * bits);
343 }
344
347 snr = best_noise > 0 ? best_signal / sqrtf(best_noise)
348 : (best_signal > 0 ? 100.0f : 0.0f);
349 // the integrated timing lags behind a drifting clock: we use the best
350 // block of the neighboring time slices
351 int use = best_phase;
352 for (int d = -1; d <= 1; d += 2) {
353 int p = (best_phase + d + phases) % phases;
354 if (block_snr[p] > block_snr[use]) use = p;
355 }
356 // the block itself must be valid: avoids garbage at the end of a
357 // transmission, while the integrated signal is still strong
358 if (snr < cfg.sync_threshold ||
359 block_snr[use] < BLOCK_THRESHOLD * cfg.sync_threshold) {
360 if (carrier) endOfCarrier();
361 return;
362 }
363 if (!carrier) startOfCarrier();
364 nblocks++;
365 snr_total += snr;
366 uint64_t block = blocks[use];
367 for (int b = 0; b < bits; b++) outputChar((block >> (8 * b)) & 0xFF);
368 }
369
370 void outputChar(uint8_t ch) {
371 if (!cfg.contestia) {
372 if (escape) {
373 escape = false;
374 ch += 128;
375 } else if (ch == 127) {
376 escape = true;
377 return;
378 }
379 }
380 if (ch == 0) return; // idle
381 ndata++;
382 if (p_print != nullptr) p_print->write(ch);
383 }
384
386 carrier = true;
387 ndata = 0;
388 nblocks = 0;
389 snr_total = 0;
390 escape = false;
391 status("### CARRIER %s %d/%d @ %.1f Hz ###\n",
392 cfg.contestia ? "contestia" : "olivia", cfg.tones, cfg.bandwidth,
393 (double)(cfg.center_freq + frequencyOffset()));
394 }
395
397 carrier = false;
398 float n = nblocks > 0 ? nblocks : 1;
399 status("\n### NOCARRIER ndata=%u blocks=%u snr=%.1f freq=%.1f Hz ###\n",
400 ndata, nblocks, (double)(snr_total / n),
401 (double)(cfg.center_freq + frequencyOffset()));
402 resetSync();
403 if (cfg.rx_one) is_finished = true;
404 }
405};
406
407} // 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
The forward error correction of Olivia and Contestia (as in fldigi): each character of a block of bit...
Definition Olivia.h:213
static uint8_t binaryCode(uint8_t gray)
Definition Olivia.h:289
audio_tools::Vector< float > block_snr
Definition OliviaDecoder.h:176
audio_tools::AudioInfo audioInfo() override
Definition OliviaDecoder.h:52
void linearize(const int8_t *buffer, int8_t *block)
Copies the circular soft bit buffer: the oldest symbol first.
Definition OliviaDecoder.h:337
int slice
Definition OliviaDecoder.h:169
audio_tools::Vector< float > sync_signal
Definition OliviaDecoder.h:174
float snr_total
Definition OliviaDecoder.h:186
bool begin(OliviaConfig config)
Starts the processing with the indicated configuration.
Definition OliviaDecoder.h:57
void resetState()
Definition OliviaDecoder.h:188
audio_tools::Vector< float > energy
Definition OliviaDecoder.h:166
audio_tools::Vector< float > fft_im
Definition OliviaDecoder.h:166
audio_tools::Vector< uint64_t > blocks
Definition OliviaDecoder.h:175
void startOfCarrier()
Definition OliviaDecoder.h:385
void flush() override
Processes the remaining samples (e.g. at the end of the input).
Definition OliviaDecoder.h:110
OliviaConfig cfg
Definition OliviaDecoder.h:154
int phases
Definition OliviaDecoder.h:163
uint32_t noise_seed
Definition OliviaDecoder.h:161
SimpleFFT fft
Definition OliviaDecoder.h:160
int sample_pos
Definition OliviaDecoder.h:167
int best_offset
Definition OliviaDecoder.h:179
int soft_pos
Definition OliviaDecoder.h:172
int repeatedSymbols(const int8_t *block)
Number of symbols which are equal to the previous symbol (hard decision).
Definition OliviaDecoder.h:323
bool is_finished
Definition OliviaDecoder.h:156
audio_tools::Vector< float > sync_noise
Definition OliviaDecoder.h:174
int first_bin
Definition OliviaDecoder.h:163
void softDecode(const float *tone_energy, int8_t *out)
Soft bits of a symbol from the tone energies (like fldigi).
Definition OliviaDecoder.h:308
float frequencyOffset()
Frequency offset of the received signal in Hz.
Definition OliviaDecoder.h:141
int tones
Definition OliviaDecoder.h:162
OliviaDecoder(Print &out)
Definition OliviaDecoder.h:36
float best_signal
Definition OliviaDecoder.h:178
int block_len
Definition OliviaDecoder.h:162
float noise()
uniform random value in the range -0.5 to 0.5
Definition OliviaDecoder.h:218
void endOfCarrier()
Definition OliviaDecoder.h:396
int symbol_len
Definition OliviaDecoder.h:162
float signalToNoise()
Signal to noise ratio of the synchronizer.
Definition OliviaDecoder.h:138
void setAudioInfo(audio_tools::AudioInfo info) override
Defines the format of the input audio data.
Definition OliviaDecoder.h:42
void outputChar(uint8_t ch)
Definition OliviaDecoder.h:370
bool begin() override
Definition OliviaDecoder.h:62
unsigned nblocks
Definition OliviaDecoder.h:185
audio_tools::Vector< float > samples
Definition OliviaDecoder.h:166
audio_tools::Vector< float > fft_re
Definition OliviaDecoder.h:166
static constexpr int SLICES
Definition OliviaDecoder.h:151
audio_tools::Vector< int8_t > soft
Definition OliviaDecoder.h:171
int phase
Definition OliviaDecoder.h:177
int offsets
Definition OliviaDecoder.h:163
int margin
Definition OliviaDecoder.h:163
void resetSync()
Forgets the timing and frequency of the last transmission.
Definition OliviaDecoder.h:206
bool isCarrier() override
true while a transmission is received
Definition OliviaDecoder.h:132
void evaluateBlock()
Provides the best block if the signal to noise ratio is sufficient.
Definition OliviaDecoder.h:346
int slice_countdown
Definition OliviaDecoder.h:168
float snr
Definition OliviaDecoder.h:180
OliviaCodec codec
Definition OliviaDecoder.h:159
int best_phase
Definition OliviaDecoder.h:179
PcmInput input
Definition OliviaDecoder.h:157
bool carrier
Definition OliviaDecoder.h:182
unsigned ndata
Definition OliviaDecoder.h:184
void end() override
Processes the remaining samples and reports the end of the carrier.
Definition OliviaDecoder.h:103
int bits
Definition OliviaDecoder.h:162
float best_noise
Definition OliviaDecoder.h:178
int width
Definition OliviaDecoder.h:163
bool escape
Definition OliviaDecoder.h:183
OliviaConfig & config()
The configuration which was provided in begin().
Definition OliviaDecoder.h:146
void processSlice()
FFT over the last symbol_len samples and decoding of all offsets.
Definition OliviaDecoder.h:234
audio_tools::Vector< float > window
Definition OliviaDecoder.h:166
void addSample8k(float x)
Definition OliviaDecoder.h:223
SampleRateConverter converter
Definition OliviaDecoder.h:158
size_t write(const uint8_t *data, size_t len) override
Provide PCM data to the decoder.
Definition OliviaDecoder.h:122
static constexpr float BLOCK_THRESHOLD
minimum signal to noise ratio of a block relative to the sync threshold
Definition OliviaDecoder.h:153
bool isFinished() override
true if the decoding was stopped (rx_one)
Definition OliviaDecoder.h:135
bool is_active
Definition OliviaDecoder.h:155
OliviaConfig defaultConfig()
Provides a default configuration: olivia-32-1000.
Definition OliviaDecoder.h:39
Assembles the PCM frames from the written bytes and provides the samples of the selected channel as f...
Definition ModemBase.h:170
Converts the sample rate of a signal with a windowed sinc low pass filter (by default the cut off is ...
Definition DSPUtils.h:15
Radix 2 complex FFT with a precomputed twiddle table.
Definition DSPUtils.h:131
TinyModem: software audio modems and packets with error correction.
Definition AX25.h:11
Configuration of the Olivia and Contestia modems (OliviaEncoder, OliviaDecoder): MFSK with 2 to 256 t...
Definition Olivia.h:31
static constexpr float BASE_RATE
internal sample rate which defines the signal
Definition Olivia.h:65