437 lines
17 KiB
C++
437 lines
17 KiB
C++
#include "./common.h"
|
|
|
|
#ifndef SIGNALSMITH_DSP_FILTERS_H
|
|
#define SIGNALSMITH_DSP_FILTERS_H
|
|
|
|
#include "./perf.h"
|
|
|
|
#include <cmath>
|
|
#include <complex>
|
|
|
|
namespace signalsmith {
|
|
namespace filters {
|
|
/** @defgroup Filters Basic filters
|
|
@brief Classes for some common filter types
|
|
|
|
@{
|
|
@file
|
|
*/
|
|
|
|
/** Filter design methods.
|
|
These differ mostly in how they handle frequency-warping near Nyquist:
|
|
\diagram{filters-lowpass.svg}
|
|
\diagram{filters-highpass.svg}
|
|
\diagram{filters-peak.svg}
|
|
\diagram{filters-bandpass.svg}
|
|
\diagram{filters-notch.svg}
|
|
\diagram{filters-high-shelf.svg}
|
|
\diagram{filters-low-shelf.svg}
|
|
\diagram{filters-allpass.svg}
|
|
*/
|
|
enum class BiquadDesign {
|
|
bilinear, ///< Bilinear transform, adjusting for centre frequency but not bandwidth
|
|
cookbook, ///< RBJ's "Audio EQ Cookbook". Based on `bilinear`, adjusting bandwidth (for peak/notch/bandpass) to preserve the ratio between upper/lower boundaries. This performs oddly near Nyquist.
|
|
oneSided, ///< Based on `bilinear`, adjusting bandwidth to preserve the lower boundary (leaving the upper one loose).
|
|
vicanek ///< From Martin Vicanek's [Matched Second Order Digital Filters](https://vicanek.de/articles/BiquadFits.pdf). Falls back to `oneSided` for shelf and allpass filters. This takes the poles from the impulse-invariant approach, and then picks the zeros to create a better match. This means that Nyquist is not 0dB for peak/notch (or -Inf for lowpass), but it is a decent match to the analogue prototype.
|
|
};
|
|
|
|
/** A standard biquad.
|
|
|
|
This is not guaranteed to be stable if modulated at audio rate.
|
|
|
|
The default highpass/lowpass bandwidth (`defaultBandwidth`) produces a Butterworth filter when bandwidth-compensation is disabled.
|
|
|
|
Bandwidth compensation defaults to `BiquadDesign::oneSided` (or `BiquadDesign::cookbook` if `cookbookBandwidth` is enabled) for all filter types aside from highpass/lowpass (which use `BiquadDesign::bilinear`).*/
|
|
template<typename Sample, bool cookbookBandwidth=false>
|
|
class BiquadStatic {
|
|
static constexpr BiquadDesign bwDesign = cookbookBandwidth ? BiquadDesign::cookbook : BiquadDesign::oneSided;
|
|
Sample a1 = 0, a2 = 0, b0 = 1, b1 = 0, b2 = 0;
|
|
Sample x1 = 0, x2 = 0, y1 = 0, y2 = 0;
|
|
|
|
enum class Type {highpass, lowpass, highShelf, lowShelf, bandpass, notch, peak, allpass};
|
|
|
|
struct FreqSpec {
|
|
double scaledFreq;
|
|
double w0, sinW0, cosW0;
|
|
double inv2Q;
|
|
|
|
FreqSpec(double freq, BiquadDesign design) {
|
|
scaledFreq = std::max(1e-6, std::min(0.4999, freq));
|
|
if (design == BiquadDesign::cookbook) {
|
|
scaledFreq = std::min(0.45, scaledFreq);
|
|
}
|
|
w0 = 2*M_PI*scaledFreq;
|
|
cosW0 = std::cos(w0);
|
|
sinW0 = std::sin(w0);
|
|
}
|
|
|
|
void oneSidedCompQ() {
|
|
// Ratio between our (digital) lower boundary f1 and centre f0
|
|
double f1Factor = std::sqrt(inv2Q*inv2Q + 1) - inv2Q;
|
|
// Bilinear means discrete-time freq f = continuous-time freq tan(pi*xf/pi)
|
|
double ctF1 = std::tan(M_PI*scaledFreq*f1Factor), invCtF0 = (1 + cosW0)/sinW0;
|
|
double ctF1Factor = ctF1*invCtF0;
|
|
inv2Q = 0.5/ctF1Factor - 0.5*ctF1Factor;
|
|
}
|
|
};
|
|
SIGNALSMITH_INLINE static FreqSpec octaveSpec(double scaledFreq, double octaves, BiquadDesign design) {
|
|
FreqSpec spec(scaledFreq, design);
|
|
|
|
if (design == BiquadDesign::cookbook) {
|
|
// Approximately preserves bandwidth between halfway points
|
|
octaves *= spec.w0/spec.sinW0;
|
|
}
|
|
spec.inv2Q = std::sinh(std::log(2)*0.5*octaves); // 1/(2Q)
|
|
if (design == BiquadDesign::oneSided) spec.oneSidedCompQ();
|
|
return spec;
|
|
}
|
|
SIGNALSMITH_INLINE static FreqSpec qSpec(double scaledFreq, double q, BiquadDesign design) {
|
|
FreqSpec spec(scaledFreq, design);
|
|
|
|
spec.inv2Q = 0.5/q;
|
|
if (design == BiquadDesign::oneSided) spec.oneSidedCompQ();
|
|
return spec;
|
|
}
|
|
|
|
SIGNALSMITH_INLINE double dbToSqrtGain(double db) {
|
|
return std::pow(10, db*0.025);
|
|
}
|
|
|
|
SIGNALSMITH_INLINE BiquadStatic & configure(Type type, FreqSpec calc, double sqrtGain, BiquadDesign design) {
|
|
double w0 = calc.w0;
|
|
|
|
if (design == BiquadDesign::vicanek) {
|
|
if (type == Type::notch) { // Heuristic for notches near Nyquist
|
|
calc.inv2Q *= (1 - calc.scaledFreq*0.5);
|
|
}
|
|
double Q = (type == Type::peak ? 0.5*sqrtGain : 0.5)/calc.inv2Q;
|
|
double q = (type == Type::peak ? 1/sqrtGain : 1)*calc.inv2Q;
|
|
double expmqw = std::exp(-q*w0);
|
|
double da1, da2;
|
|
if (q <= 1) {
|
|
a1 = da1 = -2*expmqw*std::cos(std::sqrt(1 - q*q)*w0);
|
|
} else {
|
|
a1 = da1 = -2*expmqw*std::cosh(std::sqrt(q*q - 1)*w0);
|
|
}
|
|
a2 = da2 = expmqw*expmqw;
|
|
double sinpd2 = std::sin(w0/2);
|
|
double p0 = 1 - sinpd2*sinpd2, p1 = sinpd2*sinpd2, p2 = 4*p0*p1;
|
|
double A0 = 1 + da1 + da2, A1 = 1 - da1 + da2, A2 = -4*da2;
|
|
A0 *= A0;
|
|
A1 *= A1;
|
|
if (type == Type::lowpass) {
|
|
double R1 = (A0*p0 + A1*p1 + A2*p2)*Q*Q;
|
|
double B0 = A0, B1 = (R1 - B0*p0)/p1;
|
|
b0 = 0.5*(std::sqrt(B0) + std::sqrt(std::max(0.0, B1)));
|
|
b1 = std::sqrt(B0) - b0;
|
|
b2 = 0;
|
|
return *this;
|
|
} else if (type == Type::highpass) {
|
|
b2 = b0 = std::sqrt(A0*p0 + A1*p1 + A2*p2)*Q/(4*p1);
|
|
b1 = -2*b0;
|
|
return *this;
|
|
} else if (type == Type::bandpass) {
|
|
double R1 = A0*p0 + A1*p1 + A2*p2;
|
|
double R2 = -A0 + A1 + 4*(p0 - p1)*A2;
|
|
double B2 = (R1 - R2*p1)/(4*p1*p1);
|
|
double B1 = R2 + 4*(p1 - p0)*B2;
|
|
b1 = -0.5*std::sqrt(std::max(0.0, B1));
|
|
b0 = 0.5*(std::sqrt(std::max(0.0, B2 + 0.25*B1)) - b1);
|
|
b2 = -b0 - b1;
|
|
return *this;
|
|
} else if (type == Type::notch) {
|
|
// The Vicanek paper doesn't cover notches (band-stop), but we know where the zeros should be:
|
|
b0 = 1;
|
|
double db1 = -2*std::cos(w0); // might be higher precision
|
|
b1 = db1;
|
|
b2 = 1;
|
|
// Scale so that B0 == A0 to get 0dB at f=0
|
|
double scale = std::sqrt(A0)/(b0 + db1 + b2);
|
|
b0 *= scale;
|
|
b1 *= scale;
|
|
b2 *= scale;
|
|
return *this;
|
|
} else if (type == Type::peak) {
|
|
double G2 = (sqrtGain*sqrtGain)*(sqrtGain*sqrtGain);
|
|
double R1 = (A0*p0 + A1*p1 + A2*p2)*G2;
|
|
double R2 = (-A0 + A1 + 4*(p0 - p1)*A2)*G2;
|
|
double B0 = A0;
|
|
double B2 = (R1 - R2*p1 - B0)/(4*p1*p1);
|
|
double B1 = R2 + B0 + 4*(p1 - p0)*B2;
|
|
double W = 0.5*(std::sqrt(B0) + std::sqrt(std::max(0.0, B1)));
|
|
b0 = 0.5*(W + std::sqrt(std::max(0.0, W*W + B2)));
|
|
b1 = 0.5*(std::sqrt(B0) - std::sqrt(std::max(0.0, B1)));
|
|
b2 = -B2/(4*b0);
|
|
return *this;
|
|
}
|
|
// All others fall back to `oneSided`
|
|
design = BiquadDesign::oneSided;
|
|
calc.oneSidedCompQ();
|
|
}
|
|
|
|
double alpha = calc.sinW0*calc.inv2Q;
|
|
double A = sqrtGain, sqrtA2alpha = 2*std::sqrt(A)*alpha;
|
|
|
|
double a0;
|
|
if (type == Type::highpass) {
|
|
b1 = -1 - calc.cosW0;
|
|
b0 = b2 = (1 + calc.cosW0)*0.5;
|
|
a0 = 1 + alpha;
|
|
a1 = -2*calc.cosW0;
|
|
a2 = 1 - alpha;
|
|
} else if (type == Type::lowpass) {
|
|
b1 = 1 - calc.cosW0;
|
|
b0 = b2 = b1*0.5;
|
|
a0 = 1 + alpha;
|
|
a1 = -2*calc.cosW0;
|
|
a2 = 1 - alpha;
|
|
} else if (type == Type::highShelf) {
|
|
b0 = A*((A+1)+(A-1)*calc.cosW0+sqrtA2alpha);
|
|
b2 = A*((A+1)+(A-1)*calc.cosW0-sqrtA2alpha);
|
|
b1 = -2*A*((A-1)+(A+1)*calc.cosW0);
|
|
a0 = (A+1)-(A-1)*calc.cosW0+sqrtA2alpha;
|
|
a2 = (A+1)-(A-1)*calc.cosW0-sqrtA2alpha;
|
|
a1 = 2*((A-1)-(A+1)*calc.cosW0);
|
|
} else if (type == Type::lowShelf) {
|
|
b0 = A*((A+1)-(A-1)*calc.cosW0+sqrtA2alpha);
|
|
b2 = A*((A+1)-(A-1)*calc.cosW0-sqrtA2alpha);
|
|
b1 = 2*A*((A-1)-(A+1)*calc.cosW0);
|
|
a0 = (A+1)+(A-1)*calc.cosW0+sqrtA2alpha;
|
|
a2 = (A+1)+(A-1)*calc.cosW0-sqrtA2alpha;
|
|
a1 = -2*((A-1)+(A+1)*calc.cosW0);
|
|
} else if (type == Type::bandpass) {
|
|
b0 = alpha;
|
|
b1 = 0;
|
|
b2 = -alpha;
|
|
a0 = 1 + alpha;
|
|
a1 = -2*calc.cosW0;
|
|
a2 = 1 - alpha;
|
|
} else if (type == Type::notch) {
|
|
b0 = 1;
|
|
b1 = -2*calc.cosW0;
|
|
b2 = 1;
|
|
a0 = 1 + alpha;
|
|
a1 = b1;
|
|
a2 = 1 - alpha;
|
|
} else if (type == Type::peak) {
|
|
b0 = 1 + alpha*A;
|
|
b1 = -2*calc.cosW0;
|
|
b2 = 1 - alpha*A;
|
|
a0 = 1 + alpha/A;
|
|
a1 = b1;
|
|
a2 = 1 - alpha/A;
|
|
} else if (type == Type::allpass) {
|
|
a0 = b2 = 1 + alpha;
|
|
a1 = b1 = -2*calc.cosW0;
|
|
a2 = b0 = 1 - alpha;
|
|
} else {
|
|
// reset to neutral
|
|
a1 = a2 = b1 = b2 = 0;
|
|
a0 = b0 = 1;
|
|
}
|
|
double invA0 = 1/a0;
|
|
b0 *= invA0;
|
|
b1 *= invA0;
|
|
b2 *= invA0;
|
|
a1 *= invA0;
|
|
a2 *= invA0;
|
|
return *this;
|
|
}
|
|
public:
|
|
static constexpr double defaultQ = 0.7071067811865476; // sqrt(0.5)
|
|
static constexpr double defaultBandwidth = 1.8999686269529916; // equivalent to above Q
|
|
|
|
Sample operator ()(Sample x0) {
|
|
Sample y0 = x0*b0 + x1*b1 + x2*b2 - y1*a1 - y2*a2;
|
|
y2 = y1;
|
|
y1 = y0;
|
|
x2 = x1;
|
|
x1 = x0;
|
|
return y0;
|
|
}
|
|
|
|
void reset() {
|
|
x1 = x2 = y1 = y2 = 0;
|
|
}
|
|
|
|
std::complex<Sample> response(Sample scaledFreq) const {
|
|
Sample w = scaledFreq*Sample(2*M_PI);
|
|
std::complex<Sample> invZ = {std::cos(w), -std::sin(w)}, invZ2 = invZ*invZ;
|
|
return (b0 + invZ*b1 + invZ2*b2)/(Sample(1) + invZ*a1 + invZ2*a2);
|
|
}
|
|
Sample responseDb(Sample scaledFreq) const {
|
|
Sample w = scaledFreq*Sample(2*M_PI);
|
|
std::complex<Sample> invZ = {std::cos(w), -std::sin(w)}, invZ2 = invZ*invZ;
|
|
Sample energy = std::norm(b0 + invZ*b1 + invZ2*b2)/std::norm(Sample(1) + invZ*a1 + invZ2*a2);
|
|
return 10*std::log10(energy);
|
|
}
|
|
|
|
/// @name Lowpass
|
|
/// @{
|
|
BiquadStatic & lowpass(double scaledFreq, double octaves=defaultBandwidth, BiquadDesign design=BiquadDesign::bilinear) {
|
|
return configure(Type::lowpass, octaveSpec(scaledFreq, octaves, design), 0, design);
|
|
}
|
|
BiquadStatic & lowpassQ(double scaledFreq, double q, BiquadDesign design=BiquadDesign::bilinear) {
|
|
return configure(Type::lowpass, qSpec(scaledFreq, q, design), 0, design);
|
|
}
|
|
/// @deprecated use `BiquadDesign` instead
|
|
void lowpass(double scaledFreq, double octaves, bool correctBandwidth) {
|
|
lowpass(scaledFreq, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
}
|
|
/// @deprecated By the time you care about `design`, you should care about the bandwidth
|
|
BiquadStatic & lowpass(double scaledFreq, BiquadDesign design) {
|
|
return lowpass(scaledFreq, defaultBandwidth, design);
|
|
}
|
|
/// @}
|
|
|
|
/// @name Highpass
|
|
/// @{
|
|
BiquadStatic & highpass(double scaledFreq, double octaves=defaultBandwidth, BiquadDesign design=BiquadDesign::bilinear) {
|
|
return configure(Type::highpass, octaveSpec(scaledFreq, octaves, design), 0, design);
|
|
}
|
|
BiquadStatic & highpassQ(double scaledFreq, double q, BiquadDesign design=BiquadDesign::bilinear) {
|
|
return configure(Type::highpass, qSpec(scaledFreq, q, design), 0, design);
|
|
}
|
|
/// @deprecated use `BiquadDesign` instead
|
|
void highpass(double scaledFreq, double octaves, bool correctBandwidth) {
|
|
highpass(scaledFreq, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
}
|
|
/// @deprecated By the time you care about `design`, you should care about the bandwidth
|
|
BiquadStatic & highpass(double scaledFreq, BiquadDesign design) {
|
|
return highpass(scaledFreq, defaultBandwidth, design);
|
|
}
|
|
/// @}
|
|
|
|
/// @name Bandpass
|
|
/// @{
|
|
BiquadStatic & bandpass(double scaledFreq, double octaves=defaultBandwidth, BiquadDesign design=bwDesign) {
|
|
return configure(Type::bandpass, octaveSpec(scaledFreq, octaves, design), 0, design);
|
|
}
|
|
BiquadStatic & bandpassQ(double scaledFreq, double q, BiquadDesign design=bwDesign) {
|
|
return configure(Type::bandpass, qSpec(scaledFreq, q, design), 0, design);
|
|
}
|
|
/// @deprecated use `BiquadDesign` instead
|
|
void bandpass(double scaledFreq, double octaves, bool correctBandwidth) {
|
|
bandpass(scaledFreq, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
}
|
|
/// @deprecated By the time you care about `design`, you should care about the bandwidth
|
|
BiquadStatic & bandpass(double scaledFreq, BiquadDesign design) {
|
|
return bandpass(scaledFreq, defaultBandwidth, design);
|
|
}
|
|
/// @}
|
|
|
|
/// @name Notch
|
|
/// @{
|
|
BiquadStatic & notch(double scaledFreq, double octaves=defaultBandwidth, BiquadDesign design=bwDesign) {
|
|
return configure(Type::notch, octaveSpec(scaledFreq, octaves, design), 0, design);
|
|
}
|
|
BiquadStatic & notchQ(double scaledFreq, double q, BiquadDesign design=bwDesign) {
|
|
return configure(Type::notch, qSpec(scaledFreq, q, design), 0, design);
|
|
}
|
|
/// @deprecated use `BiquadDesign` instead
|
|
void notch(double scaledFreq, double octaves, bool correctBandwidth) {
|
|
notch(scaledFreq, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
}
|
|
/// @deprecated By the time you care about `design`, you should care about the bandwidth
|
|
BiquadStatic & notch(double scaledFreq, BiquadDesign design) {
|
|
return notch(scaledFreq, defaultBandwidth, design);
|
|
}
|
|
/// @deprecated alias for `.notch()`
|
|
void bandStop(double scaledFreq, double octaves=1, bool correctBandwidth=true) {
|
|
notch(scaledFreq, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
}
|
|
/// @}
|
|
|
|
/// @name Peak
|
|
/// @{
|
|
BiquadStatic & peak(double scaledFreq, double gain, double octaves=1, BiquadDesign design=bwDesign) {
|
|
return configure(Type::peak, octaveSpec(scaledFreq, octaves, design), std::sqrt(gain), design);
|
|
}
|
|
BiquadStatic & peakDb(double scaledFreq, double db, double octaves=1, BiquadDesign design=bwDesign) {
|
|
return configure(Type::peak, octaveSpec(scaledFreq, octaves, design), dbToSqrtGain(db), design);
|
|
}
|
|
BiquadStatic & peakQ(double scaledFreq, double gain, double q, BiquadDesign design=bwDesign) {
|
|
return configure(Type::peak, qSpec(scaledFreq, q, design), std::sqrt(gain), design);
|
|
}
|
|
BiquadStatic & peakDbQ(double scaledFreq, double db, double q, BiquadDesign design=bwDesign) {
|
|
return configure(Type::peak, qSpec(scaledFreq, q, design), dbToSqrtGain(db), design);
|
|
}
|
|
/// @deprecated By the time you care about `design`, you should care about the bandwidth
|
|
BiquadStatic & peak(double scaledFreq, double gain, BiquadDesign design) {
|
|
return peak(scaledFreq, gain, 1, design);
|
|
}
|
|
/// @}
|
|
|
|
/// @name High shelf
|
|
/// @{
|
|
BiquadStatic & highShelf(double scaledFreq, double gain, double octaves=defaultBandwidth, BiquadDesign design=bwDesign) {
|
|
return configure(Type::highShelf, octaveSpec(scaledFreq, octaves, design), std::sqrt(gain), design);
|
|
}
|
|
BiquadStatic & highShelfDb(double scaledFreq, double db, double octaves=defaultBandwidth, BiquadDesign design=bwDesign) {
|
|
return configure(Type::highShelf, octaveSpec(scaledFreq, octaves, design), dbToSqrtGain(db), design);
|
|
}
|
|
BiquadStatic & highShelfQ(double scaledFreq, double gain, double q, BiquadDesign design=bwDesign) {
|
|
return configure(Type::highShelf, qSpec(scaledFreq, q, design), std::sqrt(gain), design);
|
|
}
|
|
BiquadStatic & highShelfDbQ(double scaledFreq, double db, double q, BiquadDesign design=bwDesign) {
|
|
return configure(Type::highShelf, qSpec(scaledFreq, q, design), dbToSqrtGain(db), design);
|
|
}
|
|
/// @deprecated use `BiquadDesign` instead
|
|
BiquadStatic & highShelf(double scaledFreq, double gain, double octaves, bool correctBandwidth) {
|
|
return highShelf(scaledFreq, gain, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
}
|
|
/// @deprecated use `BiquadDesign` instead
|
|
BiquadStatic & highShelfDb(double scaledFreq, double db, double octaves, bool correctBandwidth) {
|
|
return highShelfDb(scaledFreq, db, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
}
|
|
/// @}
|
|
|
|
/// @name Low shelf
|
|
/// @{
|
|
BiquadStatic & lowShelf(double scaledFreq, double gain, double octaves=2, BiquadDesign design=bwDesign) {
|
|
return configure(Type::lowShelf, octaveSpec(scaledFreq, octaves, design), std::sqrt(gain), design);
|
|
}
|
|
BiquadStatic & lowShelfDb(double scaledFreq, double db, double octaves=2, BiquadDesign design=bwDesign) {
|
|
return configure(Type::lowShelf, octaveSpec(scaledFreq, octaves, design), dbToSqrtGain(db), design);
|
|
}
|
|
BiquadStatic & lowShelfQ(double scaledFreq, double gain, double q, BiquadDesign design=bwDesign) {
|
|
return configure(Type::lowShelf, qSpec(scaledFreq, q, design), std::sqrt(gain), design);
|
|
}
|
|
BiquadStatic & lowShelfDbQ(double scaledFreq, double db, double q, BiquadDesign design=bwDesign) {
|
|
return configure(Type::lowShelf, qSpec(scaledFreq, q, design), dbToSqrtGain(db), design);
|
|
}
|
|
/// @deprecated use `BiquadDesign` instead
|
|
BiquadStatic & lowShelf(double scaledFreq, double gain, double octaves, bool correctBandwidth) {
|
|
return lowShelf(scaledFreq, gain, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
}
|
|
/// @deprecated use `BiquadDesign` instead
|
|
BiquadStatic & lowShelfDb(double scaledFreq, double db, double octaves, bool correctBandwidth) {
|
|
return lowShelfDb(scaledFreq, db, octaves, correctBandwidth ? bwDesign : BiquadDesign::bilinear);
|
|
}
|
|
/// @}
|
|
|
|
/// @name Allpass
|
|
/// @{
|
|
BiquadStatic & allpass(double scaledFreq, double octaves=1, BiquadDesign design=bwDesign) {
|
|
return configure(Type::allpass, octaveSpec(scaledFreq, octaves, design), 0, design);
|
|
}
|
|
BiquadStatic & allpassQ(double scaledFreq, double q, BiquadDesign design=bwDesign) {
|
|
return configure(Type::allpass, qSpec(scaledFreq, q, design), 0, design);
|
|
}
|
|
/// @}
|
|
|
|
BiquadStatic & addGain(double factor) {
|
|
b0 *= factor;
|
|
b1 *= factor;
|
|
b2 *= factor;
|
|
return *this;
|
|
}
|
|
BiquadStatic & addGainDb(double db) {
|
|
return addGain(std::pow(10, db*0.05));
|
|
}
|
|
};
|
|
|
|
/** @} */
|
|
}} // signalsmith::filters::
|
|
#endif // include guard
|