TinyModem
Header-only software audio modem library
Loading...
Searching...
No Matches
FSKDemodulator.h
Go to the documentation of this file.
1#pragma once
2
3#include <float.h>
4#include <math.h>
5#include <string.h>
6
7#include "AudioTools.h"
8#include "AudioTools/CoreAudio/Analysis/GoerzelStream.h"
9
10namespace tinymodem {
11
20 public:
22 bool begin(float sample_rate, float f_mark, float f_space, float filter_bw) {
23 this->sample_rate = sample_rate;
24 this->f_mark = f_mark;
25 this->f_space = f_space;
26 band_width = filter_bw;
27 float half_bw = band_width / 2.0f;
28 fftsize = (sample_rate + half_bw) / band_width;
29 nbands = fftsize / 2 + 1;
30 b_mark = (f_mark + half_bw) / band_width;
31 b_space = (f_space + half_bw) / band_width;
32 if (fftsize == 0 || b_mark >= nbands || b_space >= nbands) return false;
34 return true;
35 }
36
39 float findFrame(const float* samples, unsigned frame_nsamples,
40 unsigned try_first_sample, unsigned try_max_nsamples,
41 unsigned try_step_nsamples, float try_confidence_search_limit,
42 const char* expect_bits_string, uint64_t* bits_outp,
43 float* ampl_outp, unsigned* frame_start_outp) {
44 int expect_n_bits = strlen(expect_bits_string);
45 if (expect_n_bits == 0 || expect_n_bits > MAX_BITS) return 0.0f;
46 float samples_per_bit = (float)frame_nsamples / expect_n_bits;
47
48 unsigned best_t = 0;
49 float best_c = 0.0f, best_a = 0.0f;
50 uint64_t best_bits = 0;
51
52 // Scan the frame positions starting with try_first_sample, alternating
53 // between a step above that, a step below that, above, below, and so on,
54 // until we've scanned the whole try_max_nsamples range.
55 for (int j = 0;; j++) {
56 int up = (j % 2) ? 1 : -1;
57 int t = try_first_sample + up * ((j + 1) / 2) * try_step_nsamples;
58 if (t >= (int)try_max_nsamples) break;
59 if (t < 0) continue;
60
61 float ampl_out = 0.0f;
62 uint64_t bits_out = 0;
63 float c = frameAnalyze(samples + t, samples_per_bit, expect_n_bits,
64 expect_bits_string, &bits_out, &ampl_out);
65 if (best_c < c) {
66 best_t = t;
67 best_c = c;
68 best_a = ampl_out;
69 best_bits = bits_out;
70 // quit searching if confidence > try_confidence_search_limit
71 if (best_c >= try_confidence_search_limit) break;
72 }
73 }
74 *bits_outp = best_bits;
75 *ampl_outp = best_a;
76 *frame_start_outp = best_t;
77 return best_c;
78 }
79
82 int detectCarrier(const float* samples, unsigned nsamples,
83 float min_mag_threshold) {
84 float magscalar = 1.0f / ((float)nsamples / 2.0f);
85 float max_mag = 0.0f;
86 int max_mag_band = -1;
87 // start detection at the first non-DC band
88 for (unsigned i = 1; i < nbands; i++) {
89 float mag = bandMagnitude(samples, nsamples, i) * magscalar;
90 if (mag < min_mag_threshold) continue;
91 if (max_mag < mag) {
92 max_mag = mag;
93 max_mag_band = i;
94 }
95 }
96 return max_mag_band;
97 }
98
100 void setTonesByBandShift(unsigned b_mark, int b_shift) {
101 this->b_mark = b_mark;
102 this->b_space = b_mark + b_shift;
103 f_mark = this->b_mark * band_width;
104 f_space = this->b_space * band_width;
106 }
107
108 unsigned fftSize() { return fftsize; }
109 unsigned bandCount() { return nbands; }
110 float bandWidth() { return band_width; }
111 unsigned markBand() { return b_mark; }
112 unsigned spaceBand() { return b_space; }
113
114 protected:
115 // a frame has max 64 bits + the previous stop bit
116 static constexpr int MAX_BITS = 72;
117 float sample_rate = 0.0f;
118 float f_mark = 0.0f;
119 float f_space = 0.0f;
120 float band_width = 0.0f;
121 unsigned fftsize = 0;
122 unsigned nbands = 0;
123 unsigned b_mark = 0;
124 unsigned b_space = 0;
125 audio_tools::GoertzelDetector mark_detector;
126 audio_tools::GoertzelDetector space_detector;
127 unsigned detector_nsamples = 0;
128
130 float bandFrequency(unsigned band) {
131 return (float)band * sample_rate / (float)fftsize;
132 }
133
134 bool setupDetector(audio_tools::GoertzelDetector& detector, unsigned band,
135 unsigned nsamples) {
136 audio_tools::GoertzelConfig gcfg;
137 gcfg.sample_rate = sample_rate;
138 gcfg.channels = 1;
139 gcfg.target_frequency = bandFrequency(band);
140 gcfg.block_size = nsamples;
141 return detector.begin(gcfg);
142 }
143
145 float magnitude(audio_tools::GoertzelDetector& detector,
146 const float* samples, unsigned nsamples) {
147 detector.reset();
148 for (unsigned j = 0; j < nsamples; j++) detector.processSample(samples[j]);
149 return detector.getMagnitude();
150 }
151
152 float bandMagnitude(const float* samples, unsigned nsamples, unsigned band) {
153 audio_tools::GoertzelDetector detector;
154 if (!setupDetector(detector, band, nsamples)) return 0.0f;
155 return magnitude(detector, samples, nsamples);
156 }
157
158 void bitAnalyze(const float* samples, unsigned bit_nsamples,
159 unsigned* bit_outp, float* bit_signal_mag_outp,
160 float* bit_noise_mag_outp) {
161 if (detector_nsamples != bit_nsamples) {
162 setupDetector(mark_detector, b_mark, bit_nsamples);
163 setupDetector(space_detector, b_space, bit_nsamples);
164 detector_nsamples = bit_nsamples;
165 }
166 float magscalar = 2.0f / (float)bit_nsamples;
167 float mag_mark = magnitude(mark_detector, samples, bit_nsamples) * magscalar;
168 float mag_space =
169 magnitude(space_detector, samples, bit_nsamples) * magscalar;
170 // mark==1, space==0
171 if (mag_mark > mag_space) {
172 *bit_outp = 1;
173 *bit_signal_mag_outp = mag_mark;
174 *bit_noise_mag_outp = mag_space;
175 } else {
176 *bit_outp = 0;
177 *bit_signal_mag_outp = mag_space;
178 *bit_noise_mag_outp = mag_mark;
179 }
180 }
181
183 float frameAnalyze(const float* samples, float samples_per_bit, int n_bits,
184 const char* expect_bits, uint64_t* bits_outp,
185 float* ampl_outp) {
186 unsigned bit_nsamples = (float)(samples_per_bit + 0.5f);
187 unsigned bit_values[MAX_BITS];
188 float bit_sig_mags[MAX_BITS];
189 float bit_noise_mags[MAX_BITS];
190
191 // pass #1 - process and check only the "required" (1/0) expect_bits
192 for (int bitnum = 0; bitnum < n_bits; bitnum++) {
193 if (expect_bits[bitnum] == 'd') continue;
194 unsigned bit_begin_sample = (float)(samples_per_bit * bitnum + 0.5f);
195 bitAnalyze(samples + bit_begin_sample, bit_nsamples, &bit_values[bitnum],
196 &bit_sig_mags[bitnum], &bit_noise_mags[bitnum]);
197 if ((unsigned)(expect_bits[bitnum] - '0') != bit_values[bitnum])
198 return 0.0f; // does not match expected; abort frame analysis.
199 }
200
201 // pass #2 - process only the dontcare ('d') expect_bits
202 for (int bitnum = 0; bitnum < n_bits; bitnum++) {
203 if (expect_bits[bitnum] != 'd') continue;
204 unsigned bit_begin_sample = (float)(samples_per_bit * bitnum + 0.5f);
205 bitAnalyze(samples + bit_begin_sample, bit_nsamples, &bit_values[bitnum],
206 &bit_sig_mags[bitnum], &bit_noise_mags[bitnum]);
207 }
208
209 float total_bit_sig = 0.0f, total_bit_noise = 0.0f;
210 float avg_mark_sig = 0.0f, avg_space_sig = 0.0f;
211 unsigned n_mark = 0, n_space = 0;
212 for (int bitnum = 0; bitnum < n_bits; bitnum++) {
213 // Deal with floating point data type quantization noise: if
214 // total_bit_noise <= FLT_EPSILON, assume it to be 0.0 (snr==inf).
215 total_bit_sig += bit_sig_mags[bitnum];
216 if (bit_noise_mags[bitnum] > FLT_EPSILON)
217 total_bit_noise += bit_noise_mags[bitnum];
218 if (bit_values[bitnum] == 1) {
219 avg_mark_sig += bit_sig_mags[bitnum];
220 n_mark++;
221 } else {
222 avg_space_sig += bit_sig_mags[bitnum];
223 n_space++;
224 }
225 }
226
227 // Compute the "frame SNR"
228 float snr = total_bit_sig / total_bit_noise;
229 float avg_bit_sig = total_bit_sig / n_bits;
230 if (n_mark) avg_mark_sig /= n_mark;
231 if (n_space) avg_space_sig /= n_space;
232
233 // Compute average "divergence": bit_mag_divergence / other_bits_mag
234 float divergence = 0.0f;
235 for (int bitnum = 0; bitnum < n_bits; bitnum++) {
236 float avg_bit_sig_other = bit_values[bitnum] ? avg_mark_sig : avg_space_sig;
237 divergence +=
238 fabsf(bit_sig_mags[bitnum] - avg_bit_sig_other) / avg_bit_sig_other;
239 }
240 divergence *= 2;
241 divergence /= n_bits;
242
243 // Frame confidence is the frame ( SNR * consistency )
244 float confidence = snr * (1.0f - divergence);
245 *ampl_outp = avg_bit_sig;
246
247 // least significant bit first
248 *bits_outp = 0;
249 for (int bitnum = 0; bitnum < n_bits && bitnum < 64; bitnum++)
250 *bits_outp |= (uint64_t)bit_values[bitnum] << bitnum;
251
252 return confidence;
253 }
254};
255
256} // namespace tinymodem
FSK bit and frame analysis (port of minimodem's fsk.c). Instead of a full FFT we use the Goertzel alg...
Definition FSKDemodulator.h:19
unsigned b_space
Definition FSKDemodulator.h:124
float f_space
Definition FSKDemodulator.h:119
unsigned nbands
Definition FSKDemodulator.h:122
float frameAnalyze(const float *samples, float samples_per_bit, int n_bits, const char *expect_bits, uint64_t *bits_outp, float *ampl_outp)
returns confidence value [0.0 to INFINITY]
Definition FSKDemodulator.h:183
void bitAnalyze(const float *samples, unsigned bit_nsamples, unsigned *bit_outp, float *bit_signal_mag_outp, float *bit_noise_mag_outp)
Definition FSKDemodulator.h:158
float bandMagnitude(const float *samples, unsigned nsamples, unsigned band)
Definition FSKDemodulator.h:152
float bandWidth()
Definition FSKDemodulator.h:110
unsigned spaceBand()
Definition FSKDemodulator.h:112
float bandFrequency(unsigned band)
center frequency of the FFT bin which corresponds to the band
Definition FSKDemodulator.h:130
unsigned fftSize()
Definition FSKDemodulator.h:108
unsigned bandCount()
Definition FSKDemodulator.h:109
bool setupDetector(audio_tools::GoertzelDetector &detector, unsigned band, unsigned nsamples)
Definition FSKDemodulator.h:134
bool begin(float sample_rate, float f_mark, float f_space, float filter_bw)
Defines the tones and the filter bandwidth: returns false if invalid.
Definition FSKDemodulator.h:22
int detectCarrier(const float *samples, unsigned nsamples, float min_mag_threshold)
Definition FSKDemodulator.h:82
unsigned markBand()
Definition FSKDemodulator.h:111
unsigned detector_nsamples
Definition FSKDemodulator.h:127
float band_width
Definition FSKDemodulator.h:120
unsigned fftsize
Definition FSKDemodulator.h:121
static constexpr int MAX_BITS
Definition FSKDemodulator.h:116
float magnitude(audio_tools::GoertzelDetector &detector, const float *samples, unsigned nsamples)
unscaled magnitude of the band over n samples
Definition FSKDemodulator.h:145
audio_tools::GoertzelDetector mark_detector
Definition FSKDemodulator.h:125
float sample_rate
Definition FSKDemodulator.h:117
float findFrame(const float *samples, unsigned frame_nsamples, unsigned try_first_sample, unsigned try_max_nsamples, unsigned try_step_nsamples, float try_confidence_search_limit, const char *expect_bits_string, uint64_t *bits_outp, float *ampl_outp, unsigned *frame_start_outp)
Definition FSKDemodulator.h:39
void setTonesByBandShift(unsigned b_mark, int b_shift)
Redefines the mark and space band.
Definition FSKDemodulator.h:100
unsigned b_mark
Definition FSKDemodulator.h:123
float f_mark
Definition FSKDemodulator.h:118
audio_tools::GoertzelDetector space_detector
Definition FSKDemodulator.h:126
TinyModem: software audio modems and packets with error correction.
Definition AX25.h:11