DSPark 1.6.1
Header-only audio DSP framework in pure C++20 — zero dependencies
Loading...
Searching...
No Matches
Biquad.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
52#include <algorithm>
53#include <array>
54#include <cmath>
55#include <numbers>
56#include <span>
57#include <atomic>
58#include <cassert>
59
60#include "AudioBuffer.h"
61#include "DenormalGuard.h"
62
63namespace dspark {
64
65// ============================================================================
66// BiquadCoeffs -- Coefficient storage + factory methods
67// ============================================================================
68
90struct alignas(32) BiquadCoeffs
91{
92 double b0 = 1.0, b1 = 0.0, b2 = 0.0;
93 double a1 = 0.0, a2 = 0.0;
94
95 // -- Factory methods (Audio EQ Cookbook) ----------------------------------
96
103 [[nodiscard]] static BiquadCoeffs makeLowPass(double sampleRate, double freq, double Q = 0.7071067811865476) noexcept
104 {
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);
111
112 const double a0 = 1.0 + alpha;
113 return normalise(a0,
114 (1.0 - cosw0) / 2.0,
115 1.0 - cosw0,
116 (1.0 - cosw0) / 2.0,
117 -2.0 * cosw0,
118 1.0 - alpha);
119 }
120
127 [[nodiscard]] static BiquadCoeffs makeHighPass(double sampleRate, double freq, double Q = 0.7071067811865476) noexcept
128 {
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);
135
136 const double a0 = 1.0 + alpha;
137 return normalise(a0,
138 (1.0 + cosw0) / 2.0,
139 -(1.0 + cosw0),
140 (1.0 + cosw0) / 2.0,
141 -2.0 * cosw0,
142 1.0 - alpha);
143 }
144
156 [[nodiscard]] static BiquadCoeffs makeBandPass(double sampleRate, double freq, double Q = 0.7071067811865476) noexcept
157 {
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);
164
165 const double a0 = 1.0 + alpha;
166 return normalise(a0,
167 alpha,
168 0.0,
169 -alpha,
170 -2.0 * cosw0,
171 1.0 - alpha);
172 }
173
185 [[nodiscard]] static BiquadCoeffs makePeak(double sampleRate, double freq, double Q, double gainDb) noexcept
186 {
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); // dB/40 = sqrt of linear gain
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);
194
195 const double a0 = 1.0 + alpha / A;
196 return normalise(a0,
197 1.0 + alpha * A,
198 -2.0 * cosw0,
199 1.0 - alpha * A,
200 -2.0 * cosw0,
201 1.0 - alpha / A);
202 }
203
223 [[nodiscard]] static BiquadCoeffs makePeakMatched(double sampleRate, double freq,
224 double Q, double gainDb) noexcept
225 {
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 };
230
231 const double G0 = 1.0; // reference gain
232 const double G = std::pow(10.0, gainDb / 20.0); // peak gain
233 const double GB = std::pow(10.0, gainDb / 40.0); // bandwidth gain (half-dB)
234
235 const double w0 = 2.0 * std::numbers::pi * freq / sampleRate;
236 const double Dw = w0 / Q;
237
238 // Analog prototype gain at the physical Nyquist frequency:
239 // |Ha(jW)|^2 = (G0^2 (W^2-W0^2)^2 + G^2 Dw^2 W^2) /
240 // ( (W^2-W0^2)^2 + Dw^2 W^2), W in rad/s.
241 const double W0 = 2.0 * std::numbers::pi * freq;
242 const double DW = W0 / Q;
243 const double Wn = std::numbers::pi * sampleRate; // 2*pi*fs/2
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));
247
248 // Orfanidis closed-form coefficients.
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);
257
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);
261
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));
266
267 const double a0 = 1.0 + W2 + A;
268 return normalise(a0,
269 G1 + G0 * W2 + B,
270 -2.0 * (G1 - G0 * W2),
271 G1 + G0 * W2 - B,
272 -2.0 * (1.0 - W2),
273 1.0 + W2 - A);
274 }
275
283 [[nodiscard]] static BiquadCoeffs makeLowShelf(double sampleRate, double freq, double gainDb, double slope = 1.0) noexcept
284 {
285 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
286 // Shelf slope S must stay in (0, 1]: for S > 1 the radicand below goes
287 // negative and std::sqrt yields NaN coefficients that poison the audio.
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;
296
297 const double a0 = (A + 1.0) + (A - 1.0) * cosw0 + twoSqrtAAlpha;
298 return normalise(a0,
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);
304 }
305
313 [[nodiscard]] static BiquadCoeffs makeHighShelf(double sampleRate, double freq, double gainDb, double slope = 1.0) noexcept
314 {
315 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
316 // Shelf slope S must stay in (0, 1]: for S > 1 the radicand below goes
317 // negative and std::sqrt yields NaN coefficients that poison the audio.
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;
326
327 const double a0 = (A + 1.0) - (A - 1.0) * cosw0 + twoSqrtAAlpha;
328 return normalise(a0,
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);
334 }
335
342 [[nodiscard]] static BiquadCoeffs makeNotch(double sampleRate, double freq, double Q = 0.7071067811865476) noexcept
343 {
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);
350
351 const double a0 = 1.0 + alpha;
352 return normalise(a0,
353 1.0,
354 -2.0 * cosw0,
355 1.0,
356 -2.0 * cosw0,
357 1.0 - alpha);
358 }
359
366 [[nodiscard]] static BiquadCoeffs makeAllPass(double sampleRate, double freq, double Q = 0.7071067811865476) noexcept
367 {
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);
374
375 const double a0 = 1.0 + alpha;
376 return normalise(a0,
377 1.0 - alpha,
378 -2.0 * cosw0,
379 1.0 + alpha,
380 -2.0 * cosw0,
381 1.0 - alpha);
382 }
383
384 // -- First-order filter factory methods ------------------------------------
385
395 [[nodiscard]] static BiquadCoeffs makeFirstOrderLowPass(double sampleRate, double frequency) noexcept
396 {
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);
400 BiquadCoeffs c;
401 c.b0 = w * n;
402 c.b1 = w * n;
403 c.b2 = 0.0;
404 c.a1 = (w - 1.0) * n;
405 c.a2 = 0.0;
406 return c;
407 }
408
418 [[nodiscard]] static BiquadCoeffs makeFirstOrderHighPass(double sampleRate, double frequency) noexcept
419 {
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);
423 BiquadCoeffs c;
424 c.b0 = n;
425 c.b1 = -n;
426 c.b2 = 0.0;
427 c.a1 = (w - 1.0) * n;
428 c.a2 = 0.0;
429 return c;
430 }
431
444 [[nodiscard]] static BiquadCoeffs makeTilt(double sampleRate, double pivotFreq, double gainDb) noexcept
445 {
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);
450
451 // First-order tilt shelf via bilinear transform:
452 // DC gain = 1/sqrt(g), pivot gain = 1, Nyquist gain = sqrt(g)
453 // Total swing = gainDb (half below pivot, half above).
454 const double norm = 1.0 / (1.0 + sqrtG * c);
455
456 BiquadCoeffs coeffs;
457 coeffs.b0 = (sqrtG + c) * norm;
458 coeffs.b1 = (c - sqrtG) * norm;
459 coeffs.b2 = 0.0;
460 coeffs.a1 = (sqrtG * c - 1.0) * norm;
461 coeffs.a2 = 0.0;
462 return coeffs;
463 }
464
465 // -- Frequency response analysis -------------------------------------------
466
479 [[nodiscard]] double getMagnitude(double frequency, double sampleRate) const noexcept
480 {
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);
486
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;
491
492 const double numMag2 = nRe * nRe + nIm * nIm;
493 const double denMag2 = dRe * dRe + dIm * dIm;
494
495 return (denMag2 > 1e-30) ? std::sqrt(numMag2 / denMag2) : 0.0;
496 }
497
510 template <typename U>
511 void getMagnitudeForFrequencyArray(std::span<const U> frequencies,
512 std::span<U> magnitudes,
513 double sampleRate) const noexcept
514 {
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));
518 }
519
520private:
525 [[nodiscard]] static BiquadCoeffs normalise(double a0, double b0r, double b1r, double b2r,
526 double a1r, double a2r) noexcept
527 {
528 const double invA0 = 1.0 / a0;
529 return { b0r * invA0, b1r * invA0, b2r * invA0, a1r * invA0, a2r * invA0 };
530 }
531};
532
533// ============================================================================
534// Biquad -- Filter processor with per-channel state
535// ============================================================================
536
554template <typename T, int MaxChannels = 8>
555class alignas(32) Biquad
556{
557public:
558 Biquad() noexcept = default;
559
560 // std::atomic<bool> makes the default copy/move ops vanish. Provide manual
561 // move semantics so this class can live inside std::array / std::vector
562 // (e.g. CrossoverFilter::AllPassChain). Copying is left deleted on
563 // purpose: it would imply two threads observing the same staged coefficient
564 // dirty flag, which is semantically ambiguous and not needed in practice.
565 // Moves only happen during setup (single-threaded relocation into a
566 // container), so the seqlock counter is simply reset to an even value.
567 Biquad(Biquad&& other) noexcept
568 : activeCoeffs_(other.activeCoeffs_),
569 stagedCoeffs_(other.stagedCoeffs_),
570 coeffsDirty_(other.coeffsDirty_.load(std::memory_order_relaxed)),
571 coeffsSeq_(0),
572 state_(other.state_)
573 {}
574
575 Biquad& operator=(Biquad&& other) noexcept
576 {
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_;
584 return *this;
585 }
586
587 Biquad(const Biquad&) = delete;
588 Biquad& operator=(const Biquad&) = delete;
589
599 void setCoeffs(const BiquadCoeffs& c) noexcept
600 {
601 // Seqlock publish (single producer). An odd sequence number marks a
602 // write in progress; the audio thread retries its read while odd or if
603 // the counter moved, so a concurrent update can never be observed as a
604 // torn coefficient set (mixing a1 of one filter with a2 of another).
605 //
606 // Standards note: the reader's struct copy races with this write in the
607 // strict C++ memory model (classic seqlock caveat; the Linux kernel and
608 // mainstream audio frameworks rely on the same pattern). The retry loop
609 // discards any value read during a write, and the acquire/release fences
610 // order the accesses on every supported compiler/architecture.
611 coeffsSeq_.fetch_add(1, std::memory_order_acq_rel); // -> odd
612 stagedCoeffs_ = c;
613 coeffsSeq_.fetch_add(1, std::memory_order_release); // -> even
614 coeffsDirty_.store(true, std::memory_order_release);
615 }
616
631 bool applyPendingCoeffs() noexcept
632 {
633 if (coeffsDirty_.exchange(false, std::memory_order_acquire))
634 {
635 // Seqlock read: retry on a torn/in-progress publish. The writer's
636 // critical section is a 5-double copy, so this converges in at most
637 // a couple of iterations even under a busy GUI thread.
638 BiquadCoeffs tmp;
639 unsigned s0, s1;
640 do {
641 s0 = coeffsSeq_.load(std::memory_order_acquire);
642 tmp = stagedCoeffs_;
643 // The fence keeps the copy above from sinking below the
644 // re-read of the counter. A plain acquire load only orders
645 // LATER accesses; without the fence, both the compiler and
646 // weakly-ordered CPUs (ARM) may complete the copy after s1
647 // is read, and a torn copy would pass the s0 == s1 check.
648 std::atomic_thread_fence(std::memory_order_acquire);
649 s1 = coeffsSeq_.load(std::memory_order_relaxed);
650 } while ((s0 & 1u) != 0u || s0 != s1);
651 activeCoeffs_ = tmp;
652 return true;
653 }
654 return false;
655 }
656
665 [[nodiscard]] const BiquadCoeffs& getCoeffs() const noexcept { return activeCoeffs_; }
666
668 void reset() noexcept
669 {
670 for (auto& s : state_)
671 s = {};
672 }
673
694 T processSample(T input, int channel) noexcept
695 {
696 return static_cast<T>(processSampleCore(static_cast<double>(input), channel));
697 }
698
714 double processSampleCore(double input, int channel) noexcept
715 {
716 assert(channel >= 0 && channel < MaxChannels && "Channel index out of bounds");
717
718 // Lock-free fast path: relaxed load is free on every modern CPU.
719 // Branch is marked unlikely so the compiler keeps the hot DSP path
720 // straight-line; the dirty flag is only true on the first sample
721 // of a block where setCoeffs() landed since last process call.
722 if (coeffsDirty_.load(std::memory_order_relaxed)) [[unlikely]]
724
725 auto& s = state_[channel];
726
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;
730
731 return output;
732 }
733
750 void processBlock(AudioBufferView<T> buffer) noexcept
751 {
752 DenormalGuard guard;
753
754 // Pick up any pending lock-free coefficient update from the GUI thread.
756
757 const int numChannels = std::min(buffer.getNumChannels(), MaxChannels);
758 const int numSamples = buffer.getNumSamples();
759
760 // Block-local coefficient copy: stable for the whole block by design
761 // (updates land at the next block boundary), and register-friendly.
762 const BiquadCoeffs c = activeCoeffs_;
763
764 for (int ch = 0; ch < numChannels; ++ch)
765 {
766 T* data = buffer.getChannel(ch);
767 auto& s = state_[ch];
768 double z1 = s.z1;
769 double z2 = s.z2;
770
771 for (int i = 0; i < numSamples; ++i)
772 {
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);
778 }
779
780 s.z1 = z1;
781 s.z2 = z2;
782 }
783 }
784
785private:
786 // Kept compact on purpose (no over-alignment): the states are only ever
787 // touched by the processing thread, sequentially per channel, so packing
788 // adjacent channels into the same cache line is strictly better than
789 // padding each one out to its own.
790 struct State
791 {
792 double z1 = 0.0;
793 double z2 = 0.0;
794 };
795
796 BiquadCoeffs activeCoeffs_ {};
797 BiquadCoeffs stagedCoeffs_ {};
798 std::atomic<bool> coeffsDirty_{false};
799 std::atomic<unsigned> coeffsSeq_{0};
800
801 std::array<State, MaxChannels> state_ {};
802};
803
804} // namespace dspark
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.
Definition AudioBuffer.h:50
Biquad filter using Transposed Direct Form II (TDF-II) with thread-safe updates.
Definition Biquad.h:556
Biquad(const Biquad &)=delete
void setCoeffs(const BiquadCoeffs &c) noexcept
Sets the filter coefficients asynchronously.
Definition Biquad.h:599
void reset() noexcept
Resets all per-channel filter states to zero to avoid ringing/clicks.
Definition Biquad.h:668
const BiquadCoeffs & getCoeffs() const noexcept
Returns the active coefficient set currently in use by the DSP thread.
Definition Biquad.h:665
bool applyPendingCoeffs() noexcept
Promotes any pending staged coefficients to active.
Definition Biquad.h:631
Biquad & operator=(Biquad &&other) noexcept
Definition Biquad.h:575
void processBlock(AudioBufferView< T > buffer) noexcept
Processes a full audio buffer in-place.
Definition Biquad.h:750
Biquad & operator=(const Biquad &)=delete
T processSample(T input, int channel) noexcept
Processes a single sample for a specific channel.
Definition Biquad.h:694
double processSampleCore(double input, int channel) noexcept
One recursion step in the core's own precision (double).
Definition Biquad.h:714
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.
Definition Biquad.h:91
static BiquadCoeffs makeFirstOrderHighPass(double sampleRate, double frequency) noexcept
First-order (6 dB/oct) high-pass filter.
Definition Biquad.h:418
static BiquadCoeffs makePeakMatched(double sampleRate, double freq, double Q, double gainDb) noexcept
Peaking filter with prescribed Nyquist gain (Orfanidis design).
Definition Biquad.h:223
void getMagnitudeForFrequencyArray(std::span< const U > frequencies, std::span< U > magnitudes, double sampleRate) const noexcept
Computes magnitude responses for a batch of frequencies.
Definition Biquad.h:511
static BiquadCoeffs makeHighPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
High-pass filter.
Definition Biquad.h:127
static BiquadCoeffs makeAllPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
All-pass filter.
Definition Biquad.h:366
static BiquadCoeffs makeBandPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
Band-pass filter (constant 0 dB peak gain).
Definition Biquad.h:156
static BiquadCoeffs makePeak(double sampleRate, double freq, double Q, double gainDb) noexcept
Peak (parametric EQ) filter.
Definition Biquad.h:185
double getMagnitude(double frequency, double sampleRate) const noexcept
Evaluates magnitude response |H(f)| at a single frequency.
Definition Biquad.h:479
static BiquadCoeffs makeFirstOrderLowPass(double sampleRate, double frequency) noexcept
First-order (6 dB/oct) low-pass filter.
Definition Biquad.h:395
static BiquadCoeffs makeTilt(double sampleRate, double pivotFreq, double gainDb) noexcept
Creates a first-order tilt filter.
Definition Biquad.h:444
static BiquadCoeffs makeLowPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
Low-pass filter.
Definition Biquad.h:103
static BiquadCoeffs makeLowShelf(double sampleRate, double freq, double gainDb, double slope=1.0) noexcept
Low-shelf filter.
Definition Biquad.h:283
static BiquadCoeffs makeHighShelf(double sampleRate, double freq, double gainDb, double slope=1.0) noexcept
High-shelf filter.
Definition Biquad.h:313
static BiquadCoeffs makeNotch(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
Notch (band-reject) filter.
Definition Biquad.h:342