TinyModem
Header-only software audio modem library
Loading...
Searching...
No Matches
MorseEncoder.h
Go to the documentation of this file.
1#pragma once
2
3#include <math.h>
4
5#include "AudioTools.h"
6#include "ModemBase.h"
7#include "Morse.h"
8
9namespace tinymodem {
10
19class MorseEncoder : public ModemEncoder {
20 public:
21 MorseEncoder() = default;
22 MorseEncoder(Print& out) { setOutput(out); }
23
26
27 void setOutput(Print& out) override {
28 samples.setOutput(out);
29 ModemEncoder::setOutput(out); // writeSilence(), writeTone()
30 }
31
33 void setAudioInfo(audio_tools::AudioInfo info) override {
34 cfg.sample_rate = info.sample_rate;
35 cfg.channels = info.channels;
36 cfg.bits_per_sample = info.bits_per_sample;
37 if (is_active) begin();
38 }
39
40 audio_tools::AudioInfo audioInfo() override { return cfg; }
41
42 using audio_tools::AudioEncoder::begin;
43
46 cfg = config;
47 return begin();
48 }
49
50 bool begin() override {
51 is_active = false;
52 if (!cfg.isValid()) return false;
53 if (cfg.bits_per_sample != 16) {
54 LOGE("MorseEncoder: bits_per_sample must be 16, not %d",
55 (int)cfg.bits_per_sample);
56 return false;
57 }
58 if (cfg.channels < 1) {
59 LOGE("MorseEncoder: invalid number of channels: %d", (int)cfg.channels);
60 return false;
61 }
62 samples.begin(cfg.channels, cfg.volume);
63 if (!tones.begin(cfg, cfg.volume)) return false;
64 unit_samples = cfg.unitTime() * cfg.sample_rate;
65 double w = 2.0 * M_PI * cfg.frequency / cfg.sample_rate;
66 step_re = cos(w);
67 step_im = sin(w);
68 transmitting = false;
69 word_pending = false;
70 is_active = true;
71 return true;
72 }
73
74 void end() override {
76 is_active = false;
77 }
78
80 size_t write(const uint8_t* data, size_t len) override {
81 if (!is_active) return 0;
82 for (size_t j = 0; j < len; j++) {
83 const char* code = MorseCode::encode(cfg.alphabet, data[j]);
84 if (code == nullptr) {
85 // word space before the next character
86 if (transmitting) word_pending = true;
87 continue;
88 }
89 sendCode(code);
90 }
91 samples.flush();
92 return len;
93 }
94
97 bool endTransmission(size_t silence_samples = 0) override {
98 if (!is_active || !transmitting) return false;
99 for (size_t j = 0; j < silence_samples; j++) samples.write(0.0f);
100 samples.flush();
101 transmitting = false;
102 word_pending = false;
103 return true;
104 }
105
107 bool writeIdle(size_t nsamples) override {
108 if (!is_active) return false;
109 for (size_t j = 0; j < nsamples; j++) samples.write(0.0f);
110 samples.flush();
111 return true;
112 }
113
115 bool isTransmitting() override { return transmitting; }
116
118 float symbolRate() override { return 1.0f / cfg.unitTime(); }
119
121 MorseConfig& config() { return cfg; }
122
123 operator bool() override { return is_active; }
124
125 protected:
128 bool is_active = false;
129 bool transmitting = false;
130 bool word_pending = false;
131 double unit_samples = 0;
132 double position = 0; // exact time in samples (avoids rounding errors)
133 uint64_t written = 0;
134 float re = 1, im = 0, step_re = 1, step_im = 0;
135 uint32_t sample_count = 0;
136
137 void sendCode(const char* code) {
138 const MorseCode::Timing& t = MorseCode::timing(cfg.alphabet);
139 if (!transmitting) {
140 transmitting = true;
142 } else {
143 // the letter space was sent after the previous character
145 }
146 word_pending = false;
147 for (const char* c = code; *c; c++) {
148 if (*c == ' ') {
149 // space within an American character instead of the element space
151 continue;
152 }
153 float len = *c == '.' ? t.dot
154 : *c == '-' ? t.dash
155 : *c == '_' ? t.long_dash
156 : t.extra_long_dash;
157 tone(len);
158 silence(c[1] ? t.element_gap : t.letter_gap);
159 }
160 }
161
162 void silence(float units) {
163 position += units * unit_samples;
164 for (; written < (uint64_t)llround(position); written++) samples.write(0.0f);
165 }
166
168 void tone(float units) {
169 position += units * unit_samples;
170 uint64_t start = written, stop = llround(position);
171 size_t n = stop - start;
172 size_t ramp = cfg.ramp_ms * cfg.sample_rate / 1000;
173 if (2 * ramp > n) ramp = n / 2;
174 re = 1;
175 im = 0;
176 for (size_t j = 0; j < n; j++) {
177 float gain = 1.0f;
178 if (j < ramp) gain = 0.5f - 0.5f * cosf((float)M_PI * j / ramp);
179 if (n - j <= ramp) gain = 0.5f - 0.5f * cosf((float)M_PI * (n - j) / ramp);
180 samples.write(gain * im);
181 float r = re * step_re - im * step_im;
182 im = re * step_im + im * step_re;
183 re = r;
184 if (++sample_count % 1024 == 0) {
185 float mag = sqrtf(re * re + im * im);
186 re /= mag;
187 im /= mag;
188 }
189 }
190 written = stop;
191 }
192};
193
194} // namespace tinymodem
Common API of the modulators (FSKEncoder, MFSKEncoder): the written data is converted to 16 bit PCM a...
Definition ModemBase.h:286
void setOutput(Print &out) override
Definition ModemBase.h:288
ToneOutput tones
Definition ModemBase.h:334
static const Timing & timing(MorseAlphabet alphabet)
Definition Morse.h:153
static const char * encode(MorseAlphabet alphabet, char ch)
Definition Morse.h:161
MorseConfig & config()
The configuration which was provided in begin().
Definition MorseEncoder.h:121
double unit_samples
Definition MorseEncoder.h:131
bool word_pending
Definition MorseEncoder.h:130
float step_im
Definition MorseEncoder.h:134
void setAudioInfo(audio_tools::AudioInfo info) override
Defines the sample rate and number of channels of the generated audio.
Definition MorseEncoder.h:33
bool writeIdle(size_t nsamples) override
Sends silence for the indicated number of samples.
Definition MorseEncoder.h:107
float re
Definition MorseEncoder.h:134
size_t write(const uint8_t *data, size_t len) override
Sends the characters.
Definition MorseEncoder.h:80
bool isTransmitting() override
true between the first character and endTransmission()
Definition MorseEncoder.h:115
MorseEncoder(Print &out)
Definition MorseEncoder.h:22
double position
Definition MorseEncoder.h:132
bool transmitting
Definition MorseEncoder.h:129
audio_tools::AudioInfo audioInfo() override
Definition MorseEncoder.h:40
bool begin() override
Definition MorseEncoder.h:50
MorseConfig defaultConfig()
Provides a default configuration: International Morse code, 20 wpm.
Definition MorseEncoder.h:25
void tone(float units)
Tone with raised cosine ramps.
Definition MorseEncoder.h:168
SampleOutput samples
Definition MorseEncoder.h:127
float step_re
Definition MorseEncoder.h:134
bool endTransmission(size_t silence_samples=0) override
Definition MorseEncoder.h:97
void end() override
Definition MorseEncoder.h:74
uint32_t sample_count
Definition MorseEncoder.h:135
uint64_t written
Definition MorseEncoder.h:133
MorseConfig cfg
Definition MorseEncoder.h:126
float symbolRate() override
Units per second.
Definition MorseEncoder.h:118
void silence(float units)
Definition MorseEncoder.h:162
bool is_active
Definition MorseEncoder.h:128
void sendCode(const char *code)
Definition MorseEncoder.h:137
float im
Definition MorseEncoder.h:134
void setOutput(Print &out) override
Definition MorseEncoder.h:27
bool begin(MorseConfig config)
Starts the processing with the indicated configuration.
Definition MorseEncoder.h:45
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
TinyModem: software audio modems and packets with error correction.
Definition AX25.h:11
Lengths in units.
Definition Morse.h:146
float extra_long_dash
Definition Morse.h:147
float dot
Definition Morse.h:147
float letter_gap
Definition Morse.h:148
float element_gap
Definition Morse.h:148
float dash
Definition Morse.h:147
float inner_gap
Definition Morse.h:148
float long_dash
Definition Morse.h:147
float word_gap
Definition Morse.h:148
Configuration of the Morse code modem (MorseEncoder, MorseDecoder): a tone which is switched on and o...
Definition Morse.h:31