arduino-audio-tools
Loading...
Searching...
No Matches
GoerzelStream.h
1#pragma once
2
3#include <math.h>
4#include "AudioStreams.h"
5
6#ifndef M_PI
7# define M_PI (3.14159265358979323846f)
8#endif
9
16namespace audio_tools {
17
29struct GoertzelConfig : public AudioInfo {
31 float target_frequency = 440.0f;
33 int block_size = 205;
35 float threshold = 0.5f;
37 float volume = 1.0f;
38
39 GoertzelConfig() = default;
41 GoertzelConfig(const AudioInfo& info) : AudioInfo(info) {
42 target_frequency = 440.0f;
43 block_size = 205;
44 threshold = 0.5f;
45 }
46};
47
64 public:
65 GoertzelDetector() = default;
66
71 void begin(const GoertzelConfig& config) {
72 this->config = config;
73 this->sample_count = 0;
74
75 // Calculate the coefficient k and omega
76 float omega = (2.0f * M_PI * config.target_frequency) / config.sample_rate;
77 coeff = 2.0f * cos(omega);
78
79 // Reset state variables
80 reset();
81 }
82
88 bool processSample(float sample) {
89 // Goertzel algorithm core computation
90 float s0 = sample + coeff * s1 - s2;
91 s2 = s1;
92 s1 = s0;
93
94 sample_count++;
95
96 // Check if we've processed a complete block
97 if (sample_count >= config.block_size) {
98 // Calculate magnitude squared
99 float real = s1 - s2 * cos(2.0f * M_PI * config.target_frequency /
100 config.sample_rate);
101 float imag =
102 s2 * sin(2.0f * M_PI * config.target_frequency / config.sample_rate);
103 magnitude_squared = real * real + imag * imag;
104 magnitude = sqrt(magnitude_squared);
105
106 // Reset for next block
107 reset();
108 return true;
109 }
110
111 return false;
112 }
113
118 float getMagnitude() const { return magnitude; }
119
125 float getMagnitudeSquared() const { return magnitude_squared; }
126
132 bool isDetected(float threshold) const { return magnitude > threshold; }
133
137 void reset() {
138 s1 = 0.0f;
139 s2 = 0.0f;
140 sample_count = 0;
141 magnitude = 0.0f;
142 magnitude_squared = 0.0f;
143 }
144
148 float getTargetFrequency() const { return config.target_frequency; }
149
153 int getBlockSize() const { return config.block_size; }
154
158 const GoertzelConfig& getConfig() const { return config; }
159
160 private:
161 GoertzelConfig config;
162 float coeff = 0.0f;
163
164 // State variables
165 float s1 = 0.0f;
166 float s2 = 0.0f;
167 int sample_count = 0;
168
169 // Results
170 float magnitude = 0.0f;
171 float magnitude_squared = 0.0f;
172};
173
197 public:
198 GoertzelStream() = default;
199
209 void setAudioInfo(AudioInfo info) override {
212 // Initialize detectors for each channel
213 detectors.resize(info.channels);
214 }
215
226 bool begin(const GoertzelConfig& config) {
227 this->single_config = config;
228 // Set up audio info from config
229 setAudioInfo(config);
230 return begin();
231 }
232
242 bool begin() {
243 for (int i = 0; i < info.channels; i++) {
244 detectors[i].begin(single_config);
245 }
246 return info.channels > 0;
247 }
248
250 void setStream(Stream& in) {
251 p_stream = &in;
252 p_print = &in;
253 }
254
256 void setOutput(Print& out) { p_print = &out; }
257
267 void setChannelDetectionCallback(void (*callback)(
268 int channel, float frequency, float magnitude, void* ref)) {
270 }
271
284 size_t write(const uint8_t* data, size_t len) override {
285 if (p_print == nullptr) return 0;
286
287 // Process samples for detection
288 processSamples(data, len);
289
291 if (p_print == nullptr) return len;
292
293 // Pass data through unchanged
294 return p_print->write(data, len);
295 }
296
309 size_t readBytes(uint8_t* data, size_t len) override {
310 if (p_stream == nullptr) return 0;
311
312 size_t result = p_stream->readBytes(data, len);
313
314 // Process samples for detection
315 processSamples(data, result);
316
317 return result;
318 }
319
330 float getCurrentMagnitude(int channel = 0) {
331 if (channel >= 0 && channel < detectors.size()) {
332 return detectors[channel].getMagnitude();
333 }
334 return 0.0f;
335 }
336
346 bool isFrequencyDetected(int channel = 0) {
347 if (channel >= 0 && channel < detectors.size()) {
348 return detectors[channel].isDetected(single_config.threshold);
349 }
350 return false;
351 }
352
361 const GoertzelConfig& getConfig() const { return single_config; }
362
372 void setReference(void* ref) { this->ref = ref; }
373
374 protected:
375 // Core detection components
378
379 // Stream I/O components
380 Stream* p_stream = nullptr;
381 Print* p_print = nullptr;
382
383 // Callback system
384 void (*channel_detection_callback)(int channel, float frequency,
385 float magnitude, void* ref) = nullptr;
386 void* ref = this;
387
397 void checkDetection(int channel) {
398 if (channel >= 0 && channel < detectors.size()) {
399 float magnitude = detectors[channel].getMagnitude();
400 if (magnitude > single_config.threshold) {
401 float frequency = detectors[channel].getTargetFrequency();
403 channel_detection_callback(channel, frequency, magnitude, ref);
404 }
405 }
406 }
407 }
408
422 template <typename T>
423 void processSamplesOfType(const uint8_t* data, size_t data_len,
424 int channels) {
425 const T* samples = reinterpret_cast<const T*>(data);
426 size_t num_samples = data_len / sizeof(T);
427
428 for (size_t i = 0; i < num_samples; i++) {
429 // Convert to normalized float and apply volume scaling
430 float normalized = clip(NumberConverter::toFloatT<T>(samples[i]) * single_config.volume);
431 // Distribute samples to channels in interleaved format
432 int channel = i % channels;
433 if (detectors[channel].processSample(normalized)) {
434 checkDetection(channel);
435 }
436 }
437 }
438
448 float clip(float value) {
449 // Clip the value to the range [-1.0, 1.0]
450 if (value > 1.0f) return 1.0f;
451 if (value < -1.0f) return -1.0f;
452 return value;
453 }
454
472 void processSamples(const uint8_t* data, size_t data_len) {
473 int channels = single_config.channels;
474
476 case 8:
477 processSamplesOfType<uint8_t>(data, data_len, channels);
478 break;
479 case 16:
480 processSamplesOfType<int16_t>(data, data_len, channels);
481 break;
482 case 24:
483 processSamplesOfType<int24_t>(data, data_len, channels);
484 break;
485 case 32:
486 processSamplesOfType<int32_t>(data, data_len, channels);
487 break;
488 default:
489 LOGE("Unsupported bits_per_sample: %d", single_config.bits_per_sample);
490 break;
491 }
492 }
493};
494
495} // 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:63
int getBlockSize() const
Get the block size.
Definition GoerzelStream.h:153
float getMagnitudeSquared() const
Get the squared magnitude (more efficient if you don't need the actual magnitude)
Definition GoerzelStream.h:125
bool processSample(float sample)
Process a single sample.
Definition GoerzelStream.h:88
bool isDetected(float threshold) const
Check if the detected magnitude is above a threshold.
Definition GoerzelStream.h:132
const GoertzelConfig & getConfig() const
Get the current configuration.
Definition GoerzelStream.h:158
void begin(const GoertzelConfig &config)
Initialize the Goertzel detector with configuration.
Definition GoerzelStream.h:71
float getMagnitude() const
Get the magnitude of the detected frequency.
Definition GoerzelStream.h:118
float getTargetFrequency() const
Get the target frequency.
Definition GoerzelStream.h:148
void reset()
Reset the detector state.
Definition GoerzelStream.h:137
AudioStream implementation that processes audio data through Goertzel algorithm. This class acts as a...
Definition GoerzelStream.h:196
bool isFrequencyDetected(int channel=0)
Check if frequency is currently detected.
Definition GoerzelStream.h:346
bool begin(const GoertzelConfig &config)
Initialize with GoertzelConfig.
Definition GoerzelStream.h:226
void * ref
User-defined reference for callback context.
Definition GoerzelStream.h:386
void processSamplesOfType(const uint8_t *data, size_t data_len, int channels)
Template helper to process samples of a specific type.
Definition GoerzelStream.h:423
GoertzelConfig single_config
Current algorithm configuration.
Definition GoerzelStream.h:377
void(* channel_detection_callback)(int channel, float frequency, float magnitude, void *ref)
User callback for detection events.
Definition GoerzelStream.h:384
size_t readBytes(uint8_t *data, size_t len) override
Read data from input stream and process it.
Definition GoerzelStream.h:309
void checkDetection(int channel)
Helper method to check detection and call callback.
Definition GoerzelStream.h:397
void setChannelDetectionCallback(void(*callback)(int channel, float frequency, float magnitude, void *ref))
Set detection callback function for channel-aware frequency detection.
Definition GoerzelStream.h:267
void processSamples(const uint8_t *data, size_t data_len)
Generic sample processing method for both write and readBytes.
Definition GoerzelStream.h:472
bool begin()
Initialize detectors for all channels.
Definition GoerzelStream.h:242
float clip(float value)
Clip audio values to prevent overflow.
Definition GoerzelStream.h:448
size_t write(const uint8_t *data, size_t len) override
Process audio data and pass it through.
Definition GoerzelStream.h:284
Stream * p_stream
Input stream for reading audio data.
Definition GoerzelStream.h:380
const GoertzelConfig & getConfig() const
Get the current configuration.
Definition GoerzelStream.h:361
void setReference(void *ref)
Set reference pointer for callback context.
Definition GoerzelStream.h:372
Vector< GoertzelDetector > detectors
One detector per audio channel.
Definition GoerzelStream.h:376
Print * p_print
Output stream for writing audio data.
Definition GoerzelStream.h:381
void setAudioInfo(AudioInfo info) override
Set audio format and initialize detector array.
Definition GoerzelStream.h:209
float getCurrentMagnitude(int channel=0)
Get the current magnitude for frequency detection.
Definition GoerzelStream.h:330
void setOutput(Print &out)
Defines/Changes the output target.
Definition GoerzelStream.h:256
void setStream(Stream &in)
Defines/Changes the input & output.
Definition GoerzelStream.h:250
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:29
float volume
Volume factor for normalization - scales input samples before processing.
Definition GoerzelStream.h:37
float threshold
Detection threshold for magnitude (normalized samples, typically 0.1 to 1.0)
Definition GoerzelStream.h:35
int block_size
Number of samples to process per block (N) - affects detection latency and accuracy.
Definition GoerzelStream.h:33
GoertzelConfig(const AudioInfo &info)
Copy constructor from AudioInfo.
Definition GoerzelStream.h:41
float target_frequency
Target frequency to detect in Hz (same for all channels)
Definition GoerzelStream.h:31