TinyModem
Header-only software audio modem library
Loading...
Searching...
No Matches
Olivia.h
Go to the documentation of this file.
1#pragma once
2
3#include <ctype.h>
4#include <math.h>
5#include <stdint.h>
6#include <stdlib.h>
7#include <string.h>
8
9#include "AudioTools.h"
10#include "DSPUtils.h"
11
12namespace tinymodem {
13
31struct OliviaConfig : public audio_tools::AudioInfo {
33 sample_rate = 48000;
34 channels = 1;
35 bits_per_sample = 16;
36 }
37
39 int tones = 32;
41 int bandwidth = 1000;
43 float center_freq = 1500.0f;
45 bool contestia = false;
47 float volume = 1.0f;
49 bool start_tones = true;
52 int sync_margin = 8;
56 float sync_threshold = 3.0f;
58 bool rx_one = false;
60 uint8_t channel = 0;
62 float rx_noise = 0.0f;
63
65 static constexpr float BASE_RATE = 8000.0f;
66
69 bool setMode(const char* mode) {
70 int t, b;
71 bool c;
72 if (!parseMode(mode, t, b, c)) return false;
73 tones = t;
74 bandwidth = b;
75 contestia = c;
76 return true;
77 }
78
80 static bool isMode(const char* mode) {
81 int t, b;
82 bool c;
83 return parseMode(mode, t, b, c);
84 }
85
87 int bitsPerSymbol() const {
88 int bits = 0;
89 while ((1 << (bits + 1)) <= tones) bits++;
90 return bits;
91 }
92
94 int symbolLen() const {
95 int bw = 0;
96 while ((125 << (bw + 1)) <= bandwidth) bw++;
97 return 1 << (bitsPerSymbol() + 7 - bw);
98 }
99
101 int symbolSepar() const { return symbolLen() / 2; }
102
104 int symbolsPerBlock() const { return contestia ? 32 : 64; }
105
107 int bitsPerCharacter() const { return contestia ? 6 : 7; }
108
110 int firstCarrier() const {
111 double offset = contestia ? bandwidth / 2.0
112 : bandwidth * (1.0 - 0.5 / tones) / 2.0;
113 double multiplier = (center_freq - offset) / 500.0;
114 return (int)((symbolLen() / 16) * multiplier) + 1;
115 }
116
118 float toneFreq(int tone) const {
119 return (firstCarrier() + 2 * tone) * BASE_RATE / symbolLen();
120 }
121
123 float symbolRate() const { return BASE_RATE / symbolSepar(); }
124
126 float charRate() const {
128 }
129
131 bool isValid() const {
132 if (tones < 2 || tones > 256 || (tones & (tones - 1)) != 0) {
133 LOGE("Olivia: tones must be a power of 2 (2 - 256)");
134 return false;
135 }
136 if (!(bandwidth == 125 || bandwidth == 250 || bandwidth == 500 ||
137 bandwidth == 1000 || bandwidth == 2000)) {
138 LOGE("Olivia: bandwidth must be 125, 250, 500, 1000 or 2000");
139 return false;
140 }
141 if (symbolLen() < 16 || symbolLen() > MAX_SYMBOL_LEN) {
142 LOGE("Olivia: %d tones with %d Hz are not supported", tones, bandwidth);
143 return false;
144 }
145 if (sync_margin < 1 || sync_integration < 1 || sync_threshold <= 0) {
146 LOGE("Olivia: invalid synchronizer settings");
147 return false;
148 }
149 if (firstCarrier() < 1 || toneFreq(tones) >= 0.45f * BASE_RATE) {
150 LOGE("Olivia: the center frequency %.1f Hz is out of range",
151 (double)center_freq);
152 return false;
153 }
154 if (sample_rate < 4000) {
155 LOGE("Olivia: the sample rate must be at least 4000 Hz");
156 return false;
157 }
158 return true;
159 }
160
162 static constexpr int MAX_SYMBOL_LEN = 2048;
163
164 protected:
165 static bool parseMode(const char* mode, int& tones, int& bandwidth,
166 bool& contestia) {
167 if (mode == nullptr) return false;
168 const char* rest;
169 if (startsWith(mode, "olivia")) {
170 contestia = false;
171 rest = mode + 6;
172 tones = 32;
173 bandwidth = 1000;
174 } else if (startsWith(mode, "contestia")) {
175 contestia = true;
176 rest = mode + 9;
177 tones = 8;
178 bandwidth = 250;
179 } else {
180 return false;
181 }
182 if (*rest == 0) return true;
183 if (*rest != '-' && *rest != '_') return false;
184 char* end;
185 tones = strtol(rest + 1, &end, 10);
186 if (*end != '-' && *end != '_' && *end != '/') return false;
187 bandwidth = strtol(end + 1, &end, 10);
188 if (tolower(*end) == 'k') {
189 bandwidth *= 1000;
190 end++;
191 }
192 if (*end != 0) return false;
193 if (tones < 2 || tones > 256 || (tones & (tones - 1)) != 0) return false;
194 return bandwidth == 125 || bandwidth == 250 || bandwidth == 500 ||
195 bandwidth == 1000 || bandwidth == 2000;
196 }
197
198 static bool startsWith(const char* str, const char* prefix) {
199 while (*prefix) {
200 if (tolower(*str++) != *prefix++) return false;
201 }
202 return true;
203 }
204};
205
214 public:
215 void begin(int bits_per_symbol, bool is_contestia) {
216 bits = bits_per_symbol;
217 contestia = is_contestia;
218 block_len = contestia ? 32 : 64;
219 }
220
223 bool isSkipped(uint8_t ch) const { return contestia && ch == '\r'; }
224
226 void encodeBlock(const uint8_t* chars, uint8_t* symbols) {
227 int8_t fht[64];
228 for (int t = 0; t < block_len; t++) symbols[t] = 0;
229 for (int b = 0; b < bits; b++) {
230 for (int t = 0; t < block_len; t++) fht[t] = 0;
231 uint8_t ch = encodeChar(chars[b]);
232 if (ch < block_len)
233 fht[ch] = 1;
234 else
235 fht[ch - block_len] = -1;
236 ifht(fht, block_len);
237 int code_bit = (b * shift()) & (block_len - 1);
238 int rotate = 0;
239 for (int t = 0; t < block_len; t++) {
240 if ((scramblingCode() >> code_bit) & 1) fht[t] = -fht[t];
241 code_bit = (code_bit + 1) & (block_len - 1);
242 if (fht[t] < 0) {
243 int bit = b + rotate;
244 if (bit >= bits) bit -= bits;
245 symbols[t] |= 1 << bit;
246 }
247 if (++rotate >= bits) rotate = 0;
248 }
249 }
250 }
251
255 uint8_t decodeChar(const int8_t* soft, int b, float& peak, float& noise) {
256 int32_t fht[64];
257 int code_bit = (b * shift()) & (block_len - 1);
258 int rotate = b;
259 for (int t = 0; t < block_len; t++) {
260 int32_t bit = soft[t * bits + rotate];
261 if ((scramblingCode() >> code_bit) & 1) bit = -bit;
262 fht[t] = bit;
263 code_bit = (code_bit + 1) & (block_len - 1);
264 if (++rotate >= bits) rotate = 0;
265 }
267 int32_t max = 0;
268 int pos = 0;
269 float sum = 0;
270 for (int t = 0; t < block_len; t++) {
271 float v = fht[t];
272 sum += v * v;
273 if (abs(fht[t]) > abs(max)) {
274 max = fht[t];
275 pos = t;
276 }
277 }
278 uint8_t ch = pos;
279 if (max < 0) ch += block_len;
280 peak = abs(max);
281 noise = (sum - peak * peak) / (block_len - 1);
282 return decodeChar(ch);
283 }
284
285 int blockLen() const { return block_len; }
286
287 static uint8_t grayCode(uint8_t binary) { return binary ^ (binary >> 1); }
288
289 static uint8_t binaryCode(uint8_t gray) {
290 gray ^= gray >> 4;
291 gray ^= gray >> 2;
292 gray ^= gray >> 1;
293 return gray;
294 }
295
296 protected:
297 int bits = 5;
298 int block_len = 64;
299 bool contestia = false;
300
301 uint64_t scramblingCode() const {
302 return contestia ? 0xEDB88320ull : 0xE257E6D0291574ECull;
303 }
304
305 int shift() const { return contestia ? 5 : 13; }
306
307 uint8_t encodeChar(uint8_t ch) const {
308 if (!contestia) return ch & 0x7F;
309 if (ch >= 'a' && ch <= 'z') ch += 'A' - 'a';
310 if (ch == ' ') return 59;
311 if (ch == '\n') return 60; // fldigi shows '\r' as new line
312 if (ch == 0) return 0;
313 if (ch >= 33 && ch <= 90) return ch - 32;
314 if (ch == 8) return 61;
315 return '?' - 32;
316 }
317
318 uint8_t decodeChar(uint8_t ch) const {
319 if (!contestia || ch == 0) return ch;
320 if (ch == 59) return ' ';
321 if (ch == 60) return '\n';
322 if (ch == 61) return 8;
323 return ch + 32;
324 }
325
327 template <class T>
328 static void fhtTransform(T* data, int len) {
329 for (int step = 1; step < len; step *= 2)
330 for (int ptr = 0; ptr < len; ptr += 2 * step)
331 for (int j = ptr; j < ptr + step; j++) {
332 T a = data[j], b = data[j + step];
333 data[j] = b + a;
334 data[j + step] = b - a;
335 }
336 }
337
339 template <class T>
340 static void ifht(T* data, int len) {
341 for (int step = len / 2; step; step /= 2)
342 for (int ptr = 0; ptr < len; ptr += 2 * step)
343 for (int j = ptr; j < ptr + step; j++) {
344 T a = data[j], b = data[j + step];
345 data[j] = a - b;
346 data[j + step] = a + b;
347 }
348 }
349};
350
351} // namespace tinymodem
The forward error correction of Olivia and Contestia (as in fldigi): each character of a block of bit...
Definition Olivia.h:213
void begin(int bits_per_symbol, bool is_contestia)
Definition Olivia.h:215
int shift() const
Definition Olivia.h:305
uint8_t encodeChar(uint8_t ch) const
Definition Olivia.h:307
static void fhtTransform(T *data, int len)
fast Hadamard transform (fldigi FHT)
Definition Olivia.h:328
uint64_t scramblingCode() const
Definition Olivia.h:301
bool isSkipped(uint8_t ch) const
Definition Olivia.h:223
void encodeBlock(const uint8_t *chars, uint8_t *symbols)
Encodes bits characters into block_len symbols (binary, not Gray coded).
Definition Olivia.h:226
uint8_t decodeChar(uint8_t ch) const
Definition Olivia.h:318
static uint8_t binaryCode(uint8_t gray)
Definition Olivia.h:289
int block_len
Definition Olivia.h:298
uint8_t decodeChar(const int8_t *soft, int b, float &peak, float &noise)
Definition Olivia.h:255
int bits
Definition Olivia.h:297
static uint8_t grayCode(uint8_t binary)
Definition Olivia.h:287
static void ifht(T *data, int len)
inverse fast Hadamard transform (fldigi IFHT)
Definition Olivia.h:340
bool contestia
Definition Olivia.h:299
int blockLen() const
Definition Olivia.h:285
TinyModem: software audio modems and packets with error correction.
Definition AX25.h:11
int symbolsPerBlock() const
symbols per FEC block: 64 for Olivia, 32 for Contestia
Definition Olivia.h:104
uint8_t channel
receiver: input channel which is used for the decoding
Definition Olivia.h:60
bool contestia
Contestia instead of Olivia.
Definition Olivia.h:45
int tones
number of tones (power of 2: 2 - 256)
Definition Olivia.h:39
bool rx_one
receiver: stop after the first transmission
Definition Olivia.h:58
bool start_tones
transmitter: send the start and stop tones (like fldigi)
Definition Olivia.h:49
bool setMode(const char *mode)
Definition Olivia.h:69
int symbolLen() const
FFT length (window of a symbol) in samples at 8000 Hz.
Definition Olivia.h:94
static bool startsWith(const char *str, const char *prefix)
Definition Olivia.h:198
static constexpr float BASE_RATE
internal sample rate which defines the signal
Definition Olivia.h:65
float rx_noise
receiver: (testing only) amplitude of added uniform noise
Definition Olivia.h:62
bool isValid() const
Checks the configuration: returns false (and logs the reason) if invalid.
Definition Olivia.h:131
int bitsPerSymbol() const
number of bits per symbol (log2 of the tones)
Definition Olivia.h:87
int sync_integration
receiver: integration period of the synchronizer in FEC blocks
Definition Olivia.h:54
OliviaConfig()
Definition Olivia.h:32
float symbolRate() const
symbols per second
Definition Olivia.h:123
float charRate() const
characters per second
Definition Olivia.h:126
static constexpr int MAX_SYMBOL_LEN
largest supported FFT length
Definition Olivia.h:162
float sync_threshold
receiver: minimum signal to noise ratio of the synchronizer
Definition Olivia.h:56
static bool isMode(const char *mode)
true if the mode is an Olivia or Contestia mode
Definition Olivia.h:80
float toneFreq(int tone) const
frequency of a tone in Hz
Definition Olivia.h:118
float center_freq
center frequency in Hz
Definition Olivia.h:43
float volume
transmit amplitude (0.0 - 1.0)
Definition Olivia.h:47
int sync_margin
Definition Olivia.h:52
int symbolSepar() const
distance of two symbols in samples at 8000 Hz
Definition Olivia.h:101
static bool parseMode(const char *mode, int &tones, int &bandwidth, bool &contestia)
Definition Olivia.h:165
int bandwidth
bandwidth in Hz: 125, 250, 500, 1000 or 2000
Definition Olivia.h:41
int bitsPerCharacter() const
bits per character: 7 for Olivia, 6 for Contestia
Definition Olivia.h:107
int firstCarrier() const
lowest tone as FFT bin (like fldigi)
Definition Olivia.h:110