Skip to content

Commit efa7d8f

Browse files
committed
DRAFT GoerzelStream
1 parent 663b6ae commit efa7d8f

File tree

1 file changed

+145
-28
lines changed

1 file changed

+145
-28
lines changed

src/AudioTools/CoreAudio/GoerzelStream.h

Lines changed: 145 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,24 @@ namespace audio_tools {
1313

1414
/**
1515
* @brief Configuration for Goertzel algorithm detectors
16+
*
17+
* This structure extends AudioInfo to include Goertzel-specific parameters.
18+
* It defines the frequency detection behavior, audio format, and processing
19+
* parameters for the Goertzel algorithm implementation.
20+
*
1621
* @ingroup dsp
1722
* @author pschatzmann
1823
* @copyright GPLv3
1924
*/
2025
struct GoertzelConfig : public AudioInfo {
2126
/// Target frequency to detect in Hz (same for all channels)
2227
float target_frequency = 440.0f;
23-
/// Number of samples to process per block (N)
28+
/// Number of samples to process per block (N) - affects detection latency and accuracy
2429
int block_size = 205;
25-
/// Detection threshold for magnitude (normalized samples, typically 0.1
26-
/// to 1.0)
30+
/// Detection threshold for magnitude (normalized samples, typically 0.1 to 1.0)
2731
float threshold = 0.5f;
32+
/// Volume factor for normalization - scales input samples before processing
33+
float volume = 1.0f;
2834

2935
GoertzelConfig() = default;
3036
/// Copy constructor from AudioInfo
@@ -184,6 +190,15 @@ class GoertzelStream : public AudioStream {
184190
public:
185191
GoertzelStream() = default;
186192

193+
/**
194+
* @brief Set audio format and initialize detector array
195+
*
196+
* This method is called when the audio format changes. It updates the
197+
* internal configuration and resizes the detector array to match the
198+
* number of audio channels.
199+
*
200+
* @param info Audio format information (sample rate, channels, bits per sample)
201+
*/
187202
void setAudioInfo(AudioInfo info) override {
188203
AudioStream::setAudioInfo(info);
189204
single_config.copyFrom(info);
@@ -193,7 +208,13 @@ class GoertzelStream : public AudioStream {
193208

194209
/**
195210
* @brief Initialize with GoertzelConfig
211+
*
212+
* Sets up the stream with specific Goertzel algorithm parameters.
213+
* This method configures the audio format and detection parameters
214+
* in one call.
215+
*
196216
* @param config Configuration object containing all parameters
217+
* @return true if initialization successful, false otherwise
197218
*/
198219
bool begin(const GoertzelConfig& config) {
199220
this->single_config = config;
@@ -202,6 +223,15 @@ class GoertzelStream : public AudioStream {
202223
return begin();
203224
}
204225

226+
/**
227+
* @brief Initialize detectors for all channels
228+
*
229+
* Creates and configures individual Goertzel detectors for each audio
230+
* channel. Each detector will independently analyze its channel for
231+
* the target frequency.
232+
*
233+
* @return true if at least one channel is configured, false otherwise
234+
*/
205235
bool begin() {
206236
for (int i = 0; i < info.channels; i++) {
207237
detectors[i].begin(single_config);
@@ -219,10 +249,13 @@ class GoertzelStream : public AudioStream {
219249
void setOutput(Print& out) { p_print = &out; }
220250

221251
/**
222-
* @brief Set detection callback function for channel-aware frequency
223-
* detection
224-
* @param callback Function to call when frequency is detected, includes
225-
* channel info
252+
* @brief Set detection callback function for channel-aware frequency detection
253+
*
254+
* Registers a callback function that will be called when the target frequency
255+
* is detected on any channel. The callback receives the channel number,
256+
* detected frequency, magnitude, and a user reference pointer.
257+
*
258+
* @param callback Function to call when frequency is detected, includes channel info
226259
*/
227260
void setChannelDetectionCallback(void (*callback)(
228261
int channel, float frequency, float magnitude, void* ref)) {
@@ -231,9 +264,15 @@ class GoertzelStream : public AudioStream {
231264

232265
/**
233266
* @brief Process audio data and pass it through
234-
* @param data Input audio data
235-
* @param len Length of data
236-
* @return Number of bytes written to output
267+
*
268+
* This method receives audio data, processes it through the Goertzel
269+
* detectors for frequency analysis, and then passes the unmodified
270+
* data to the output stream. This allows for real-time frequency
271+
* detection without affecting the audio flow.
272+
*
273+
* @param data Input audio data buffer
274+
* @param len Length of data in bytes
275+
* @return Number of bytes written to output stream
237276
*/
238277
size_t write(const uint8_t* data, size_t len) override {
239278
if (p_print == nullptr) return 0;
@@ -250,9 +289,15 @@ class GoertzelStream : public AudioStream {
250289

251290
/**
252291
* @brief Read data from input stream and process it
253-
* @param data Output buffer
254-
* @param len Length to read
255-
* @return Number of bytes read
292+
*
293+
* Reads audio data from the input stream, processes it through the
294+
* Goertzel detectors for frequency analysis, and returns the data
295+
* to the caller. This allows for frequency detection in pull-mode
296+
* audio processing.
297+
*
298+
* @param data Output buffer to store read data
299+
* @param len Maximum number of bytes to read
300+
* @return Number of bytes actually read
256301
*/
257302
size_t readBytes(uint8_t* data, size_t len) override {
258303
if (p_stream == nullptr) return 0;
@@ -267,7 +312,13 @@ class GoertzelStream : public AudioStream {
267312

268313
/**
269314
* @brief Get the current magnitude for frequency detection
270-
* @param channel Channel index (default: 0)
315+
*
316+
* Returns the most recent magnitude calculation for the specified channel.
317+
* The magnitude represents the strength of the target frequency in the
318+
* last processed block of samples.
319+
*
320+
* @param channel Channel index (0-based, default: 0 for mono)
321+
* @return Magnitude value (0.0 if channel invalid or no detection)
271322
*/
272323
float getCurrentMagnitude(int channel = 0) {
273324
if (channel >= 0 && channel < detectors.size()) {
@@ -278,7 +329,12 @@ class GoertzelStream : public AudioStream {
278329

279330
/**
280331
* @brief Check if frequency is currently detected
281-
* @param channel Channel index (default: 0)
332+
*
333+
* Compares the current magnitude against the configured threshold
334+
* to determine if the target frequency is present on the specified channel.
335+
*
336+
* @param channel Channel index (0-based, default: 0 for mono)
337+
* @return true if frequency detected above threshold, false otherwise
282338
*/
283339
bool isFrequencyDetected(int channel = 0) {
284340
if (channel >= 0 && channel < detectors.size()) {
@@ -289,28 +345,47 @@ class GoertzelStream : public AudioStream {
289345

290346
/**
291347
* @brief Get the current configuration
348+
*
349+
* Returns a reference to the current Goertzel configuration, including
350+
* audio format, detection parameters, and processing settings.
351+
*
352+
* @return Reference to the current GoertzelConfig
292353
*/
293354
const GoertzelConfig& getConfig() const { return single_config; }
294355

295-
/// Defines a reference to any object that should be available in the callback
356+
/**
357+
* @brief Set reference pointer for callback context
358+
*
359+
* Defines a reference to any object that should be available in the
360+
* detection callback. This allows the callback to access application
361+
* context or other objects.
362+
*
363+
* @param ref Pointer to user-defined context object
364+
*/
296365
void setReference(void* ref) { this->ref = ref; }
297366

298367
protected:
299-
Vector<GoertzelDetector> detectors;
300-
// Configuration objects
301-
GoertzelConfig single_config;
302-
// Stream pointers
303-
Stream* p_stream = nullptr;
304-
Print* p_print = nullptr;
305-
306-
// Callback functions
368+
// Core detection components
369+
Vector<GoertzelDetector> detectors; ///< One detector per audio channel
370+
GoertzelConfig single_config; ///< Current algorithm configuration
371+
372+
// Stream I/O components
373+
Stream* p_stream = nullptr; ///< Input stream for reading audio data
374+
Print* p_print = nullptr; ///< Output stream for writing audio data
375+
376+
// Callback system
307377
void (*channel_detection_callback)(int channel, float frequency,
308-
float magnitude, void* ref) = nullptr;
309-
void* ref = this;
378+
float magnitude, void* ref) = nullptr; ///< User callback for detection events
379+
void* ref = this; ///< User-defined reference for callback context
310380

311381
/**
312382
* @brief Helper method to check detection and call callback
313-
* @param channel Channel index
383+
*
384+
* Examines the magnitude from a detector and triggers the user callback
385+
* if the magnitude exceeds the configured threshold. This method is
386+
* called after each complete block is processed.
387+
*
388+
* @param channel Channel index that completed a detection block
314389
*/
315390
void checkDetection(int channel) {
316391
if (channel >= 0 && channel < detectors.size()) {
@@ -326,6 +401,16 @@ class GoertzelStream : public AudioStream {
326401

327402
/**
328403
* @brief Template helper to process samples of a specific type
404+
*
405+
* Converts audio samples from their native format to normalized floats,
406+
* applies volume scaling, and feeds them to the appropriate channel
407+
* detectors. This method handles the format conversion and channel
408+
* distribution automatically.
409+
*
410+
* @tparam T Sample data type (uint8_t, int16_t, int24_t, int32_t)
411+
* @param data Raw audio data buffer
412+
* @param data_len Length of data buffer in bytes
413+
* @param channels Number of audio channels (for sample distribution)
329414
*/
330415
template <typename T>
331416
void processSamplesOfType(const uint8_t* data, size_t data_len,
@@ -334,16 +419,48 @@ class GoertzelStream : public AudioStream {
334419
size_t num_samples = data_len / sizeof(T);
335420

336421
for (size_t i = 0; i < num_samples; i++) {
337-
float normalized = NumberConverter::toFloatT<T>(samples[i]);
422+
// Convert to normalized float and apply volume scaling
423+
float normalized = clip(NumberConverter::toFloatT<T>(samples[i]) * single_config.volume);
424+
// Distribute samples to channels in interleaved format
338425
int channel = i % channels;
339426
if (detectors[channel].processSample(normalized)) {
340427
checkDetection(channel);
341428
}
342429
}
343430
}
344431

432+
/**
433+
* @brief Clip audio values to prevent overflow
434+
*
435+
* Ensures that normalized audio samples stay within the valid range
436+
* of [-1.0, 1.0] to prevent algorithm instability.
437+
*
438+
* @param value Input audio sample value
439+
* @return Clipped value in range [-1.0, 1.0]
440+
*/
441+
float clip(float value) {
442+
// Clip the value to the range [-1.0, 1.0]
443+
if (value > 1.0f) return 1.0f;
444+
if (value < -1.0f) return -1.0f;
445+
return value;
446+
}
447+
345448
/**
346449
* @brief Generic sample processing method for both write and readBytes
450+
*
451+
* This method serves as the central dispatcher for audio sample processing.
452+
* It examines the configured sample format and routes the data to the
453+
* appropriate type-specific processing method. This approach eliminates
454+
* code duplication between write() and readBytes() methods.
455+
*
456+
* Supported formats:
457+
* - 8-bit: Unsigned samples (0-255)
458+
* - 16-bit: Signed samples (-32768 to 32767)
459+
* - 24-bit: Signed samples in 3-byte packed format
460+
* - 32-bit: Signed samples (-2^31 to 2^31-1)
461+
*
462+
* @param data Raw audio data buffer
463+
* @param data_len Length of data buffer in bytes
347464
*/
348465
void processSamples(const uint8_t* data, size_t data_len) {
349466
int channels = single_config.channels;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy