DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
Goertzel.h
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
43#include "../Core/DspMath.h"
44
45#include <algorithm>
46#include <cassert>
47#include <cmath>
48#include <numbers>
49
50namespace dspark {
51
62template <FloatType T>
64{
65public:
89 void prepare(double sampleRate, double targetFreqHz, int blockSize) noexcept
90 {
91 assert(sampleRate > 0.0 && "Sample rate must be positive");
92 assert(blockSize > 0 && "Block size must be strictly positive");
93 assert(targetFreqHz >= 0.0 && targetFreqHz <= (sampleRate * 0.5) && "Frequency must be within Nyquist limit");
94
95 if (!std::isfinite(sampleRate) || sampleRate <= 0.0
96 || !std::isfinite(targetFreqHz) || blockSize <= 0)
97 return;
98
99 sampleRate_ = sampleRate;
100 targetFreq_ = std::clamp(targetFreqHz, 0.0, sampleRate * 0.5);
101 blockSize_ = blockSize;
102
103 // Exact target frequency omega calculation (Generalized Goertzel)
104 double omega = 2.0 * std::numbers::pi * targetFreq_ / sampleRate_;
105
106 coeff_ = static_cast<T>(2.0 * std::cos(omega));
107 cosOmega_ = static_cast<T>(std::cos(omega));
108 sinOmega_ = static_cast<T>(std::sin(omega));
109
110 // Magnitude normalization: 2/N for interior bins, 1/N for DC and for
111 // exact Nyquist (neither has a mirrored bin; the old 2/N at Nyquist
112 // read a full-scale alternating signal 2x / +6 dB high).
113 const bool isDc = targetFreq_ <= 0.001;
114 const bool isNyquist = targetFreq_ >= sampleRate_ * 0.5 * (1.0 - 1e-12);
115 normalisationFactor_ = (isDc || isNyquist)
116 ? static_cast<T>(1.0 / blockSize_)
117 : static_cast<T>(2.0 / blockSize_);
118
119 reset();
120 }
121
131 void processBlock(const T* data, int numSamples) noexcept
132 {
133 if (data == nullptr || numSamples <= 0) return;
134
135 // Bring state to local variables for compiler optimization (register allocation)
136 T s1 = s1_;
137 T s2 = s2_;
138 const T coeff = coeff_;
139
140 for (int i = 0; i < numSamples; ++i)
141 {
142 T s0 = data[i] + coeff * s1 - s2;
143 s2 = s1;
144 s1 = s0;
145
146 if (++sampleCount_ >= blockSize_)
147 {
148 computeResult(s1, s2);
149 s1 = T(0);
150 s2 = T(0);
151 sampleCount_ = 0;
152 }
153 }
154
155 // Save local state back to member variables
156 s1_ = s1;
157 s2_ = s2;
158 }
159
166 bool pushSample(T sample) noexcept
167 {
168 T s0 = sample + coeff_ * s1_ - s2_;
169 s2_ = s1_;
170 s1_ = s0;
171
172 if (++sampleCount_ >= blockSize_)
173 {
174 computeResult(s1_, s2_);
175 s1_ = T(0);
176 s2_ = T(0);
177 sampleCount_ = 0;
178 return true;
179 }
180 return false;
181 }
182
189 void forceCompute() noexcept
190 {
191 computeResult(s1_, s2_);
192 s1_ = T(0);
193 s2_ = T(0);
194 sampleCount_ = 0;
195 }
196
197 // -- Results ------------------------------------------------------------------
198
207 [[nodiscard]] bool checkNewResultAvailable() noexcept
208 {
209 if (hasNewResult_)
210 {
211 hasNewResult_ = false;
212 return true;
213 }
214 return false;
215 }
216
221 [[nodiscard]] T getMagnitude() const noexcept
222 {
223 return std::sqrt(real_ * real_ + imag_ * imag_);
224 }
225
230 [[nodiscard]] T getPower() const noexcept
231 {
232 return real_ * real_ + imag_ * imag_;
233 }
234
240 [[nodiscard]] T getMagnitudeDb() const noexcept
241 {
243 }
244
254 [[nodiscard]] T getPhase() const noexcept
255 {
256 return std::atan2(imag_, real_);
257 }
258
263 [[nodiscard]] double getTargetFrequency() const noexcept { return targetFreq_; }
264
266 [[nodiscard]] double getSampleRate() const noexcept { return sampleRate_; }
267
269 [[nodiscard]] int getBlockSize() const noexcept { return blockSize_; }
270
276 void reset() noexcept
277 {
278 s1_ = T(0);
279 s2_ = T(0);
280 sampleCount_ = 0;
281 real_ = T(0);
282 imag_ = T(0);
283 hasNewResult_ = false;
284 }
285
286private:
290 void computeResult(T s1, T s2) noexcept
291 {
292 real_ = (s1 - s2 * cosOmega_) * normalisationFactor_;
293 imag_ = (s2 * sinOmega_) * normalisationFactor_;
294 hasNewResult_ = true;
295 }
296
297 double sampleRate_ = 48000.0;
298 double targetFreq_ = 440.0;
299 int blockSize_ = 2048;
300
301 T coeff_ = T(0);
302 T cosOmega_ = T(0);
303 T sinOmega_ = T(0);
304 T normalisationFactor_ = T(0);
305
306 // Streaming state
307 T s1_ = T(0);
308 T s2_ = T(0);
309 int sampleCount_ = 0;
310
311 // Results
312 T real_ = T(0);
313 T imag_ = T(0);
314 bool hasNewResult_ = false;
315};
316
317} // namespace dspark
Single-frequency magnitude detector using the Goertzel algorithm.
Definition Goertzel.h:64
double getSampleRate() const noexcept
Returns the configured sample rate in Hz.
Definition Goertzel.h:266
T getPower() const noexcept
Returns the power at the target frequency.
Definition Goertzel.h:230
void processBlock(const T *data, int numSamples) noexcept
Processes a block of audio samples, updating the internal state.
Definition Goertzel.h:131
double getTargetFrequency() const noexcept
Returns the currently configured target frequency.
Definition Goertzel.h:263
void reset() noexcept
Resets the internal IIR state and counters to zero.
Definition Goertzel.h:276
void forceCompute() noexcept
Manually forces the computation of the result before N samples are reached.
Definition Goertzel.h:189
T getPhase() const noexcept
Returns the phase angle at the target frequency.
Definition Goertzel.h:254
bool checkNewResultAvailable() noexcept
Checks if a new result has been computed.
Definition Goertzel.h:207
T getMagnitude() const noexcept
Returns the magnitude at the target frequency (linear scale).
Definition Goertzel.h:221
int getBlockSize() const noexcept
Returns the configured analysis block size in samples.
Definition Goertzel.h:269
bool pushSample(T sample) noexcept
Feeds a single sample into the running Goertzel computation.
Definition Goertzel.h:166
T getMagnitudeDb() const noexcept
Returns the magnitude in decibels.
Definition Goertzel.h:240
void prepare(double sampleRate, double targetFreqHz, int blockSize) noexcept
Prepares the detector for a specific frequency.
Definition Goertzel.h:89
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:89