18 [[nodiscard]] BiquadTerms terms(
double frequency,
double q,
double sample_rate)
20 const double nyquist = sample_rate * 0.5;
21 const double f = std::clamp(
frequency, 1.0, nyquist - 1.0);
22 const double qq = std::max(
q, 1e-4);
23 const double w0 = 2.0 * std::numbers::pi * f / sample_rate;
24 const double sinw0 = std::sin(
w0);
25 return { .w0 =
w0, .cosw0 = std::cos(
w0), .sinw0 =
sinw0, .alpha =
sinw0 / (2.0 * qq) };
28 void emit(std::vector<double>&
a, std::vector<double>&
b,
29 double a0,
double a1,
double a2,
30 double b0,
double b1,
double b2)
32 a.assign({ 1.0, a1 / a0, a2 / a0 });
33 b.assign({ b0 / a0, b1 / a0, b2 / a0 });
36 [[nodiscard]]
double db_to_amplitude_sqrt(
double gain_db)
38 return std::pow(10.0, gain_db / 40.0);
41 [[nodiscard]]
double sinc(
double x)
43 if (std::abs(x) < 1e-12)
45 const double px = std::numbers::pi * x;
46 return std::sin(px) / px;
49 [[nodiscard]]
size_t force_odd(
size_t n)
53 return (n % 2 == 0) ? n + 1 : n;
56 [[nodiscard]] std::vector<double> lowpass_kernel(
double frequency,
double sample_rate,
size_t taps)
58 const double nyquist = sample_rate * 0.5;
59 const double f = std::clamp(
frequency, 1.0, nyquist - 1.0);
60 const double fc = f / sample_rate;
61 const double centre =
static_cast<double>(taps - 1) * 0.5;
63 std::vector<double>
h(taps);
64 for (
size_t i = 0; i < taps; ++i)
65 h[i] = 2.0 * fc * sinc(2.0 * fc * (
static_cast<double>(i) - centre));
72 if (std::abs(total) > 1e-12) {
82 std::vector<double>&
a, std::vector<double>&
b)
84 const auto t = terms(
frequency,
q, sample_rate);
85 const double k = (1.0 - t.cosw0) * 0.5;
87 1.0 + t.alpha, -2.0 * t.cosw0, 1.0 - t.alpha,
92 std::vector<double>&
a, std::vector<double>&
b)
94 const auto t = terms(
frequency,
q, sample_rate);
95 const double k = (1.0 + t.cosw0) * 0.5;
97 1.0 + t.alpha, -2.0 * t.cosw0, 1.0 - t.alpha,
98 k, -(1.0 + t.cosw0),
k);
102 std::vector<double>&
a, std::vector<double>&
b)
104 const auto t = terms(
frequency,
q, sample_rate);
106 1.0 + t.alpha, -2.0 * t.cosw0, 1.0 - t.alpha,
107 t.alpha, 0.0, -t.alpha);
111 std::vector<double>&
a, std::vector<double>&
b)
113 const auto t = terms(
frequency,
q, sample_rate);
115 1.0 + t.alpha, -2.0 * t.cosw0, 1.0 - t.alpha,
116 1.0, -2.0 * t.cosw0, 1.0);
120 std::vector<double>&
a, std::vector<double>&
b)
122 const auto t = terms(
frequency,
q, sample_rate);
124 1.0 + t.alpha, -2.0 * t.cosw0, 1.0 - t.alpha,
125 1.0 - t.alpha, -2.0 * t.cosw0, 1.0 + t.alpha);
129 double sample_rate, std::vector<double>&
a, std::vector<double>&
b)
131 const auto t = terms(
frequency,
q, sample_rate);
132 const double amp = db_to_amplitude_sqrt(gain_db);
134 1.0 + t.alpha / amp, -2.0 * t.cosw0, 1.0 - t.alpha / amp,
135 1.0 + t.alpha * amp, -2.0 * t.cosw0, 1.0 - t.alpha * amp);
139 double sample_rate, std::vector<double>&
a, std::vector<double>&
b)
141 const double amp = db_to_amplitude_sqrt(gain_db);
142 const double nyquist = sample_rate * 0.5;
143 const double f = std::clamp(
frequency, 1.0, nyquist - 1.0);
144 const double w0 = 2.0 * std::numbers::pi * f / sample_rate;
145 const double cosw0 = std::cos(
w0);
146 const double s = std::clamp(slope, 1e-4, 1.0);
147 const double alpha = std::sin(
w0) * 0.5
148 * std::sqrt((amp + 1.0 / amp) * (1.0 / s - 1.0) + 2.0);
149 const double sq = 2.0 * std::sqrt(amp) *
alpha;
152 (amp + 1.0) + (amp - 1.0) *
cosw0 + sq,
153 -2.0 * ((amp - 1.0) + (amp + 1.0) *
cosw0),
154 (amp + 1.0) + (amp - 1.0) *
cosw0 - sq,
155 amp * ((amp + 1.0) - (amp - 1.0) *
cosw0 + sq),
156 2.0 * amp * ((amp - 1.0) - (amp + 1.0) *
cosw0),
157 amp * ((amp + 1.0) - (amp - 1.0) *
cosw0 - sq));
161 double sample_rate, std::vector<double>&
a, std::vector<double>&
b)
163 const double amp = db_to_amplitude_sqrt(gain_db);
164 const double nyquist = sample_rate * 0.5;
165 const double f = std::clamp(
frequency, 1.0, nyquist - 1.0);
166 const double w0 = 2.0 * std::numbers::pi * f / sample_rate;
167 const double cosw0 = std::cos(
w0);
168 const double s = std::clamp(slope, 1e-4, 1.0);
169 const double alpha = std::sin(
w0) * 0.5
170 * std::sqrt((amp + 1.0 / amp) * (1.0 / s - 1.0) + 2.0);
171 const double sq = 2.0 * std::sqrt(amp) *
alpha;
174 (amp + 1.0) - (amp - 1.0) *
cosw0 + sq,
175 2.0 * ((amp - 1.0) - (amp + 1.0) *
cosw0),
176 (amp + 1.0) - (amp - 1.0) *
cosw0 - sq,
177 amp * ((amp + 1.0) + (amp - 1.0) *
cosw0 + sq),
178 -2.0 * amp * ((amp - 1.0) + (amp + 1.0) *
cosw0),
179 amp * ((amp + 1.0) + (amp - 1.0) *
cosw0 - sq));
184 return lowpass_kernel(
frequency, sample_rate, force_odd(taps));
189 const size_t n = force_odd(taps);
190 std::vector<double>
h = lowpass_kernel(
frequency, sample_rate, n);
194 h[(n - 1) / 2] += 1.0;
199std::vector<double>
sinc_bandpass(
double low_hz,
double high_hz,
double sample_rate,
size_t taps)
206 const size_t n = force_odd(taps);
207 std::vector<double> upper = lowpass_kernel(
hi, sample_rate, n);
208 const std::vector<double> lower = lowpass_kernel(
lo, sample_rate, n);
210 for (
size_t i = 0; i < n; ++i)
211 upper[i] -= lower[i];
216std::vector<double>
savitzky_golay(
size_t window,
size_t poly_order,
size_t derivative)
218 const size_t n = force_odd(window);
219 if (n <= poly_order || derivative > poly_order)
222 const auto rows =
static_cast<Eigen::Index
>(n);
223 const auto cols =
static_cast<Eigen::Index
>(poly_order) + 1;
224 const double half =
static_cast<double>(n - 1) * 0.5;
226 Eigen::MatrixXd vandermonde(rows, cols);
227 for (Eigen::Index i = 0; i < rows; ++i) {
228 const double z =
static_cast<double>(i) - half;
230 for (Eigen::Index j = 0; j < cols; ++j) {
231 vandermonde(i, j) = p;
236 const Eigen::MatrixXd pseudo = vandermonde
237 .completeOrthogonalDecomposition()
240 double factorial = 1.0;
241 for (
size_t k = 2;
k <= derivative; ++
k)
242 factorial *=
static_cast<double>(
k);
244 const Eigen::VectorXd row = pseudo.row(
static_cast<Eigen::Index
>(derivative)) * factorial;
246 std::vector<double>
h(n);
247 for (
size_t i = 0; i < n; ++i)
248 h[i] = row(
static_cast<Eigen::Index
>(n - 1 - i));
255 const size_t taps = order + 1;
256 const double d = std::clamp(
delay, 0.0,
static_cast<double>(order));
258 std::vector<double>
h(taps, 1.0);
259 for (
size_t k = 0;
k < taps; ++
k) {
260 for (
size_t j = 0; j < taps; ++j) {
263 h[
k] *= (d -
static_cast<double>(j))
264 / (
static_cast<double>(
k) -
static_cast<double>(j));
271 std::vector<double>&
a, std::vector<double>&
b)
273 const double r = std::clamp(pole_radius, 0.0, 0.9999);
274 const double theta = std::clamp(pole_angle, 0.0, std::numbers::pi);
275 const double cos_theta = std::cos(theta);
277 a.assign({ 1.0, -2.0 * r * cos_theta, r * r });
279 const double gain = (1.0 - r)
280 * std::sqrt(1.0 - 2.0 * r * std::cos(2.0 * theta) + r * r);
284void one_pole(
double pole, std::vector<double>&
a, std::vector<double>&
b)
286 const double p = std::clamp(pole, -0.9999, 0.9999);
287 a.assign({ 1.0, -p });
288 b.assign({ 1.0 - std::abs(p) });
291void dc_block(
double pole, std::vector<double>&
a, std::vector<double>&
b)
293 const double p = std::clamp(pole, 0.0, 0.9999);
294 a.assign({ 1.0, -p });
295 b.assign({ 1.0, -1.0 });
298std::vector<double>
cascade(std::span<const double> lhs, std::span<const double> rhs)
300 if (lhs.empty() || rhs.empty())
303 std::vector<double> out(lhs.size() + rhs.size() - 1, 0.0);
304 for (
size_t i = 0; i < lhs.size(); ++i) {
305 for (
size_t j = 0; j < rhs.size(); ++j)
306 out[i + j] += lhs[i] * rhs[j];
313 size_t order =
a.size();
314 while (order > 1 &&
a[order - 1] == 0.0)
320 const double a0 =
a[0];
323 return std::abs(
a[1] / a0);
326 const double p =
a[1] / a0;
327 const double q =
a[2] / a0;
328 const double disc = p * p - 4.0 *
q;
330 const double r = std::sqrt(disc);
331 return std::max(std::abs((-p + r) * 0.5), std::abs((-p - r) * 0.5));
336 const Eigen::Index n =
static_cast<Eigen::Index
>(order) - 1;
337 Eigen::MatrixXd companion = Eigen::MatrixXd::Zero(n, n);
338 for (Eigen::Index i = 0; i < n; ++i)
339 companion(0, i) = -
a[
static_cast<size_t>(i) + 1] / a0;
340 for (Eigen::Index i = 1; i < n; ++i)
341 companion(i, i - 1) = 1.0;
343 Eigen::EigenSolver<Eigen::MatrixXd> solver(companion,
false);
344 double largest = 0.0;
345 for (Eigen::Index i = 0; i < solver.eigenvalues().size(); ++i)
346 largest = std::max(largest, std::abs(solver.eigenvalues()(i)));
Digital filter coefficient design for MayaFlux::Kinesis.
Discrete taper (window) coefficient generation and in-place application for MayaFlux::Kinesis.
std::vector< double > delay(std::span< const double > data, uint32_t delay_samples, double fill_value)
Prepend delay_samples zero-valued (or fill_value) samples, returning a new vector.
void biquad_notch(double frequency, double q, double sample_rate, std::vector< double > &a, std::vector< double > &b)
Second-order notch coefficients.
void biquad_bandpass(double frequency, double q, double sample_rate, std::vector< double > &a, std::vector< double > &b)
Second-order bandpass coefficients, constant 0 dB peak gain.
void biquad_lowpass(double frequency, double q, double sample_rate, std::vector< double > &a, std::vector< double > &b)
Second-order lowpass coefficients.
void biquad_highpass(double frequency, double q, double sample_rate, std::vector< double > &a, std::vector< double > &b)
Second-order highpass coefficients.
std::vector< double > savitzky_golay(size_t window, size_t poly_order, size_t derivative)
Savitzky-Golay FIR coefficients for smoothing or differentiation.
std::vector< double > sinc_lowpass(double frequency, double sample_rate, size_t taps)
Windowed-sinc lowpass FIR coefficients.
std::vector< double > lagrange_delay(double delay, size_t order)
Lagrange fractional-delay FIR coefficients.
void biquad_peaking(double frequency, double q, double gain_db, double sample_rate, std::vector< double > &a, std::vector< double > &b)
Second-order peaking coefficients.
void dc_block(double pole, std::vector< double > &a, std::vector< double > &b)
DC blocking filter.
double max_pole_magnitude(std::span< const double > a)
Largest pole magnitude of a denominator polynomial.
void one_pole(double pole, std::vector< double > &a, std::vector< double > &b)
One-pole filter specified by pole position.
void biquad_allpass(double frequency, double q, double sample_rate, std::vector< double > &a, std::vector< double > &b)
Second-order allpass coefficients.
std::vector< double > sinc_highpass(double frequency, double sample_rate, size_t taps)
Windowed-sinc highpass FIR coefficients.
std::vector< double > cascade(std::span< const double > lhs, std::span< const double > rhs)
Polynomial product of two coefficient vectors.
void biquad_low_shelf(double frequency, double slope, double gain_db, double sample_rate, std::vector< double > &a, std::vector< double > &b)
Second-order low shelf coefficients.
void biquad_high_shelf(double frequency, double slope, double gain_db, double sample_rate, std::vector< double > &a, std::vector< double > &b)
Second-order high shelf coefficients.
void apply_hann(std::span< double > data) noexcept
Apply a Hann taper in-place without materialising coefficients.
void resonator(double pole_radius, double pole_angle, std::vector< double > &a, std::vector< double > &b)
Two-pole resonator specified by z-plane pole position.
std::vector< double > sinc_bandpass(double low_hz, double high_hz, double sample_rate, size_t taps)
Windowed-sinc bandpass FIR coefficients.