105 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
106 Q = std::max(Q, 0.001);
107 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
108 const double cosw0 = std::cos(w0);
109 const double sinw0 = std::sin(w0);
110 const double alpha = sinw0 / (2.0 * Q);
112 const double a0 = 1.0 + alpha;
129 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
130 Q = std::max(Q, 0.001);
131 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
132 const double cosw0 = std::cos(w0);
133 const double sinw0 = std::sin(w0);
134 const double alpha = sinw0 / (2.0 * Q);
136 const double a0 = 1.0 + alpha;
158 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
159 Q = std::max(Q, 0.001);
160 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
161 const double cosw0 = std::cos(w0);
162 const double sinw0 = std::sin(w0);
163 const double alpha = sinw0 / (2.0 * Q);
165 const double a0 = 1.0 + alpha;
187 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
188 Q = std::max(Q, 0.001);
189 const double A = std::pow(10.0, gainDb / 40.0);
190 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
191 const double cosw0 = std::cos(w0);
192 const double sinw0 = std::sin(w0);
193 const double alpha = sinw0 / (2.0 * Q);
195 const double a0 = 1.0 + alpha / A;
224 double Q,
double gainDb)
noexcept
226 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.495));
227 Q = std::max(Q, 0.001);
228 if (std::abs(gainDb) < 0.01)
229 return { 1.0, 0.0, 0.0, 0.0, 0.0 };
231 const double G0 = 1.0;
232 const double G = std::pow(10.0, gainDb / 20.0);
233 const double GB = std::pow(10.0, gainDb / 40.0);
235 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
236 const double Dw = w0 / Q;
241 const double W0 = 2.0 * std::numbers::pi * freq;
242 const double DW = W0 / Q;
243 const double Wn = std::numbers::pi * sampleRate;
244 const double d2 = (Wn * Wn - W0 * W0) * (Wn * Wn - W0 * W0);
245 const double G1 = std::sqrt((G0 * G0 * d2 + G * G * DW * DW * Wn * Wn)
246 / (d2 + DW * DW * Wn * Wn));
249 const double G2 = G * G, G02 = G0 * G0, GB2 = GB * GB, G12 = G1 * G1;
250 const double F = std::abs(G2 - GB2);
251 const double G00 = std::abs(G2 - G02);
252 const double F00 = std::abs(GB2 - G02);
253 const double F01 = std::abs(GB2 - G0 * G1);
254 const double F11 = std::abs(GB2 - G12);
255 const double G01 = std::abs(G2 - G0 * G1);
256 const double G11 = std::abs(G2 - G12);
258 const double t0 = std::tan(w0 / 2.0);
259 const double W2 = std::sqrt(G11 / G00) * t0 * t0;
260 const double DWd = (1.0 + std::sqrt(F00 / F11) * W2) * std::tan(Dw / 2.0);
262 const double C = F11 * DWd * DWd - 2.0 * W2 * (F01 - std::sqrt(F00 * F11));
263 const double D = 2.0 * W2 * (G01 - std::sqrt(G00 * G11));
264 const double A = std::sqrt(std::max((C + D) / std::max(F, 1e-30), 0.0));
265 const double B = std::sqrt(std::max((G2 * C + GB2 * D) / std::max(F, 1e-30), 0.0));
267 const double a0 = 1.0 + W2 + A;
270 -2.0 * (G1 - G0 * W2),
285 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
288 slope = std::clamp(slope, 0.0001, 1.0);
289 const double A = std::pow(10.0, gainDb / 40.0);
290 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
291 const double cosw0 = std::cos(w0);
292 const double sinw0 = std::sin(w0);
293 const double radicand = (A + 1.0 / A) * (1.0 / slope - 1.0) + 2.0;
294 const double alpha = sinw0 / 2.0 * std::sqrt(std::max(0.0, radicand));
295 const double twoSqrtAAlpha = 2.0 * std::sqrt(A) * alpha;
297 const double a0 = (A + 1.0) + (A - 1.0) * cosw0 + twoSqrtAAlpha;
299 A * ((A + 1.0) - (A - 1.0) * cosw0 + twoSqrtAAlpha),
300 2.0 * A * ((A - 1.0) - (A + 1.0) * cosw0),
301 A * ((A + 1.0) - (A - 1.0) * cosw0 - twoSqrtAAlpha),
302 -2.0 * ((A - 1.0) + (A + 1.0) * cosw0),
303 (A + 1.0) + (A - 1.0) * cosw0 - twoSqrtAAlpha);
315 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
318 slope = std::clamp(slope, 0.0001, 1.0);
319 const double A = std::pow(10.0, gainDb / 40.0);
320 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
321 const double cosw0 = std::cos(w0);
322 const double sinw0 = std::sin(w0);
323 const double radicand = (A + 1.0 / A) * (1.0 / slope - 1.0) + 2.0;
324 const double alpha = sinw0 / 2.0 * std::sqrt(std::max(0.0, radicand));
325 const double twoSqrtAAlpha = 2.0 * std::sqrt(A) * alpha;
327 const double a0 = (A + 1.0) - (A - 1.0) * cosw0 + twoSqrtAAlpha;
329 A * ((A + 1.0) + (A - 1.0) * cosw0 + twoSqrtAAlpha),
330 -2.0 * A * ((A - 1.0) + (A + 1.0) * cosw0),
331 A * ((A + 1.0) + (A - 1.0) * cosw0 - twoSqrtAAlpha),
332 2.0 * ((A - 1.0) - (A + 1.0) * cosw0),
333 (A + 1.0) - (A - 1.0) * cosw0 - twoSqrtAAlpha);
344 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
345 Q = std::max(Q, 0.001);
346 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
347 const double cosw0 = std::cos(w0);
348 const double sinw0 = std::sin(w0);
349 const double alpha = sinw0 / (2.0 * Q);
351 const double a0 = 1.0 + alpha;
368 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
369 Q = std::max(Q, 0.001);
370 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
371 const double cosw0 = std::cos(w0);
372 const double sinw0 = std::sin(w0);
373 const double alpha = sinw0 / (2.0 * Q);
375 const double a0 = 1.0 + alpha;
397 frequency = std::clamp(frequency, 1.0, std::max(1.0, sampleRate * 0.499));
398 const double w = std::tan(std::numbers::pi * frequency / sampleRate);
399 const double n = 1.0 / (1.0 + w);
404 c.
a1 = (w - 1.0) * n;
420 frequency = std::clamp(frequency, 1.0, std::max(1.0, sampleRate * 0.499));
421 const double w = std::tan(std::numbers::pi * frequency / sampleRate);
422 const double n = 1.0 / (1.0 + w);
427 c.
a1 = (w - 1.0) * n;
446 pivotFreq = std::clamp(pivotFreq, 1.0, std::max(1.0, sampleRate * 0.499));
447 const double g = std::pow(10.0, gainDb / 20.0);
448 const double sqrtG = std::sqrt(g);
449 const double c = std::tan(std::numbers::pi * pivotFreq / sampleRate);
454 const double norm = 1.0 / (1.0 + sqrtG * c);
457 coeffs.
b0 = (sqrtG + c) * norm;
458 coeffs.
b1 = (c - sqrtG) * norm;
460 coeffs.
a1 = (sqrtG * c - 1.0) * norm;
479 [[nodiscard]]
double getMagnitude(
double frequency,
double sampleRate)
const noexcept
481 const double w = 2.0 * std::numbers::pi * frequency / sampleRate;
482 const double cosW = std::cos(w);
483 const double cos2W = std::cos(2.0 * w);
484 const double sinW = std::sin(w);
485 const double sin2W = std::sin(2.0 * w);
487 const double nRe =
b0 +
b1 * cosW +
b2 * cos2W;
488 const double nIm = -
b1 * sinW -
b2 * sin2W;
489 const double dRe = 1.0 +
a1 * cosW +
a2 * cos2W;
490 const double dIm = -
a1 * sinW -
a2 * sin2W;
492 const double numMag2 = nRe * nRe + nIm * nIm;
493 const double denMag2 = dRe * dRe + dIm * dIm;
495 return (denMag2 > 1e-30) ? std::sqrt(numMag2 / denMag2) : 0.0;
510 template <
typename U>
512 std::span<U> magnitudes,
513 double sampleRate)
const noexcept
515 assert(magnitudes.size() >= frequencies.size());
516 for (
size_t i = 0; i < frequencies.size(); ++i)
517 magnitudes[i] =
static_cast<U
>(
getMagnitude(
static_cast<double>(frequencies[i]), sampleRate));
525 [[nodiscard]]
static BiquadCoeffs normalise(
double a0,
double b0r,
double b1r,
double b2r,
526 double a1r,
double a2r)
noexcept
528 const double invA0 = 1.0 / a0;
529 return { b0r * invA0, b1r * invA0, b2r * invA0, a1r * invA0, a2r * invA0 };
554template <
typename T,
int MaxChannels = 8>
568 : activeCoeffs_(other.activeCoeffs_),
569 stagedCoeffs_(other.stagedCoeffs_),
570 coeffsDirty_(other.coeffsDirty_.load(std::memory_order_relaxed)),
577 if (
this == &other)
return *
this;
578 activeCoeffs_ = other.activeCoeffs_;
579 stagedCoeffs_ = other.stagedCoeffs_;
580 coeffsDirty_.store(other.coeffsDirty_.load(std::memory_order_relaxed),
581 std::memory_order_relaxed);
582 coeffsSeq_.store(0, std::memory_order_relaxed);
583 state_ = other.state_;
611 coeffsSeq_.fetch_add(1, std::memory_order_acq_rel);
613 coeffsSeq_.fetch_add(1, std::memory_order_release);
614 coeffsDirty_.store(
true, std::memory_order_release);
633 if (coeffsDirty_.exchange(
false, std::memory_order_acquire))
641 s0 = coeffsSeq_.load(std::memory_order_acquire);
648 std::atomic_thread_fence(std::memory_order_acquire);
649 s1 = coeffsSeq_.load(std::memory_order_relaxed);
650 }
while ((s0 & 1u) != 0u || s0 != s1);
670 for (
auto& s : state_)
716 assert(channel >= 0 && channel < MaxChannels &&
"Channel index out of bounds");
722 if (coeffsDirty_.load(std::memory_order_relaxed)) [[unlikely]]
725 auto& s = state_[channel];
727 const double output = activeCoeffs_.
b0 * input + s.z1;
728 s.z1 = activeCoeffs_.
b1 * input - activeCoeffs_.
a1 * output + s.z2;
729 s.z2 = activeCoeffs_.
b2 * input - activeCoeffs_.
a2 * output;
757 const int numChannels = std::min(buffer.getNumChannels(), MaxChannels);
758 const int numSamples = buffer.getNumSamples();
764 for (
int ch = 0; ch < numChannels; ++ch)
766 T* data = buffer.getChannel(ch);
767 auto& s = state_[ch];
771 for (
int i = 0; i < numSamples; ++i)
773 const double input =
static_cast<double>(data[i]);
774 const double output = c.
b0 * input + z1;
775 z1 = c.
b1 * input - c.
a1 * output + z2;
776 z2 = c.
b2 * input - c.
a2 * output;
777 data[i] =
static_cast<T
>(output);
796 BiquadCoeffs activeCoeffs_ {};
797 BiquadCoeffs stagedCoeffs_ {};
798 std::atomic<bool> coeffsDirty_{
false};
799 std::atomic<unsigned> coeffsSeq_{0};
801 std::array<State, MaxChannels> state_ {};
Owning audio buffer and non-owning view for real-time DSP processing.
RAII scope guard that disables denormal (subnormal) float arithmetic.
Non-owning view over audio channel data.
Biquad filter using Transposed Direct Form II (TDF-II) with thread-safe updates.
Biquad(const Biquad &)=delete
void setCoeffs(const BiquadCoeffs &c) noexcept
Sets the filter coefficients asynchronously.
void reset() noexcept
Resets all per-channel filter states to zero to avoid ringing/clicks.
const BiquadCoeffs & getCoeffs() const noexcept
Returns the active coefficient set currently in use by the DSP thread.
bool applyPendingCoeffs() noexcept
Promotes any pending staged coefficients to active.
Biquad & operator=(Biquad &&other) noexcept
void processBlock(AudioBufferView< T > buffer) noexcept
Processes a full audio buffer in-place.
Biquad & operator=(const Biquad &)=delete
T processSample(T input, int channel) noexcept
Processes a single sample for a specific channel.
double processSampleCore(double input, int channel) noexcept
One recursion step in the core's own precision (double).
Biquad() noexcept=default
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
Main namespace for the DSPark framework.
Stores normalised biquad coefficients (b0, b1, b2, a1, a2), always double.
static BiquadCoeffs makeFirstOrderHighPass(double sampleRate, double frequency) noexcept
First-order (6 dB/oct) high-pass filter.
static BiquadCoeffs makePeakMatched(double sampleRate, double freq, double Q, double gainDb) noexcept
Peaking filter with prescribed Nyquist gain (Orfanidis design).
void getMagnitudeForFrequencyArray(std::span< const U > frequencies, std::span< U > magnitudes, double sampleRate) const noexcept
Computes magnitude responses for a batch of frequencies.
static BiquadCoeffs makeHighPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
High-pass filter.
static BiquadCoeffs makeAllPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
All-pass filter.
static BiquadCoeffs makeBandPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
Band-pass filter (constant 0 dB peak gain).
static BiquadCoeffs makePeak(double sampleRate, double freq, double Q, double gainDb) noexcept
Peak (parametric EQ) filter.
double getMagnitude(double frequency, double sampleRate) const noexcept
Evaluates magnitude response |H(f)| at a single frequency.
static BiquadCoeffs makeFirstOrderLowPass(double sampleRate, double frequency) noexcept
First-order (6 dB/oct) low-pass filter.
static BiquadCoeffs makeTilt(double sampleRate, double pivotFreq, double gainDb) noexcept
Creates a first-order tilt filter.
static BiquadCoeffs makeLowPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
Low-pass filter.
static BiquadCoeffs makeLowShelf(double sampleRate, double freq, double gainDb, double slope=1.0) noexcept
Low-shelf filter.
static BiquadCoeffs makeHighShelf(double sampleRate, double freq, double gainDb, double slope=1.0) noexcept
High-shelf filter.
static BiquadCoeffs makeNotch(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
Notch (band-reject) filter.