TinyModem
Header-only software audio modem library
Loading...
Searching...
No Matches
DTMFDecoder.h
Go to the documentation of this file.
1#pragma once
2
3#include <math.h>
4
5#include "AudioTools.h"
6#include "DTMF.h"
7#include "ModemBase.h"
8
9namespace tinymodem {
10
31class DTMFDecoder : public ModemDecoder {
32 public:
33 DTMFDecoder() = default;
34 DTMFDecoder(Print& out) { setOutput(out); }
35
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;
62 if (!cfg.isValid()) return false;
63 if (!input.begin(cfg.bits_per_sample, cfg.channels, cfg.channel))
64 return false;
65 window = lroundf(cfg.sample_rate * WINDOW_SEC);
67 for (int t = 0; t < TONES; t++) {
68 float freq = t < 4 ? DTMFKeys::rows()[t] : DTMFKeys::columns()[t - 4];
69 coeff[t] = 2.0f * cosf(2.0f * (float)M_PI * freq / cfg.sample_rate);
70 }
71 timeout = (uint32_t)cfg.rx_timeout_ms * cfg.sample_rate / 1000;
72 twist = powf(10.0f, cfg.max_twist_db / 20.0f); // amplitude ratio
73 group_ratio = powf(10.0f, cfg.group_ratio_db / 10.0f); // power ratio
74 resetState();
75 is_active = true;
76 return true;
77 }
78
80 void end() override {
81 if (!is_active) return;
82 flush();
83 is_active = false;
84 }
85
87 void flush() override {
88 if (!is_active) return;
89 // feed silence, so that the end of the key is detected
90 for (int j = 0; j < 2 * window && !is_finished; j++) addSample(0.0f, false);
91 if (carrier) endOfCarrier();
92 bool finished = is_finished;
93 resetState();
94 is_finished = finished;
95 }
96
98 size_t write(const uint8_t* data, size_t len) override {
99 if (!is_active) return 0;
100 input.write(data, len, [this](float sample) { addSample(sample, true); });
101 return len;
102 }
103
105 bool isCarrier() override { return carrier; }
106
108 bool isFinished() override { return is_finished; }
109
111 char currentKey() { return held < 0 ? 0 : DTMFKeys::key(held / 4, held % 4); }
112
114 DTMFConfig& config() { return cfg; }
115
116 operator bool() override { return is_active; }
117
118 protected:
119 static constexpr int TONES = 8;
120 static constexpr int WINDOWS = 4;
121 static constexpr float WINDOW_SEC = 205.0f / 8000.0f;
122 static constexpr int HITS = 3;
123 static constexpr int MISSES = 3;
125 static constexpr float ECHO_LEVEL = 0.5f;
127 static constexpr float ENERGY_RATIO = 0.3f;
128
130 struct Window {
131 float s1[TONES], s2[TONES];
132 float power;
133 int count;
134 };
135
137 bool is_active = false;
138 bool is_finished = false;
140 uint32_t noise_seed = 1;
141 int window = 205;
142 int step = 51;
143 float coeff[TONES];
145 uint32_t timeout = 0;
146 float twist = 6.3f;
147 float group_ratio = 4.0f;
148
149 // debouncing
150 int candidate = -1;
151 int hits = 0;
152 int held = -1;
153 int misses = 0;
154 // the last reported key and its level: detects echoes
155 int last_key = -1;
156 float last_level = 0;
157 float level = 0; // tone level of the current window
158 bool carrier = false;
159 uint32_t idle_samples = 0;
160 unsigned ndata = 0;
161
162 void resetState() {
163 for (int w = 0; w < WINDOWS; w++) {
165 // staggered start: one window is completed every step samples
166 windows[w].count = -w * step;
167 }
168 candidate = held = last_key = -1;
169 hits = misses = 0;
170 last_level = 0;
171 carrier = false;
172 idle_samples = 0;
173 is_finished = false;
174 input.reset();
175 }
176
178 for (int t = 0; t < TONES; t++) w.s1[t] = w.s2[t] = 0;
179 w.power = 0;
180 w.count = 0;
181 }
182
184 float noise() {
185 noise_seed = noise_seed * 1103515245u + 12345u;
186 return ((noise_seed >> 8) & 0xFFFF) / 65535.0f - 0.5f;
187 }
188
189 void addSample(float x, bool add_noise) {
190 if (is_finished) return;
191 if (add_noise && cfg.rx_noise != 0.0f) x += noise() * cfg.rx_noise * 2.0f;
192 for (int k = 0; k < WINDOWS; k++) {
193 Window& w = windows[k];
194 if (w.count++ < 0) continue; // not started yet
195 for (int t = 0; t < TONES; t++) {
196 float s = x + coeff[t] * w.s1[t] - w.s2[t];
197 w.s2[t] = w.s1[t];
198 w.s1[t] = s;
199 }
200 w.power += x * x;
201 if (w.count == window) {
202 decide(detect(w));
203 resetWindow(w);
204 }
205 }
207 }
208
211 int detect(Window& w) {
212 float amp[TONES];
213 for (int t = 0; t < TONES; t++) {
214 float p = w.s1[t] * w.s1[t] + w.s2[t] * w.s2[t] -
215 coeff[t] * w.s1[t] * w.s2[t];
216 amp[t] = 2.0f * sqrtf(p > 0 ? p : 0) / window;
217 }
218 int row = strongest(amp);
219 int column = strongest(amp + 4);
220 float a_row = amp[row], a_col = amp[4 + column];
221 level = a_row + a_col;
222 if (a_row < cfg.min_amplitude || a_col < cfg.min_amplitude) return -1;
223 if (a_row > twist * a_col || a_col > twist * a_row) return -1;
224 for (int t = 0; t < 4; t++) {
225 if (t != row && amp[t] * amp[t] * group_ratio > a_row * a_row) return -1;
226 if (t != column && amp[4 + t] * amp[4 + t] * group_ratio > a_col * a_col)
227 return -1;
228 }
229 // power of the tones (a^2 / 2) compared to the mean power
230 float tone_power = (a_row * a_row + a_col * a_col) / 2.0f;
231 if (tone_power < ENERGY_RATIO * w.power / window) return -1;
232 return row * 4 + column;
233 }
234
235 static int strongest(const float* amp) {
236 int result = 0;
237 for (int t = 1; t < 4; t++)
238 if (amp[t] > amp[result]) result = t;
239 return result;
240 }
241
243 void decide(int key) {
244 if (key >= 0 && key == candidate) {
245 hits++;
246 } else {
247 candidate = key;
248 hits = 1;
249 }
250 if (held >= 0 && key != held) {
251 if (++misses >= MISSES) held = -1;
252 } else {
253 misses = 0;
254 }
255 if (candidate >= 0 && hits >= HITS && candidate != held) {
256 held = candidate;
257 misses = 0;
258 bool echo = held == last_key && level < ECHO_LEVEL * last_level;
259 if (!echo) {
260 if (!carrier) startOfCarrier();
261 ndata++;
262 if (p_print != nullptr) p_print->write((uint8_t)currentKey());
263 last_key = held;
264 last_level = 0;
265 }
266 }
267 // the level of the reported key
268 if (held >= 0 && held == last_key && key == held && level > last_level)
270 if (held >= 0) idle_samples = 0;
271 }
272
274 carrier = true;
275 ndata = 0;
276 idle_samples = 0;
277 status("### CARRIER dtmf ###\n");
278 }
279
281 carrier = false;
282 status("\n### NOCARRIER ndata=%u ###\n", ndata);
283 if (cfg.rx_one) is_finished = true;
284 }
285};
286
287} // namespace tinymodem
void addSample(float x, bool add_noise)
Definition DTMFDecoder.h:189
int last_key
Definition DTMFDecoder.h:155
bool is_finished
Definition DTMFDecoder.h:138
uint32_t noise_seed
Definition DTMFDecoder.h:140
static constexpr int TONES
Definition DTMFDecoder.h:119
float twist
Definition DTMFDecoder.h:146
bool begin(DTMFConfig config)
Starts the processing with the indicated configuration.
Definition DTMFDecoder.h:55
void flush() override
Processes the remaining samples (e.g. at the end of the input).
Definition DTMFDecoder.h:87
float noise()
uniform random value in the range -0.5 to 0.5
Definition DTMFDecoder.h:184
audio_tools::AudioInfo audioInfo() override
Definition DTMFDecoder.h:50
static constexpr float WINDOW_SEC
Definition DTMFDecoder.h:121
static constexpr int MISSES
Definition DTMFDecoder.h:123
int candidate
Definition DTMFDecoder.h:150
int held
Definition DTMFDecoder.h:152
int window
Definition DTMFDecoder.h:141
void decide(int key)
Debouncing of the detected keys.
Definition DTMFDecoder.h:243
void resetWindow(Window &w)
Definition DTMFDecoder.h:177
bool is_active
Definition DTMFDecoder.h:137
int detect(Window &w)
Definition DTMFDecoder.h:211
size_t write(const uint8_t *data, size_t len) override
Provide PCM data to the decoder.
Definition DTMFDecoder.h:98
DTMFConfig & config()
The configuration which was provided in begin().
Definition DTMFDecoder.h:114
void resetState()
Definition DTMFDecoder.h:162
static int strongest(const float *amp)
Definition DTMFDecoder.h:235
bool begin() override
Definition DTMFDecoder.h:60
uint32_t timeout
Definition DTMFDecoder.h:145
void end() override
Processes the remaining samples and reports the end of the carrier.
Definition DTMFDecoder.h:80
char currentKey()
The key which is currently received or 0.
Definition DTMFDecoder.h:111
unsigned ndata
Definition DTMFDecoder.h:160
static constexpr float ECHO_LEVEL
the same key with a lower level (relative to the last one) is an echo
Definition DTMFDecoder.h:125
bool isCarrier() override
true from the first key until the end of the transmission
Definition DTMFDecoder.h:105
void startOfCarrier()
Definition DTMFDecoder.h:273
Window windows[WINDOWS]
Definition DTMFDecoder.h:144
int step
Definition DTMFDecoder.h:142
float group_ratio
Definition DTMFDecoder.h:147
static constexpr int HITS
Definition DTMFDecoder.h:122
PcmInput input
Definition DTMFDecoder.h:139
int misses
Definition DTMFDecoder.h:153
bool isFinished() override
true if the decoding was stopped (rx_one)
Definition DTMFDecoder.h:108
int hits
Definition DTMFDecoder.h:151
uint32_t idle_samples
Definition DTMFDecoder.h:159
static constexpr int WINDOWS
Definition DTMFDecoder.h:120
void setAudioInfo(audio_tools::AudioInfo info) override
Defines the format of the input audio data.
Definition DTMFDecoder.h:40
bool carrier
Definition DTMFDecoder.h:158
DTMFConfig defaultConfig()
Provides a default configuration.
Definition DTMFDecoder.h:37
DTMFConfig cfg
Definition DTMFDecoder.h:136
float level
Definition DTMFDecoder.h:157
DTMFDecoder(Print &out)
Definition DTMFDecoder.h:34
float last_level
Definition DTMFDecoder.h:156
static constexpr float ENERGY_RATIO
part of the signal energy which must be in the two tones
Definition DTMFDecoder.h:127
void endOfCarrier()
Definition DTMFDecoder.h:280
float coeff[TONES]
Definition DTMFDecoder.h:143
static const float * rows()
row frequencies in Hz
Definition DTMF.h:87
static char key(int row, int column)
Character of the key in the indicated row and column.
Definition DTMF.h:99
static const float * columns()
column frequencies in Hz
Definition DTMF.h:93
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
TinyModem: software audio modems and packets with error correction.
Definition AX25.h:11
Configuration of the DTMF (touch tone) modem (DTMFEncoder, DTMFDecoder): each of the characters 0-9,...
Definition DTMF.h:20
Goertzel filters of one window.
Definition DTMFDecoder.h:130
float s2[TONES]
Definition DTMFDecoder.h:131
float s1[TONES]
Definition DTMFDecoder.h:131
int count
Definition DTMFDecoder.h:133
float power
Definition DTMFDecoder.h:132