Skip to content

Commit 56147ba

Browse files
committed
GoertzelStream: corrections
1 parent 2f8833f commit 56147ba

File tree

2 files changed

+159
-34
lines changed

2 files changed

+159
-34
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Example for DTMF detection using Goertzel algorithm
2+
// Uses AudioKit and I2S microphones as input
3+
4+
#include "AudioTools.h"
5+
#include "AudioTools/AudioLibs/AudioBoardStream.h"
6+
7+
AudioInfo info(8000, 1, 16); // 8kHz, mono, 16 bits
8+
AudioBoardStream kit(AudioKitEs8388V1); // Access I2S as stream
9+
GoertzelStream goerzel;
10+
StreamCopy copier(goerzel, kit); // copy kit to georzel
11+
12+
// represent DTMF keys
13+
class DTMF {
14+
public:
15+
enum Dimension { Row, Col };
16+
DTMF() = default;
17+
DTMF(Dimension d, int i) {
18+
if (d == Row)
19+
row = i;
20+
else
21+
col = i;
22+
}
23+
void clear() {
24+
row = -1;
25+
col = -1;
26+
}
27+
char getChar() {
28+
if (row == -1 || col == -1) return '?';
29+
return keys[row][col];
30+
}
31+
int row = -1;
32+
int col = -1;
33+
const char* keys[4] = {"123A", "456B", "789C", "*0#D"};
34+
} actual_dtmf;
35+
36+
// combine row and col information
37+
void GoezelCallback(float frequency, float magnitude, void* ref) {
38+
DTMF* dtmf = (DTMF*)ref;
39+
LOGW("Time: %lu - Hz: %f Mag: %f", millis(), frequency, magnitude);
40+
// we get either row or col information
41+
if (dtmf->row != -1) {
42+
actual_dtmf.row = dtmf->row;
43+
} else {
44+
actual_dtmf.col = dtmf->col;
45+
46+
// print detected key
47+
Serial.println(actual_dtmf.getChar());
48+
actual_dtmf.clear();
49+
}
50+
}
51+
52+
void setup() {
53+
Serial.begin(115200);
54+
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Info);
55+
56+
// start audio input from microphones
57+
auto cfg = kit.defaultConfig(RX_MODE);
58+
cfg.copyFrom(info);
59+
cfg.sd_active = false;
60+
cfg.input_device = ADC_INPUT_LINE2;
61+
kit.begin(cfg);
62+
63+
// lower frequencies - with keys
64+
goerzel.addFrequency(697, new DTMF(DTMF::Row, 0));
65+
goerzel.addFrequency(770, new DTMF(DTMF::Row, 1));
66+
goerzel.addFrequency(853, new DTMF(DTMF::Row, 2));
67+
goerzel.addFrequency(943, new DTMF(DTMF::Row, 3));
68+
// higher frequencies with idx
69+
goerzel.addFrequency(1209, new DTMF(DTMF::Col, 0));
70+
goerzel.addFrequency(1336, new DTMF(DTMF::Col, 1));
71+
goerzel.addFrequency(1477, new DTMF(DTMF::Col, 2));
72+
goerzel.addFrequency(1633, new DTMF(DTMF::Col, 3));
73+
// define callback
74+
goerzel.setFrequencyDetectionCallback(GoezelCallback);
75+
76+
// start goerzel
77+
auto gcfg = goerzel.defaultConfig();
78+
gcfg.copyFrom(info);
79+
gcfg.threshold = 0.5;
80+
gcfg.volume = 4.5;
81+
goerzel.begin(gcfg);
82+
}
83+
84+
void loop() { copier.copy(); }

src/AudioTools/CoreAudio/GoerzelStream.h

Lines changed: 75 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class GoertzelDetector {
7777
this->config = config;
7878
this->sample_count = 0;
7979

80-
if (config.target_frequency==0.0f){
80+
if (config.target_frequency == 0.0f) {
8181
return false;
8282
}
8383

@@ -110,9 +110,9 @@ class GoertzelDetector {
110110
config.sample_rate);
111111
float imag =
112112
s2 * sin(2.0f * M_PI * config.target_frequency / config.sample_rate);
113-
magnitude_squared = real * real + imag * imag;
113+
magnitude_squared = (real * real) + (imag * imag);
114114
magnitude = sqrt(magnitude_squared);
115-
115+
116116
// Reset for next block
117117
reset();
118118
return true;
@@ -146,15 +146,14 @@ class GoertzelDetector {
146146
* @return True if magnitude is above configured threshold
147147
*/
148148
bool isDetected() const { return isDetected(config.threshold); }
149-
149+
150150
/**
151151
* @brief Reset the detector state
152152
*/
153153
void reset() {
154154
s1 = 0.0f;
155155
s2 = 0.0f;
156156
sample_count = 0;
157-
magnitude = 0.0f;
158157
magnitude_squared = 0.0f;
159158
}
160159

@@ -173,14 +172,14 @@ class GoertzelDetector {
173172
*/
174173
const GoertzelConfig& getConfig() const { return config; }
175174

176-
void setReference(void* ref){ this->reference = ref; }
175+
void setReference(void* ref) { this->reference = ref; }
177176

178-
void *getReference(){ return reference; }
177+
void* getReference() { return reference; }
179178

180179
protected:
181180
GoertzelConfig config;
182181
float coeff = 0.0f;
183-
void *reference = nullptr;
182+
void* reference = nullptr;
184183

185184
// State variables
186185
float s1 = 0.0f;
@@ -193,10 +192,13 @@ class GoertzelDetector {
193192
};
194193

195194
/**
196-
* @brief AudioStream-based multi-frequency Goertzel detector for real-time audio analysis.
195+
* @brief AudioStream-based multi-frequency Goertzel detector for real-time
196+
* audio analysis.
197197
*
198-
* GoertzelStream enables efficient detection of one or more target frequencies in a continuous audio stream.
199-
* It acts as a transparent filter: audio data flows through unchanged, while the class analyzes the signal for specified tones.
198+
* GoertzelStream enables efficient detection of one or more target frequencies
199+
* in a continuous audio stream. It acts as a transparent filter: audio data
200+
* flows through unchanged, while the class analyzes the signal for specified
201+
* tones.
200202
*
201203
* Key Features:
202204
* - Detects multiple frequencies simultaneously (DTMF, tone detection, etc.)
@@ -208,10 +210,12 @@ class GoertzelDetector {
208210
* - Configurable detection parameters (block size, threshold, volume, etc.)
209211
*
210212
* Usage:
211-
* 1. Configure the stream with GoertzelConfig or AudioInfo (sample rate, channels, etc.)
213+
* 1. Configure the stream with GoertzelConfig or AudioInfo (sample rate,
214+
* channels, etc.)
212215
* 2. Add one or more target frequencies using addFrequency()
213216
* 3. Optionally set a detection callback with setFrequencyDetectionCallback()
214-
* 4. Use write() or readBytes() to process audio data; detection runs automatically
217+
* 4. Use write() or readBytes() to process audio data; detection runs
218+
* automatically
215219
*
216220
* Supported sample formats:
217221
* - 8-bit: unsigned (0-255), internally converted to signed (-128 to 127)
@@ -226,7 +230,12 @@ class GoertzelDetector {
226230
*/
227231
class GoertzelStream : public AudioStream {
228232
public:
233+
// Default Constructor with no output or input
229234
GoertzelStream() = default;
235+
GoertzelStream(Print& out) { setOutput(out); }
236+
GoertzelStream(AudioOutput& out) { setOutput(out); }
237+
GoertzelStream(Stream& io) { setStream(io); };
238+
GoertzelStream(AudioStream& io) { setStream(io); };
230239

231240
/**
232241
* @brief Set audio format and initialize detector array
@@ -245,6 +254,20 @@ class GoertzelStream : public AudioStream {
245254
begin();
246255
}
247256

257+
/**
258+
* @brief Returns a default GoertzelConfig instance with standard parameters
259+
*
260+
* This utility method provides a convenient way to obtain a default
261+
* configuration for the Goertzel algorithm. The returned config can be
262+
* customized before use.
263+
*
264+
* @return GoertzelConfig with default values
265+
*/
266+
GoertzelConfig defaultConfig() {
267+
GoertzelConfig result;
268+
return result;
269+
}
270+
248271
/**
249272
* @brief Initialize with GoertzelConfig
250273
*
@@ -284,11 +307,12 @@ class GoertzelStream : public AudioStream {
284307
GoertzelConfig cfg = default_config;
285308
cfg.target_frequency = freq;
286309
GoertzelDetector detector;
287-
if (i++ < references.size()){
310+
if (i < references.size()) {
288311
detector.setReference(references[i]);
289312
}
290313
detector.begin(cfg);
291314
detectors.push_back(detector);
315+
i++;
292316
}
293317
sample_no = 0;
294318
return true;
@@ -308,9 +332,21 @@ class GoertzelStream : public AudioStream {
308332
p_print = &in;
309333
}
310334

335+
/// Defines/Changes the input & output
336+
void setStream(AudioStream& io) {
337+
setStream((Stream&)io);
338+
addNotifyAudioChange(io);
339+
}
340+
311341
/// Defines/Changes the output target
312342
void setOutput(Print& out) { p_print = &out; }
313343

344+
/// Defines/Changes the output target
345+
void setOutput(AudioOutput& out) {
346+
setOutput((Print&)out);
347+
addNotifyAudioChange(out);
348+
}
349+
314350
/**
315351
* @brief Set detection callback function for channel-aware frequency
316352
* detection
@@ -322,8 +358,9 @@ class GoertzelStream : public AudioStream {
322358
* @param callback Function to call when frequency is detected, includes
323359
* channel info
324360
*/
325-
void setFrequencyDetectionCallback(void (*callback)(
326-
int channel, float frequency, float magnitude, void* ref)) {
361+
void setFrequencyDetectionCallback(void (*callback)(float frequency,
362+
float magnitude,
363+
void* ref)) {
327364
frequency_detection_callback = callback;
328365
}
329366

@@ -340,8 +377,6 @@ class GoertzelStream : public AudioStream {
340377
* @return Number of bytes written to output stream
341378
*/
342379
size_t write(const uint8_t* data, size_t len) override {
343-
if (p_print == nullptr) return 0;
344-
345380
// Process samples for detection
346381
processSamples(data, len);
347382

@@ -408,40 +443,43 @@ class GoertzelStream : public AudioStream {
408443
GoertzelDetector& getDetector(int no) { return detectors[no]; }
409444

410445
/**
411-
* @brief Add a frequency to the detection list
446+
* @brief Add a frequency to the detection list
412447
*
413448
* @param freq Frequency in Hz to add to the detection list
414449
*/
415450
void addFrequency(float freq) { frequencies.push_back(freq); }
416451

417452
/**
418-
* @brief Add a frequency to the detection list with a custom reference pointer
453+
* @brief Add a frequency to the detection list with a custom reference
454+
* pointer
419455
*
420-
* This method allows you to associate a user-defined reference (context pointer)
421-
* with a specific frequency. The reference will be passed to the detection callback
422-
* when this frequency is detected, enabling per-frequency context handling.
456+
* This method allows you to associate a user-defined reference (context
457+
* pointer) with a specific frequency. The reference will be passed to the
458+
* detection callback when this frequency is detected, enabling per-frequency
459+
* context handling.
423460
*
424461
* @param freq Frequency in Hz to add to the detection list
425462
* @param ref Pointer to user-defined context object for this frequency
426463
*/
427-
void addFrequency(float freq, void* ref) {
428-
frequencies.push_back(freq);
464+
void addFrequency(float freq, void* ref) {
465+
frequencies.push_back(freq);
429466
references.push_back(ref);
430467
}
431468

432469
protected:
433470
// Core detection components
434-
Vector<GoertzelDetector> detectors; ///< One detector per frequency in frequencies
471+
Vector<GoertzelDetector>
472+
detectors; ///< One detector per frequency in frequencies
435473
Vector<float> frequencies; ///< List of frequencies to detect
436-
Vector<void*> references; ///< List of frequencies to detect
474+
Vector<void*> references; ///< List of frequencies to detect
437475
GoertzelConfig default_config; ///< Current algorithm configuration
438476
// Stream I/O components
439477
Stream* p_stream = nullptr; ///< Input stream for reading audio data
440478
Print* p_print = nullptr; ///< Output stream for writing audio data
441479

442480
// Callback system
443-
void (*frequency_detection_callback)(int channel, float frequency,
444-
float magnitude, void* ref) =
481+
void (*frequency_detection_callback)(float frequency, float magnitude,
482+
void* ref) =
445483
nullptr; ///< User callback for detection events
446484
void* ref = this; ///< User-defined reference for callback context
447485

@@ -452,13 +490,15 @@ class GoertzelStream : public AudioStream {
452490
*/
453491
void checkDetection(GoertzelDetector& detector) {
454492
float magnitude = detector.getMagnitude();
493+
if (magnitude > 0.0f)
494+
LOGD("frequency: %f / magnitude: %f / threshold: %f", detector.getTargetFrequency(), magnitude, default_config.threshold);
495+
455496
if (magnitude > default_config.threshold) {
456497
float frequency = detector.getTargetFrequency();
457-
void *reference = detector.getReference();
458-
if (reference==nullptr) reference = ref;
498+
void* reference = detector.getReference();
499+
if (reference == nullptr) reference = ref;
459500
if (frequency_detection_callback != nullptr) {
460-
frequency_detection_callback(default_config.channel, frequency,
461-
magnitude, reference);
501+
frequency_detection_callback(frequency, magnitude, reference);
462502
}
463503
}
464504
}
@@ -487,8 +527,9 @@ class GoertzelStream : public AudioStream {
487527
if (sample_no % channels == default_config.channel) {
488528
float normalized = clip(NumberConverter::toFloatT<T>(samples[i]) *
489529
default_config.volume);
530+
LOGD("sample: %f", normalized);
490531
// process all frequencies
491-
for (auto &detector : detectors) {
532+
for (auto& detector : detectors) {
492533
if (detector.processSample(normalized)) {
493534
checkDetection(detector);
494535
}

0 commit comments

Comments
 (0)