TinyModem
Header-only software audio modem library
Loading...
Searching...
No Matches
ModemBase.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
11namespace tinymodem {
12
19template <class T = int16_t>
20class FSKToneGenerator : public audio_tools::FloatSineGenerator<T> {
21 public:
22 void setFrequency(float frequency) override {
23 this->m_frequency = frequency;
24 if (frequency == 0.0f) this->m_cycles = 0.0f;
25 }
26
27 T readSample() override {
28 if (this->m_frequency == 0.0f) return 0;
29 float value = this->m_amplitude * sinf(2.0f * (float)M_PI * this->m_cycles);
30 this->m_cycles += this->m_frequency * this->m_deltaTime;
31 this->m_cycles -= floorf(this->m_cycles);
32 return (T)lroundf(value);
33 }
34};
35
42 public:
43 void setOutput(Print& out) { p_print = &out; }
44
45 void setGenerator(audio_tools::SoundGenerator<int16_t>& generator) {
47 }
48
49 audio_tools::SoundGenerator<int16_t>& generator() { return *p_generator; }
50
52 bool begin(audio_tools::AudioInfo info, float volume) {
53 channels = info.channels;
54 audio_tools::AudioInfo gen_info = info;
55 gen_info.channels = 1; // we copy the sample to all channels ourselves
56 if (!p_generator->begin(gen_info)) {
57 LOGE("tone generator could not be started");
58 return false;
59 }
60 gain = amplitude(volume);
61 return true;
62 }
63
66 void tone(float freq, size_t nsamples) {
67 if (p_print == nullptr) return;
68 p_generator->setFrequency(freq);
69 int frames_per_buffer = BUFFER_FRAMES * 2 / channels;
70 while (nsamples > 0) {
71 int frames = nsamples < (size_t)frames_per_buffer ? nsamples
72 : frames_per_buffer;
73 int idx = 0;
74 for (int j = 0; j < frames; j++) {
75 int16_t sample = freq == 0.0f ? 0 : scale(p_generator->readSample());
76 for (int ch = 0; ch < channels; ch++) buffer[idx++] = sample;
77 }
78 // blocking write
79 const uint8_t* data = (const uint8_t*)buffer;
80 size_t open = idx * sizeof(int16_t);
81 while (open > 0) {
82 size_t written = p_print->write(data, open);
83 data += written;
84 open -= written;
85 }
86 nsamples -= frames;
87 }
88 }
89
91 static int32_t amplitude(float volume) {
92 float mag = 32767.0f * volume + 0.5f;
93 if (volume > 1.0f) mag = 32767; // clamp to 1.0 to avoid overflow
94 if (mag < 1) mag = 1; // "short epsilon"
95 return (int)mag;
96 }
97
98 protected:
99 static constexpr int BUFFER_FRAMES = 64;
100 Print* p_print = nullptr;
102 audio_tools::SoundGenerator<int16_t>* p_generator = &default_generator;
103 int32_t gain = 32767; // volume in Q15
104 int channels = 1;
105 int16_t buffer[BUFFER_FRAMES * 2];
106
107 int16_t scale(int16_t sample) {
108 if (gain == 32767) return sample;
109 return (int16_t)((int32_t)sample * gain / 32767);
110 }
111};
112
119 public:
120 void setOutput(Print& out) { p_print = &out; }
121
122 void begin(int channels, float volume) {
123 this->channels = channels;
124 gain = ToneOutput::amplitude(volume);
125 idx = 0;
126 }
127
129 void write(float sample) {
130 if (sample > 1.0f) sample = 1.0f;
131 if (sample < -1.0f) sample = -1.0f;
132 int16_t value = lroundf(sample * gain);
133 for (int ch = 0; ch < channels; ch++) {
134 buffer[idx++] = value;
135 if (idx == BUFFER_SIZE) flush();
136 }
137 }
138
140 void flush() {
141 if (p_print == nullptr || idx == 0) {
142 idx = 0;
143 return;
144 }
145 const uint8_t* data = (const uint8_t*)buffer;
146 size_t open = idx * sizeof(int16_t);
147 while (open > 0) {
148 size_t written = p_print->write(data, open);
149 data += written;
150 open -= written;
151 }
152 idx = 0;
153 }
154
155 protected:
156 static constexpr int BUFFER_SIZE = 128;
157 Print* p_print = nullptr;
159 int idx = 0;
160 int channels = 1;
161 int32_t gain = 32767;
162};
163
170class PcmInput {
171 public:
172 static constexpr int MAX_CHANNELS = 8;
173
175 bool begin(int bits_per_sample, int channels, int channel) {
176 if (!(bits_per_sample == 8 || bits_per_sample == 16 ||
177 bits_per_sample == 32)) {
178 LOGE("bits_per_sample must be 8, 16 or 32, not %d", bits_per_sample);
179 return false;
180 }
181 if (channels < 1 || channels > MAX_CHANNELS) {
182 LOGE("invalid number of channels: %d", channels);
183 return false;
184 }
185 if (channel >= channels) {
186 LOGE("channel %d is not available (channels=%d)", channel, channels);
187 return false;
188 }
189 bits = bits_per_sample;
190 int bytes = bits / 8;
191 frame_bytes = bytes * channels;
192 sample_offset = bytes * channel;
193 partial_len = 0;
194 return true;
195 }
196
198 void reset() { partial_len = 0; }
199
201 template <class F>
202 void write(const uint8_t* data, size_t len, F on_sample) {
203 size_t j = 0;
204 // complete a frame from the previous call
205 while (partial_len > 0 && j < len) {
206 partial[partial_len++] = data[j++];
207 if (partial_len == frame_bytes) {
208 on_sample(sampleValue(partial + sample_offset));
209 partial_len = 0;
210 }
211 }
212 // complete frames
213 for (; len - j >= (size_t)frame_bytes; j += frame_bytes)
214 on_sample(sampleValue(data + j + sample_offset));
215 // keep the remaining bytes for the next call
216 while (j < len) partial[partial_len++] = data[j++];
217 }
218
219 protected:
220 uint8_t partial[MAX_CHANNELS * 4];
221 int partial_len = 0;
222 int frame_bytes = 2;
224 int bits = 16;
225
226 float sampleValue(const uint8_t* ptr) {
227 switch (bits) {
228 case 8:
229 return (int8_t)ptr[0] / 128.0f;
230 case 16: {
231 int16_t v;
232 memcpy(&v, ptr, sizeof(v));
233 return v / 32768.0f;
234 }
235 default: {
236 int32_t v;
237 memcpy(&v, ptr, sizeof(v));
238 return v / 2147483648.0f;
239 }
240 }
241 }
242};
243
249class ByteQueue : public Print {
250 public:
251 size_t write(uint8_t ch) override {
252 data.push_back(ch);
253 return 1;
254 }
255 int available() { return data.size() - pos; }
256 int peek() { return available() > 0 ? data[pos] : -1; }
257 int read() {
258 int result = peek();
259 if (result < 0) return result;
260 if (++pos == (size_t)data.size()) {
261 clear();
262 } else if (pos >= 64 && pos * 2 >= (size_t)data.size()) {
263 // drop the consumed data
264 size_t n = data.size() - pos;
265 memmove(data.data(), data.data() + pos, n);
266 data.resize(n);
267 pos = 0;
268 }
269 return result;
270 }
271 void clear() {
272 data.clear();
273 pos = 0;
274 }
275
276 protected:
277 audio_tools::Vector<uint8_t> data;
278 size_t pos = 0;
279};
280
286class ModemEncoder : public audio_tools::AudioEncoder {
287 public:
288 void setOutput(Print& out) override { tones.setOutput(out); }
289
293 void setGenerator(audio_tools::SoundGenerator<int16_t>& generator) {
294 tones.setGenerator(generator);
295 }
296
298 audio_tools::SoundGenerator<int16_t>& getGenerator() {
299 return tones.generator();
300 }
301
304 virtual bool endTransmission(size_t silence_samples = 0) = 0;
305
307 virtual bool isTransmitting() = 0;
308
310 virtual bool writeIdle(size_t nsamples) {
311 (void)nsamples;
312 return false;
313 }
314
316 void writeSilence(size_t nsamples) {
317 if (*this) tones.tone(0, nsamples);
318 }
319
321 void writeTone(float freq, size_t nsamples) {
322 if (*this) tones.tone(freq, nsamples);
323 }
324
326 void flush() { endTransmission(); }
327
329 virtual float symbolRate() = 0;
330
331 const char* mime() override { return "audio/pcm"; }
332
333 protected:
335};
336
344class ModemDecoder : public audio_tools::AudioDecoder {
345 public:
347 void setStatusOutput(Print& out) { p_status = &out; }
348
350 void setQuiet(bool quiet) { is_quiet = quiet; }
351
353 virtual bool isCarrier() = 0;
354
356 virtual bool isFinished() = 0;
357
359 virtual void flush() = 0;
360
361 const char* mime() override { return "audio/pcm"; }
362
363 protected:
364 Print* p_status = nullptr;
365 bool is_quiet = false;
366
367 void status(const char* fmt, ...) {
368 if (p_status == nullptr || is_quiet) return;
369 char tmp[160];
370 va_list args;
371 va_start(args, fmt);
372 vsnprintf(tmp, sizeof(tmp), fmt, args);
373 va_end(args);
374 p_status->print(tmp);
375 }
376};
377
378} // namespace tinymodem
FIFO for received data which grows when needed: e.g. one decoding step can produce more data than exp...
Definition ModemBase.h:249
int available()
Definition ModemBase.h:255
audio_tools::Vector< uint8_t > data
Definition ModemBase.h:277
void clear()
Definition ModemBase.h:271
int read()
Definition ModemBase.h:257
int peek()
Definition ModemBase.h:256
size_t write(uint8_t ch) override
Definition ModemBase.h:251
size_t pos
Definition ModemBase.h:278
Default sine generator of the encoders: like minimodem it keeps the phase continuous when the frequen...
Definition ModemBase.h:20
T readSample() override
Definition ModemBase.h:27
void setFrequency(float frequency) override
Definition ModemBase.h:22
Common API of the demodulators (FSKDecoder, MFSKDecoder): PCM audio data is written to the decoder an...
Definition ModemBase.h:344
bool is_quiet
Definition ModemBase.h:365
virtual bool isFinished()=0
true if the decoding was stopped (rx_one)
void setStatusOutput(Print &out)
Defines the output for the ### CARRIER / ### NOCARRIER messages.
Definition ModemBase.h:347
void setQuiet(bool quiet)
Suppresses the status messages.
Definition ModemBase.h:350
virtual bool isCarrier()=0
true while a carrier is detected
Print * p_status
Definition ModemBase.h:364
void status(const char *fmt,...)
Definition ModemBase.h:367
const char * mime() override
Definition ModemBase.h:361
virtual void flush()=0
Processes all buffered samples (e.g. at the end of the input).
Common API of the modulators (FSKEncoder, MFSKEncoder): the written data is converted to 16 bit PCM a...
Definition ModemBase.h:286
virtual bool writeIdle(size_t nsamples)
Sends an idle carrier: returns false if this is not supported.
Definition ModemBase.h:310
virtual float symbolRate()=0
Symbols (bits for FSK) per second.
const char * mime() override
Definition ModemBase.h:331
void setOutput(Print &out) override
Definition ModemBase.h:288
void writeSilence(size_t nsamples)
Writes the indicated number of samples of silence.
Definition ModemBase.h:316
virtual bool endTransmission(size_t silence_samples=0)=0
void writeTone(float freq, size_t nsamples)
Writes a tone for the indicated number of samples.
Definition ModemBase.h:321
void setGenerator(audio_tools::SoundGenerator< int16_t > &generator)
Definition ModemBase.h:293
ToneOutput tones
Definition ModemBase.h:334
void flush()
Ends the transmission.
Definition ModemBase.h:326
virtual bool isTransmitting()=0
true between the first character and endTransmission()
audio_tools::SoundGenerator< int16_t > & getGenerator()
Provides the active tone generator.
Definition ModemBase.h:298
Assembles the PCM frames from the written bytes and provides the samples of the selected channel as f...
Definition ModemBase.h:170
int bits
Definition ModemBase.h:224
int sample_offset
Definition ModemBase.h:223
void write(const uint8_t *data, size_t len, F on_sample)
Calls on_sample(float) for each complete frame.
Definition ModemBase.h:202
static constexpr int MAX_CHANNELS
Definition ModemBase.h:172
bool begin(int bits_per_sample, int channels, int channel)
Defines the format: returns false (and logs the reason) if not supported.
Definition ModemBase.h:175
float sampleValue(const uint8_t *ptr)
Definition ModemBase.h:226
int frame_bytes
Definition ModemBase.h:222
void reset()
Drops an incomplete frame.
Definition ModemBase.h:198
uint8_t partial[MAX_CHANNELS *4]
Definition ModemBase.h:220
int partial_len
Definition ModemBase.h:221
Writes float samples (-1.0 to 1.0) as 16 bit PCM with the requested number of channels and volume to ...
Definition ModemBase.h:118
Print * p_print
Definition ModemBase.h:157
int channels
Definition ModemBase.h:160
int16_t buffer[BUFFER_SIZE]
Definition ModemBase.h:158
int idx
Definition ModemBase.h:159
void flush()
Writes the buffered samples.
Definition ModemBase.h:140
static constexpr int BUFFER_SIZE
Definition ModemBase.h:156
void setOutput(Print &out)
Definition ModemBase.h:120
void begin(int channels, float volume)
Definition ModemBase.h:122
void write(float sample)
Writes a sample to all channels.
Definition ModemBase.h:129
int32_t gain
Definition ModemBase.h:161
Writes tones of a SoundGenerator as 16 bit PCM with the requested number of channels and volume to a ...
Definition ModemBase.h:41
void setGenerator(audio_tools::SoundGenerator< int16_t > &generator)
Definition ModemBase.h:45
audio_tools::SoundGenerator< int16_t > * p_generator
Definition ModemBase.h:102
void setOutput(Print &out)
Definition ModemBase.h:43
bool begin(audio_tools::AudioInfo info, float volume)
Starts the generator: volume is the amplitude (0.0 - 1.0).
Definition ModemBase.h:52
Print * p_print
Definition ModemBase.h:100
void tone(float freq, size_t nsamples)
Definition ModemBase.h:66
audio_tools::SoundGenerator< int16_t > & generator()
Definition ModemBase.h:49
static int32_t amplitude(float volume)
minimodem's conversion of the volume to a 16 bit amplitude
Definition ModemBase.h:91
int16_t scale(int16_t sample)
Definition ModemBase.h:107
int32_t gain
Definition ModemBase.h:103
static constexpr int BUFFER_FRAMES
Definition ModemBase.h:99
FSKToneGenerator< int16_t > default_generator
Definition ModemBase.h:101
int16_t buffer[BUFFER_FRAMES *2]
Definition ModemBase.h:105
int channels
Definition ModemBase.h:104
TinyModem: software audio modems and packets with error correction.
Definition AX25.h:11