arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Macros Modules Pages
GoerzelStream.h
1#pragma once
2#include <math.h>
3
4#include "AudioStreams.h"
5
12namespace audio_tools {
13
25struct GoertzelConfig : public AudioInfo {
27 float target_frequency = 440.0f;
29 int block_size = 205;
31 float threshold = 0.5f;
33 float volume = 1.0f;
34
35 GoertzelConfig() = default;
37 GoertzelConfig(const AudioInfo& info) : AudioInfo(info) {
38 target_frequency = 440.0f;
39 block_size = 205;
40 threshold = 0.5f;
41 }
42};
43
60 public:
61 GoertzelDetector() = default;
62
67 void begin(const GoertzelConfig& config) {
68 this->config = config;
69 this->sample_count = 0;
70
71 // Calculate the coefficient k and omega
72 float omega = (2.0f * M_PI * config.target_frequency) / config.sample_rate;
73 coeff = 2.0f * cos(omega);
74
75 // Reset state variables
76 reset();
77 }
78
84 bool processSample(float sample) {
85 // Goertzel algorithm core computation
86 float s0 = sample + coeff * s1 - s2;
87 s2 = s1;
88 s1 = s0;
89
90 sample_count++;
91
92 // Check if we've processed a complete block
93 if (sample_count >= config.block_size) {
94 // Calculate magnitude squared
95 float real = s1 - s2 * cos(2.0f * M_PI * config.target_frequency /
96 config.sample_rate);
97 float imag =
98 s2 * sin(2.0f * M_PI * config.target_frequency / config.sample_rate);
99 magnitude_squared = real * real + imag * imag;
100 magnitude = sqrt(magnitude_squared);
101
102 // Reset for next block
103 reset();
104 return true;
105 }
106
107 return false;
108 }
109
114 float getMagnitude() const { return magnitude; }
115
121 float getMagnitudeSquared() const { return magnitude_squared; }
122
128 bool isDetected(float threshold) const { return magnitude > threshold; }
129
133 void reset() {
134 s1 = 0.0f;
135 s2 = 0.0f;
136 sample_count = 0;
137 magnitude = 0.0f;
138 magnitude_squared = 0.0f;
139 }
140
144 float getTargetFrequency() const { return config.target_frequency; }
145
149 int getBlockSize() const { return config.block_size; }
150
154 const GoertzelConfig& getConfig() const { return config; }
155
156 private:
157 GoertzelConfig config;
158 float coeff = 0.0f;
159
160 // State variables
161 float s1 = 0.0f;
162 float s2 = 0.0f;
163 int sample_count = 0;
164
165 // Results
166 float magnitude = 0.0f;
167 float magnitude_squared = 0.0f;
168};
169
193 public:
194 GoertzelStream() = default;
195
205 void setAudioInfo(AudioInfo info) override {
208 // Initialize detectors for each channel
209 detectors.resize(info.channels);
210 }
211
222 bool begin(const GoertzelConfig& config) {
223 this->single_config = config;
224 // Set up audio info from config
225 setAudioInfo(config);
226 return begin();
227 }
228
238 bool begin() {
239 for (int i = 0; i < info.channels; i++) {
240 detectors[i].begin(single_config);
241 }
242 return info.channels > 0;
243 }
244
246 void setStream(Stream& in) {
247 p_stream = &in;
248 p_print = &in;
249 }
250
252 void setOutput(Print& out) { p_print = &out; }
253
263 void setChannelDetectionCallback(void (*callback)(
264 int channel, float frequency, float magnitude, void* ref)) {
266 }
267
280 size_t write(const uint8_t* data, size_t len) override {
281 if (p_print == nullptr) return 0;
282
283 // Process samples for detection
284 processSamples(data, len);
285
287 if (p_print == nullptr) return len;
288
289 // Pass data through unchanged
290 return p_print->write(data, len);
291 }
292
305 size_t readBytes(uint8_t* data, size_t len) override {
306 if (p_stream == nullptr) return 0;
307
308 size_t result = p_stream->readBytes(data, len);
309
310 // Process samples for detection
311 processSamples(data, result);
312
313 return result;
314 }
315
326 float getCurrentMagnitude(int channel = 0) {
327 if (channel >= 0 && channel < detectors.size()) {
328 return detectors[channel].getMagnitude();
329 }
330 return 0.0f;
331 }
332
342 bool isFrequencyDetected(int channel = 0) {
343 if (channel >= 0 && channel < detectors.size()) {
344 return detectors[channel].isDetected(single_config.threshold);
345 }
346 return false;
347 }
348
357 const GoertzelConfig& getConfig() const { return single_config; }
358
368 void setReference(void* ref) { this->ref = ref; }
369
370 protected:
371 // Core detection components
374
375 // Stream I/O components
376 Stream* p_stream = nullptr;
377 Print* p_print = nullptr;
378
379 // Callback system
380 void (*channel_detection_callback)(int channel, float frequency,
381 float magnitude, void* ref) = nullptr;
382 void* ref = this;
383
393 void checkDetection(int channel) {
394 if (channel >= 0 && channel < detectors.size()) {
395 float magnitude = detectors[channel].getMagnitude();
396 if (magnitude > single_config.threshold) {
397 float frequency = detectors[channel].getTargetFrequency();
399 channel_detection_callback(channel, frequency, magnitude, ref);
400 }
401 }
402 }
403 }
404
418 template <typename T>
419 void processSamplesOfType(const uint8_t* data, size_t data_len,
420 int channels) {
421 const T* samples = reinterpret_cast<const T*>(data);
422 size_t num_samples = data_len / sizeof(T);
423
424 for (size_t i = 0; i < num_samples; i++) {
425 // Convert to normalized float and apply volume scaling
426 float normalized = clip(NumberConverter::toFloatT<T>(samples[i]) * single_config.volume);
427 // Distribute samples to channels in interleaved format
428 int channel = i % channels;
429 if (detectors[channel].processSample(normalized)) {
430 checkDetection(channel);
431 }
432 }
433 }
434
444 float clip(float value) {
445 // Clip the value to the range [-1.0, 1.0]
446 if (value > 1.0f) return 1.0f;
447 if (value < -1.0f) return -1.0f;
448 return value;
449 }
450
468 void processSamples(const uint8_t* data, size_t data_len) {
469 int channels = single_config.channels;
470
472 case 8:
473 processSamplesOfType<uint8_t>(data, data_len, channels);
474 break;
475 case 16:
476 processSamplesOfType<int16_t>(data, data_len, channels);
477 break;
478 case 24:
479 processSamplesOfType<int24_t>(data, data_len, channels);
480 break;
481 case 32:
482 processSamplesOfType<int32_t>(data, data_len, channels);
483 break;
484 default:
485 LOGE("Unsupported bits_per_sample: %d", single_config.bits_per_sample);
486 break;
487 }
488 }
489};
490
491} // namespace audio_tools
Base class for all Audio Streams. It support the boolean operator to test if the object is ready with...
Definition BaseStream.h:122
virtual void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition BaseStream.h:130
Single-frequency Goertzel algorithm implementation for tone detection. The Goertzel algorithm is an e...
Definition GoerzelStream.h:59
int getBlockSize() const
Get the block size.
Definition GoerzelStream.h:149
float getMagnitudeSquared() const
Get the squared magnitude (more efficient if you don't need the actual magnitude)
Definition GoerzelStream.h:121
bool processSample(float sample)
Process a single sample.
Definition GoerzelStream.h:84
bool isDetected(float threshold) const
Check if the detected magnitude is above a threshold.
Definition GoerzelStream.h:128
const GoertzelConfig & getConfig() const
Get the current configuration.
Definition GoerzelStream.h:154
void begin(const GoertzelConfig &config)
Initialize the Goertzel detector with configuration.
Definition GoerzelStream.h:67
float getMagnitude() const
Get the magnitude of the detected frequency.
Definition GoerzelStream.h:114
float getTargetFrequency() const
Get the target frequency.
Definition GoerzelStream.h:144
void reset()
Reset the detector state.
Definition GoerzelStream.h:133
AudioStream implementation that processes audio data through Goertzel algorithm. This class acts as a...
Definition GoerzelStream.h:192
bool isFrequencyDetected(int channel=0)
Check if frequency is currently detected.
Definition GoerzelStream.h:342
bool begin(const GoertzelConfig &config)
Initialize with GoertzelConfig.
Definition GoerzelStream.h:222
void * ref
User-defined reference for callback context.
Definition GoerzelStream.h:382
void processSamplesOfType(const uint8_t *data, size_t data_len, int channels)
Template helper to process samples of a specific type.
Definition GoerzelStream.h:419
GoertzelConfig single_config
Current algorithm configuration.
Definition GoerzelStream.h:373
void(* channel_detection_callback)(int channel, float frequency, float magnitude, void *ref)
User callback for detection events.
Definition GoerzelStream.h:380
size_t readBytes(uint8_t *data, size_t len) override
Read data from input stream and process it.
Definition GoerzelStream.h:305
void checkDetection(int channel)
Helper method to check detection and call callback.
Definition GoerzelStream.h:393
void setChannelDetectionCallback(void(*callback)(int channel, float frequency, float magnitude, void *ref))
Set detection callback function for channel-aware frequency detection.
Definition GoerzelStream.h:263
void processSamples(const uint8_t *data, size_t data_len)
Generic sample processing method for both write and readBytes.
Definition GoerzelStream.h:468
bool begin()
Initialize detectors for all channels.
Definition GoerzelStream.h:238
float clip(float value)
Clip audio values to prevent overflow.
Definition GoerzelStream.h:444
size_t write(const uint8_t *data, size_t len) override
Process audio data and pass it through.
Definition GoerzelStream.h:280
Stream * p_stream
Input stream for reading audio data.
Definition GoerzelStream.h:376
const GoertzelConfig & getConfig() const
Get the current configuration.
Definition GoerzelStream.h:357
void setReference(void *ref)
Set reference pointer for callback context.
Definition GoerzelStream.h:368
Vector< GoertzelDetector > detectors
One detector per audio channel.
Definition GoerzelStream.h:372
Print * p_print
Output stream for writing audio data.
Definition GoerzelStream.h:377
void setAudioInfo(AudioInfo info) override
Set audio format and initialize detector array.
Definition GoerzelStream.h:205
float getCurrentMagnitude(int channel=0)
Get the current magnitude for frequency detection.
Definition GoerzelStream.h:326
void setOutput(Print &out)
Defines/Changes the output target.
Definition GoerzelStream.h:252
void setStream(Stream &in)
Defines/Changes the input & output.
Definition GoerzelStream.h:246
Definition NoArduino.h:62
Definition NoArduino.h:142
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:53
void copyFrom(AudioInfo info)
Same as set.
Definition AudioTypes.h:103
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition AudioTypes.h:55
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition AudioTypes.h:57
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition AudioTypes.h:59
Configuration for Goertzel algorithm detectors.
Definition GoerzelStream.h:25
float volume
Volume factor for normalization - scales input samples before processing.
Definition GoerzelStream.h:33
float threshold
Detection threshold for magnitude (normalized samples, typically 0.1 to 1.0)
Definition GoerzelStream.h:31
int block_size
Number of samples to process per block (N) - affects detection latency and accuracy.
Definition GoerzelStream.h:29
GoertzelConfig(const AudioInfo &info)
Copy constructor from AudioInfo.
Definition GoerzelStream.h:37
float target_frequency
Target frequency to detect in Hz (same for all channels)
Definition GoerzelStream.h:27