61#include "AudioBuffer.h"
62#include "DenormalGuard.h"
68#ifndef DSPARK_NOINLINE
70 #define DSPARK_NOINLINE __declspec(noinline)
71 #elif defined(__GNUC__) || defined(__clang__)
72 #define DSPARK_NOINLINE __attribute__((noinline))
74 #define DSPARK_NOINLINE
120 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
121 Q = std::max(Q, 0.001);
122 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
123 const double cosw0 = std::cos(w0);
124 const double sinw0 = std::sin(w0);
125 const double alpha = sinw0 / (2.0 * Q);
127 const double a0 = 1.0 + alpha;
144 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
145 Q = std::max(Q, 0.001);
146 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
147 const double cosw0 = std::cos(w0);
148 const double sinw0 = std::sin(w0);
149 const double alpha = sinw0 / (2.0 * Q);
151 const double a0 = 1.0 + alpha;
173 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
174 Q = std::max(Q, 0.001);
175 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
176 const double cosw0 = std::cos(w0);
177 const double sinw0 = std::sin(w0);
178 const double alpha = sinw0 / (2.0 * Q);
180 const double a0 = 1.0 + alpha;
202 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
203 Q = std::max(Q, 0.001);
204 const double A = std::pow(10.0, gainDb / 40.0);
205 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
206 const double cosw0 = std::cos(w0);
207 const double sinw0 = std::sin(w0);
208 const double alpha = sinw0 / (2.0 * Q);
210 const double a0 = 1.0 + alpha / A;
248 double Q,
double gainDb)
noexcept
250 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
251 Q = std::max(Q, 0.001);
252 if (std::abs(gainDb) < 0.01)
253 return { 1.0, 0.0, 0.0, 0.0, 0.0 };
255 const double G = std::pow(10.0, gainDb / 20.0);
256 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
260 const double q = 1.0 / (2.0 * Q * std::sqrt(G));
262 const double a2 = std::exp(-2.0 * q * w0);
265 a1 = -2.0 * std::exp(-q * w0) * std::cos(std::sqrt(1.0 - q * q) * w0);
273 const double r = std::sqrt(q * q - 1.0);
274 a1 = -(std::exp(-w0 / (q + r)) + std::exp(-(q + r) * w0));
279 const double A0 = (1.0 +
a1 +
a2) * (1.0 +
a1 +
a2);
280 const double A1 = (1.0 -
a1 +
a2) * (1.0 -
a1 +
a2);
281 const double A2 = -4.0 *
a2;
282 const double s = std::sin(0.5 * w0);
283 const double phi1 = s * s;
284 const double phi0 = 1.0 - phi1;
285 const double phi2 = 4.0 * phi0 * phi1;
287 const double G2 = G * G;
288 const double B0 = A0;
289 const double R1 = (A0 * phi0 + A1 * phi1 + A2 * phi2) * G2;
290 const double R2 = (-A0 + A1 + 4.0 * (phi0 - phi1) * A2) * G2;
291 const double B2 = (R1 - R2 * phi1 - B0) / (4.0 * phi1 * phi1);
292 const double B1 = R2 + B0 + 4.0 * (phi1 - phi0) * B2;
295 const double sqrtB0 = std::sqrt(std::max(0.0, B0));
296 const double sqrtB1 = std::sqrt(std::max(0.0, B1));
297 const double W = 0.5 * (sqrtB0 + sqrtB1);
299 c.
b0 = 0.5 * (W + std::sqrt(std::max(0.0, W * W + B2)));
300 c.
b1 = 0.5 * (sqrtB0 - sqrtB1);
301 c.
b2 = -B2 / (4.0 * c.
b0);
316 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
319 slope = std::clamp(slope, 0.0001, 1.0);
320 const double A = std::pow(10.0, gainDb / 40.0);
321 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
322 const double cosw0 = std::cos(w0);
323 const double sinw0 = std::sin(w0);
324 const double radicand = (A + 1.0 / A) * (1.0 / slope - 1.0) + 2.0;
325 const double alpha = sinw0 / 2.0 * std::sqrt(std::max(0.0, radicand));
326 const double twoSqrtAAlpha = 2.0 * std::sqrt(A) * alpha;
328 const double a0 = (A + 1.0) + (A - 1.0) * cosw0 + twoSqrtAAlpha;
330 A * ((A + 1.0) - (A - 1.0) * cosw0 + twoSqrtAAlpha),
331 2.0 * A * ((A - 1.0) - (A + 1.0) * cosw0),
332 A * ((A + 1.0) - (A - 1.0) * cosw0 - twoSqrtAAlpha),
333 -2.0 * ((A - 1.0) + (A + 1.0) * cosw0),
334 (A + 1.0) + (A - 1.0) * cosw0 - twoSqrtAAlpha);
346 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
349 slope = std::clamp(slope, 0.0001, 1.0);
350 const double A = std::pow(10.0, gainDb / 40.0);
351 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
352 const double cosw0 = std::cos(w0);
353 const double sinw0 = std::sin(w0);
354 const double radicand = (A + 1.0 / A) * (1.0 / slope - 1.0) + 2.0;
355 const double alpha = sinw0 / 2.0 * std::sqrt(std::max(0.0, radicand));
356 const double twoSqrtAAlpha = 2.0 * std::sqrt(A) * alpha;
358 const double a0 = (A + 1.0) - (A - 1.0) * cosw0 + twoSqrtAAlpha;
360 A * ((A + 1.0) + (A - 1.0) * cosw0 + twoSqrtAAlpha),
361 -2.0 * A * ((A - 1.0) + (A + 1.0) * cosw0),
362 A * ((A + 1.0) + (A - 1.0) * cosw0 - twoSqrtAAlpha),
363 2.0 * ((A - 1.0) - (A + 1.0) * cosw0),
364 (A + 1.0) - (A - 1.0) * cosw0 - twoSqrtAAlpha);
375 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
376 Q = std::max(Q, 0.001);
377 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
378 const double cosw0 = std::cos(w0);
379 const double sinw0 = std::sin(w0);
380 const double alpha = sinw0 / (2.0 * Q);
382 const double a0 = 1.0 + alpha;
399 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
400 Q = std::max(Q, 0.001);
401 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
402 const double cosw0 = std::cos(w0);
403 const double sinw0 = std::sin(w0);
404 const double alpha = sinw0 / (2.0 * Q);
406 const double a0 = 1.0 + alpha;
428 frequency = std::clamp(frequency, 1.0, std::max(1.0, sampleRate * 0.499));
429 const double w = std::tan(std::numbers::pi * frequency / sampleRate);
430 const double n = 1.0 / (1.0 + w);
435 c.
a1 = (w - 1.0) * n;
451 frequency = std::clamp(frequency, 1.0, std::max(1.0, sampleRate * 0.499));
452 const double w = std::tan(std::numbers::pi * frequency / sampleRate);
453 const double n = 1.0 / (1.0 + w);
458 c.
a1 = (w - 1.0) * n;
477 pivotFreq = std::clamp(pivotFreq, 1.0, std::max(1.0, sampleRate * 0.499));
478 const double g = std::pow(10.0, gainDb / 20.0);
479 const double sqrtG = std::sqrt(g);
480 const double c = std::tan(std::numbers::pi * pivotFreq / sampleRate);
485 const double norm = 1.0 / (1.0 + sqrtG * c);
488 coeffs.
b0 = (sqrtG + c) * norm;
489 coeffs.
b1 = (c - sqrtG) * norm;
491 coeffs.
a1 = (sqrtG * c - 1.0) * norm;
506 const double G = 3.999843853973347;
507 const double Q = 0.7071752369554196;
508 const double fc = 1681.9744509555319;
509 const double K = std::tan(std::numbers::pi * fc / sampleRate);
510 const double Vh = std::pow(10.0, G / 20.0);
511 const double Vb = std::pow(Vh, 0.4996667741545416);
512 const double a0 = 1.0 + K / Q + K * K;
514 c.
b0 = (Vh + Vb * K / Q + K * K) / a0;
515 c.
b1 = 2.0 * (K * K - Vh) / a0;
516 c.
b2 = (Vh - Vb * K / Q + K * K) / a0;
517 c.
a1 = 2.0 * (K * K - 1.0) / a0;
518 c.
a2 = (1.0 - K / Q + K * K) / a0;
531 const double Q = 0.5003270373238773;
532 const double fc = 38.13547087602444;
533 const double K = std::tan(std::numbers::pi * fc / sampleRate);
534 const double a0 = 1.0 + K / Q + K * K;
539 c.
a1 = 2.0 * (K * K - 1.0) / a0;
540 c.
a2 = (1.0 - K / Q + K * K) / a0;
558 [[nodiscard]]
double getMagnitude(
double frequency,
double sampleRate)
const noexcept
560 const double w = 2.0 * std::numbers::pi * frequency / sampleRate;
561 const double cosW = std::cos(w);
562 const double cos2W = std::cos(2.0 * w);
563 const double sinW = std::sin(w);
564 const double sin2W = std::sin(2.0 * w);
566 const double nRe =
b0 +
b1 * cosW +
b2 * cos2W;
567 const double nIm = -
b1 * sinW -
b2 * sin2W;
568 const double dRe = 1.0 +
a1 * cosW +
a2 * cos2W;
569 const double dIm = -
a1 * sinW -
a2 * sin2W;
571 const double numMag2 = nRe * nRe + nIm * nIm;
572 const double denMag2 = dRe * dRe + dIm * dIm;
574 return (denMag2 > 1e-30) ? std::sqrt(numMag2 / denMag2) : 0.0;
589 template <
typename U>
591 std::span<U> magnitudes,
592 double sampleRate)
const noexcept
594 assert(magnitudes.size() >= frequencies.size());
595 for (
size_t i = 0; i < frequencies.size(); ++i)
596 magnitudes[i] =
static_cast<U
>(
getMagnitude(
static_cast<double>(frequencies[i]), sampleRate));
604 [[nodiscard]]
static BiquadCoeffs normalise(
double a0,
double b0r,
double b1r,
double b2r,
605 double a1r,
double a2r)
noexcept
607 const double invA0 = 1.0 / a0;
608 return { b0r * invA0, b1r * invA0, b2r * invA0, a1r * invA0, a2r * invA0 };
659template <
typename T,
int MaxChannels = 8>
673 : activeCoeffs_(other.activeCoeffs_),
674 coeffsDirty_(other.coeffsDirty_.load(std::memory_order_relaxed)),
678 copyStagedRelaxed(other);
683 if (
this == &other)
return *
this;
684 activeCoeffs_ = other.activeCoeffs_;
685 copyStagedRelaxed(other);
686 coeffsDirty_.store(other.coeffsDirty_.load(std::memory_order_relaxed),
687 std::memory_order_relaxed);
688 coeffsSeq_.store(0, std::memory_order_relaxed);
689 state_ = other.state_;
728 coeffsSeq_.fetch_add(1, std::memory_order_acq_rel);
729 std::atomic_thread_fence(std::memory_order_release);
730 stagedCoeffs_.b0.store(c.b0, std::memory_order_relaxed);
731 stagedCoeffs_.b1.store(c.b1, std::memory_order_relaxed);
732 stagedCoeffs_.b2.store(c.b2, std::memory_order_relaxed);
733 stagedCoeffs_.a1.store(c.a1, std::memory_order_relaxed);
734 stagedCoeffs_.a2.store(c.a2, std::memory_order_relaxed);
735 coeffsSeq_.fetch_add(1, std::memory_order_release);
736 coeffsDirty_.store(
true, std::memory_order_release);
796 if (!coeffsDirty_.exchange(
false, std::memory_order_acquire))
return false;
797 return adoptStagedCoeffs();
813 for (
auto& s : state_)
859 assert(channel >= 0 && channel < MaxChannels &&
"Channel index out of bounds");
865 if (coeffsDirty_.load(std::memory_order_relaxed)) [[unlikely]]
868 auto& s = state_[channel];
870 const double output = activeCoeffs_.
b0 * input + s.z1;
871 s.z1 = activeCoeffs_.
b1 * input - activeCoeffs_.
a1 * output + s.z2;
872 s.z2 = activeCoeffs_.
b2 * input - activeCoeffs_.
a2 * output;
900 const int numChannels = std::min(buffer.getNumChannels(), MaxChannels);
901 const int numSamples = buffer.getNumSamples();
907 for (
int ch = 0; ch < numChannels; ++ch)
909 T* data = buffer.getChannel(ch);
910 auto& s = state_[ch];
914 for (
int i = 0; i < numSamples; ++i)
916 const double input =
static_cast<double>(data[i]);
917 const double output = c.
b0 * input + z1;
918 z1 = c.
b1 * input - c.
a1 * output + z2;
919 z2 = c.
b2 * input - c.
a2 * output;
920 data[i] =
static_cast<T
>(output);
934 static constexpr int kSeqlockMaxAttempts = 3;
964 DSPARK_NOINLINE
bool adoptStagedCoeffs() noexcept
967 bool adopted =
false;
968 for (
int attempt = 0; attempt < kSeqlockMaxAttempts; ++attempt)
970 const unsigned s0 = coeffsSeq_.load(std::memory_order_acquire);
976 if ((s0 & 1u) != 0u)
continue;
981 tmp.
b0 = stagedCoeffs_.b0.load(std::memory_order_relaxed);
982 tmp.
b1 = stagedCoeffs_.b1.load(std::memory_order_relaxed);
983 tmp.
b2 = stagedCoeffs_.b2.load(std::memory_order_relaxed);
984 tmp.
a1 = stagedCoeffs_.a1.load(std::memory_order_relaxed);
985 tmp.
a2 = stagedCoeffs_.a2.load(std::memory_order_relaxed);
993 std::atomic_thread_fence(std::memory_order_acquire);
994 if (s0 == coeffsSeq_.load(std::memory_order_relaxed))
1003 coeffsDirty_.store(
true, std::memory_order_release);
1006 activeCoeffs_ = tmp;
1031 std::atomic<double> b0{1.0}, b1{0.0}, b2{0.0};
1032 std::atomic<double> a1{0.0}, a2{0.0};
1036 void copyStagedRelaxed(
const Biquad& other)
noexcept
1038 stagedCoeffs_.b0.store(other.stagedCoeffs_.b0.load(std::memory_order_relaxed),
1039 std::memory_order_relaxed);
1040 stagedCoeffs_.b1.store(other.stagedCoeffs_.b1.load(std::memory_order_relaxed),
1041 std::memory_order_relaxed);
1042 stagedCoeffs_.b2.store(other.stagedCoeffs_.b2.load(std::memory_order_relaxed),
1043 std::memory_order_relaxed);
1044 stagedCoeffs_.a1.store(other.stagedCoeffs_.a1.load(std::memory_order_relaxed),
1045 std::memory_order_relaxed);
1046 stagedCoeffs_.a2.store(other.stagedCoeffs_.a2.load(std::memory_order_relaxed),
1047 std::memory_order_relaxed);
1050 BiquadCoeffs activeCoeffs_ {};
1051 StagedCoeffs stagedCoeffs_ {};
1052 std::atomic<bool> coeffsDirty_{
false};
1053 std::atomic<unsigned> coeffsSeq_{0};
1055 std::array<State, MaxChannels> state_ {};
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 (control thread).
void setCoeffsNow(const BiquadCoeffs &c) noexcept
Stream-owner direct set: makes c the active set immediately.
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
Analog-matched ("de-cramped") peaking filter (Vicanek 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.
static BiquadCoeffs makeKWeightingHighPass(double sampleRate) noexcept
ITU-R BS.1770 K-weighting, stage 2: the RLB high-pass.
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 makeKWeightingShelf(double sampleRate) noexcept
ITU-R BS.1770 K-weighting, stage 1: the head-related high shelf.
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.