DSPark 1.6.1
Header-only audio DSP framework in pure C++20 — zero dependencies
Loading...
Searching...
No Matches
Goertzel.h
Go to the documentation of this file.
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
26#include "../Core/DspMath.h"
27
28#include <algorithm>
29#include <cassert>
30#include <cmath>
31#include <numbers>
32
33namespace dspark {
34
45template <FloatType T>
47{
48public:
68 void prepare(double sampleRate, double targetFreqHz, int blockSize) noexcept
69 {
70 assert(sampleRate > 0.0 && "Sample rate must be positive");
71 assert(blockSize > 0 && "Block size must be strictly positive");
72 assert(targetFreqHz >= 0.0 && targetFreqHz <= (sampleRate * 0.5) && "Frequency must be within Nyquist limit");
73
74 if (!std::isfinite(sampleRate) || sampleRate <= 0.0
75 || !std::isfinite(targetFreqHz) || blockSize <= 0)
76 return;
77
78 sampleRate_ = sampleRate;
79 targetFreq_ = std::clamp(targetFreqHz, 0.0, sampleRate * 0.5);
80 blockSize_ = blockSize;
81
82 // Exact target frequency omega calculation (Generalized Goertzel)
83 double omega = 2.0 * std::numbers::pi * targetFreq_ / sampleRate_;
84
85 coeff_ = static_cast<T>(2.0 * std::cos(omega));
86 cosOmega_ = static_cast<T>(std::cos(omega));
87 sinOmega_ = static_cast<T>(std::sin(omega));
88
89 // Magnitude normalization: 2/N for interior bins, 1/N for DC and for
90 // exact Nyquist (neither has a mirrored bin; the old 2/N at Nyquist
91 // read a full-scale alternating signal 2x / +6 dB high).
92 const bool isDc = targetFreq_ <= 0.001;
93 const bool isNyquist = targetFreq_ >= sampleRate_ * 0.5 * (1.0 - 1e-12);
94 normalisationFactor_ = (isDc || isNyquist)
95 ? static_cast<T>(1.0 / blockSize_)
96 : static_cast<T>(2.0 / blockSize_);
97
98 reset();
99 }
100
110 void processBlock(const T* data, int numSamples) noexcept
111 {
112 if (data == nullptr || numSamples <= 0) return;
113
114 // Bring state to local variables for compiler optimization (register allocation)
115 T s1 = s1_;
116 T s2 = s2_;
117 const T coeff = coeff_;
118
119 for (int i = 0; i < numSamples; ++i)
120 {
121 T s0 = data[i] + coeff * s1 - s2;
122 s2 = s1;
123 s1 = s0;
124
125 if (++sampleCount_ >= blockSize_)
126 {
127 computeResult(s1, s2);
128 s1 = T(0);
129 s2 = T(0);
130 sampleCount_ = 0;
131 }
132 }
133
134 // Save local state back to member variables
135 s1_ = s1;
136 s2_ = s2;
137 }
138
145 bool pushSample(T sample) noexcept
146 {
147 T s0 = sample + coeff_ * s1_ - s2_;
148 s2_ = s1_;
149 s1_ = s0;
150
151 if (++sampleCount_ >= blockSize_)
152 {
153 computeResult(s1_, s2_);
154 s1_ = T(0);
155 s2_ = T(0);
156 sampleCount_ = 0;
157 return true;
158 }
159 return false;
160 }
161
168 void forceCompute() noexcept
169 {
170 computeResult(s1_, s2_);
171 s1_ = T(0);
172 s2_ = T(0);
173 sampleCount_ = 0;
174 }
175
176 // -- Results ------------------------------------------------------------------
177
186 [[nodiscard]] bool checkNewResultAvailable() noexcept
187 {
188 if (hasNewResult_)
189 {
190 hasNewResult_ = false;
191 return true;
192 }
193 return false;
194 }
195
200 [[nodiscard]] T getMagnitude() const noexcept
201 {
202 return std::sqrt(real_ * real_ + imag_ * imag_);
203 }
204
209 [[nodiscard]] T getPower() const noexcept
210 {
211 return real_ * real_ + imag_ * imag_;
212 }
213
219 [[nodiscard]] T getMagnitudeDb() const noexcept
220 {
222 }
223
233 [[nodiscard]] T getPhase() const noexcept
234 {
235 return std::atan2(imag_, real_);
236 }
237
242 [[nodiscard]] double getTargetFrequency() const noexcept { return targetFreq_; }
243
245 [[nodiscard]] double getSampleRate() const noexcept { return sampleRate_; }
246
248 [[nodiscard]] int getBlockSize() const noexcept { return blockSize_; }
249
255 void reset() noexcept
256 {
257 s1_ = T(0);
258 s2_ = T(0);
259 sampleCount_ = 0;
260 real_ = T(0);
261 imag_ = T(0);
262 hasNewResult_ = false;
263 }
264
265private:
269 void computeResult(T s1, T s2) noexcept
270 {
271 real_ = (s1 - s2 * cosOmega_) * normalisationFactor_;
272 imag_ = (s2 * sinOmega_) * normalisationFactor_;
273 hasNewResult_ = true;
274 }
275
276 double sampleRate_ = 48000.0;
277 double targetFreq_ = 440.0;
278 int blockSize_ = 2048;
279
280 T coeff_ = T(0);
281 T cosOmega_ = T(0);
282 T sinOmega_ = T(0);
283 T normalisationFactor_ = T(0);
284
285 // Streaming state
286 T s1_ = T(0);
287 T s2_ = T(0);
288 int sampleCount_ = 0;
289
290 // Results
291 T real_ = T(0);
292 T imag_ = T(0);
293 bool hasNewResult_ = false;
294};
295
296} // namespace dspark
Single-frequency magnitude detector using the Goertzel algorithm.
Definition Goertzel.h:47
double getSampleRate() const noexcept
Returns the configured sample rate in Hz.
Definition Goertzel.h:245
T getPower() const noexcept
Returns the power at the target frequency.
Definition Goertzel.h:209
void processBlock(const T *data, int numSamples) noexcept
Processes a block of audio samples, updating the internal state.
Definition Goertzel.h:110
double getTargetFrequency() const noexcept
Returns the currently configured target frequency.
Definition Goertzel.h:242
void reset() noexcept
Resets the internal IIR state and counters to zero.
Definition Goertzel.h:255
void forceCompute() noexcept
Manually forces the computation of the result before N samples are reached.
Definition Goertzel.h:168
T getPhase() const noexcept
Returns the phase angle at the target frequency.
Definition Goertzel.h:233
bool checkNewResultAvailable() noexcept
Checks if a new result has been computed.
Definition Goertzel.h:186
T getMagnitude() const noexcept
Returns the magnitude at the target frequency (linear scale).
Definition Goertzel.h:200
int getBlockSize() const noexcept
Returns the configured analysis block size in samples.
Definition Goertzel.h:248
bool pushSample(T sample) noexcept
Feeds a single sample into the running Goertzel computation.
Definition Goertzel.h:145
T getMagnitudeDb() const noexcept
Returns the magnitude in decibels.
Definition Goertzel.h:219
void prepare(double sampleRate, double targetFreqHz, int blockSize) noexcept
Prepares the detector for a specific frequency.
Definition Goertzel.h:68
Main namespace for the DSPark framework.
T gainToDecibels(T gain, T minusInfinityDb=T(-100)) noexcept
Converts a linear gain value to decibels.
Definition DspMath.h:78