11 constexpr size_t reciprocal_factorial_count = 24;
13 constexpr std::array<double, reciprocal_factorial_count> reciprocal_factorials()
15 std::array<double, reciprocal_factorial_count> table {};
16 double factorial = 1.0;
18 for (
size_t n = 1; n < table.size(); ++n) {
19 factorial *=
static_cast<double>(n);
20 table[n] = 1.0 / factorial;
25 constexpr auto reciprocal_factorial = reciprocal_factorials();
34 [[nodiscard]] BiquadTerms terms(
double frequency,
double q,
double sample_rate)
36 const double nyquist = sample_rate * 0.5;
37 const double f = std::clamp(
frequency, 1.0, nyquist - 1.0);
38 const double qq = std::max(
q, 1e-4);
39 const double w0 = 2.0 * std::numbers::pi * f / sample_rate;
40 const double sinw0 = std::sin(
w0);
41 return { .w0 =
w0, .cosw0 = std::cos(
w0), .sinw0 =
sinw0, .alpha =
sinw0 / (2.0 * qq) };
44 void emit(std::vector<double>&
a, std::vector<double>&
b,
45 double a0,
double a1,
double a2,
46 double b0,
double b1,
double b2)
48 a.assign({ 1.0, a1 / a0, a2 / a0 });
49 b.assign({ b0 / a0, b1 / a0, b2 / a0 });
52 [[nodiscard]]
double db_to_amplitude_sqrt(
double gain_db)
54 return std::pow(10.0, gain_db / 40.0);
57 [[nodiscard]]
double sinc(
double x)
59 if (std::abs(x) < 1e-12)
61 const double px = std::numbers::pi * x;
62 return std::sin(
px) /
px;
65 [[nodiscard]]
size_t force_odd(
size_t n)
69 return (n % 2 == 0) ? n + 1 : n;
72 [[nodiscard]] std::vector<double> lowpass_kernel(
double frequency,
double sample_rate,
size_t taps)
74 const double nyquist = sample_rate * 0.5;
75 const double f = std::clamp(
frequency, 1.0, nyquist - 1.0);
76 const double fc = f / sample_rate;
77 const double centre =
static_cast<double>(taps - 1) * 0.5;
79 std::vector<double>
h(taps);
80 for (
size_t i = 0; i < taps; ++i)
81 h[i] = 2.0 * fc * sinc(2.0 * fc * (
static_cast<double>(i) - centre));
88 if (std::abs(
total) > 1e-12) {
98 std::vector<double>&
a, std::vector<double>&
b)
100 const auto t = terms(
frequency,
q, sample_rate);
101 const double k = (1.0 - t.cosw0) * 0.5;
103 1.0 + t.alpha, -2.0 * t.cosw0, 1.0 - t.alpha,
104 k, 1.0 - t.cosw0, k);
108 std::vector<double>&
a, std::vector<double>&
b)
110 const auto t = terms(
frequency,
q, sample_rate);
111 const double k = (1.0 + t.cosw0) * 0.5;
113 1.0 + t.alpha, -2.0 * t.cosw0, 1.0 - t.alpha,
114 k, -(1.0 + t.cosw0), k);
118 std::vector<double>&
a, std::vector<double>&
b)
121 a.assign({ 1.0, biquad.a1, biquad.a2 });
122 b.assign({ biquad.b0, biquad.b1, biquad.b2 });
127 const auto t = terms(
frequency,
q, sample_rate);
128 const double a0 = 1.0 + t.alpha;
130 .a1 = (-2.0 * t.cosw0) / a0,
131 .a2 = (1.0 - t.alpha) / a0,
134 .b2 = (-t.alpha) / a0,
140 return std::exp(-1.0 / (seconds * sample_rate));
145 const double tau = std::max(seconds, 1.0 / sample_rate);
151 const double r = std::min(
radius, 1.0 - 1e-12);
154 return -1.0 / (sample_rate * std::log(r));
161 const double alpha = (1.0 - radius_sq) / (1.0 + radius_sq);
162 const auto t = terms(
frequency, 1.0, sample_rate);
163 return t.sinw0 / (2.0 *
alpha);
168 const auto t = terms(
frequency,
q, sample_rate);
171 const double radius = std::sqrt((1.0 - t.alpha) / (1.0 + t.alpha));
177 std::vector<double> weights(order + 1);
178 double binomial = 1.0;
179 for (
size_t k = 0; k <= order; ++k) {
181 binomial = binomial *
static_cast<double>(order - k + 1) /
static_cast<double>(k);
182 weights.at(k) = (k % 2 == 0) ? binomial : -binomial;
189 std::vector<double> weights(
count);
192 for (
size_t k = 0; k <
count; ++k) {
198 if (unit_sum && std::abs(
total) > 1e-12) {
199 for (
double& w : weights)
211 const double each = 1.0 /
static_cast<double>(
count);
212 for (
double& w : weights)
218 std::vector<double>&
a, std::vector<double>&
b)
220 const auto t = terms(
frequency,
q, sample_rate);
222 1.0 + t.alpha, -2.0 * t.cosw0, 1.0 - t.alpha,
223 1.0, -2.0 * t.cosw0, 1.0);
227 std::vector<double>&
a, std::vector<double>&
b)
229 const auto t = terms(
frequency,
q, sample_rate);
231 1.0 + t.alpha, -2.0 * t.cosw0, 1.0 - t.alpha,
232 1.0 - t.alpha, -2.0 * t.cosw0, 1.0 + t.alpha);
236 double sample_rate, std::vector<double>&
a, std::vector<double>&
b)
238 const auto t = terms(
frequency,
q, sample_rate);
239 const double amp = db_to_amplitude_sqrt(gain_db);
241 1.0 + t.alpha / amp, -2.0 * t.cosw0, 1.0 - t.alpha / amp,
242 1.0 + t.alpha * amp, -2.0 * t.cosw0, 1.0 - t.alpha * amp);
246 double sample_rate, std::vector<double>&
a, std::vector<double>&
b)
248 const double amp = db_to_amplitude_sqrt(gain_db);
249 const double nyquist = sample_rate * 0.5;
250 const double f = std::clamp(
frequency, 1.0, nyquist - 1.0);
251 const double w0 = 2.0 * std::numbers::pi * f / sample_rate;
252 const double cosw0 = std::cos(
w0);
253 const double s = std::clamp(slope, 1e-4, 1.0);
254 const double alpha = std::sin(
w0) * 0.5
255 * std::sqrt((amp + 1.0 / amp) * (1.0 / s - 1.0) + 2.0);
256 const double sq = 2.0 * std::sqrt(amp) *
alpha;
259 (amp + 1.0) + (amp - 1.0) *
cosw0 + sq,
260 -2.0 * ((amp - 1.0) + (amp + 1.0) *
cosw0),
261 (amp + 1.0) + (amp - 1.0) *
cosw0 - sq,
262 amp * ((amp + 1.0) - (amp - 1.0) *
cosw0 + sq),
263 2.0 * amp * ((amp - 1.0) - (amp + 1.0) *
cosw0),
264 amp * ((amp + 1.0) - (amp - 1.0) *
cosw0 - sq));
268 double sample_rate, std::vector<double>&
a, std::vector<double>&
b)
270 const double amp = db_to_amplitude_sqrt(gain_db);
271 const double nyquist = sample_rate * 0.5;
272 const double f = std::clamp(
frequency, 1.0, nyquist - 1.0);
273 const double w0 = 2.0 * std::numbers::pi * f / sample_rate;
274 const double cosw0 = std::cos(
w0);
275 const double s = std::clamp(slope, 1e-4, 1.0);
276 const double alpha = std::sin(
w0) * 0.5
277 * std::sqrt((amp + 1.0 / amp) * (1.0 / s - 1.0) + 2.0);
278 const double sq = 2.0 * std::sqrt(amp) *
alpha;
281 (amp + 1.0) - (amp - 1.0) *
cosw0 + sq,
282 2.0 * ((amp - 1.0) - (amp + 1.0) *
cosw0),
283 (amp + 1.0) - (amp - 1.0) *
cosw0 - sq,
284 amp * ((amp + 1.0) + (amp - 1.0) *
cosw0 + sq),
285 -2.0 * amp * ((amp - 1.0) + (amp + 1.0) *
cosw0),
286 amp * ((amp + 1.0) + (amp - 1.0) *
cosw0 - sq));
291 return lowpass_kernel(
frequency, sample_rate, force_odd(taps));
296 const size_t n = force_odd(taps);
297 std::vector<double>
h = lowpass_kernel(
frequency, sample_rate, n);
301 h[(n - 1) / 2] += 1.0;
306std::vector<double>
sinc_bandpass(
double low_hz,
double high_hz,
double sample_rate,
size_t taps)
313 const size_t n = force_odd(taps);
314 std::vector<double> upper = lowpass_kernel(hi, sample_rate, n);
315 const std::vector<double> lower = lowpass_kernel(lo, sample_rate, n);
317 for (
size_t i = 0; i < n; ++i)
318 upper[i] -= lower[i];
325 const size_t n = force_odd(
window);
326 if (n <= poly_order || derivative > poly_order)
329 const auto rows =
static_cast<Eigen::Index
>(n);
330 const auto cols =
static_cast<Eigen::Index
>(poly_order) + 1;
331 const double half =
static_cast<double>(n - 1) * 0.5;
333 Eigen::MatrixXd vandermonde(
rows, cols);
334 for (Eigen::Index i = 0; i <
rows; ++i) {
335 const double z =
static_cast<double>(i) - half;
337 for (Eigen::Index j = 0; j < cols; ++j) {
338 vandermonde(i, j) = p;
343 const Eigen::MatrixXd pseudo = vandermonde
344 .completeOrthogonalDecomposition()
347 double factorial = 1.0;
348 for (
size_t k = 2; k <= derivative; ++k)
349 factorial *=
static_cast<double>(k);
351 const Eigen::VectorXd row = pseudo.row(
static_cast<Eigen::Index
>(derivative)) * factorial;
353 std::vector<double>
h(n);
354 for (
size_t i = 0; i < n; ++i)
355 h[i] = row(
static_cast<Eigen::Index
>(n - 1 - i));
362 std::vector<double>
h(order + 1);
369 const size_t taps = weights.size();
373 const double d = std::clamp(
delay, 0.0,
static_cast<double>(taps - 1));
375 if (taps > reciprocal_factorial.size()) {
376 for (
size_t k = 0; k < taps; ++k) {
378 for (
size_t j = 0; j < taps; ++j) {
381 weights[k] *= (d -
static_cast<double>(j))
382 / (
static_cast<double>(k) -
static_cast<double>(j));
389 for (
size_t k = 0; k < taps; ++k) {
391 before *= d -
static_cast<double>(k);
395 for (
size_t k = taps; k-- > 0;) {
396 const size_t rest = taps - 1 - k;
397 const double scale = reciprocal_factorial[k] * reciprocal_factorial[rest];
398 weights[k] *= after * (rest % 2 == 0 ?
scale : -
scale);
399 after *= d -
static_cast<double>(k);
404 std::vector<double>&
a, std::vector<double>&
b)
406 const double r = std::clamp(pole_radius, 0.0, 0.9999);
407 const double theta = std::clamp(pole_angle, 0.0, std::numbers::pi);
408 const double cos_theta = std::cos(theta);
410 a.assign({ 1.0, -2.0 * r * cos_theta, r * r });
412 const double gain = (1.0 - r)
413 * std::sqrt(1.0 - 2.0 * r * std::cos(2.0 * theta) + r * r);
417void one_pole(
double pole, std::vector<double>&
a, std::vector<double>&
b)
419 const double p = std::clamp(pole, -0.9999, 0.9999);
420 a.assign({ 1.0, -p });
421 b.assign({ 1.0 - std::abs(p) });
424void dc_block(
double pole, std::vector<double>&
a, std::vector<double>&
b)
426 const double p = std::clamp(pole, 0.0, 0.9999);
427 a.assign({ 1.0, -p });
428 b.assign({ 1.0, -1.0 });
431std::vector<double>
cascade(std::span<const double> lhs, std::span<const double> rhs)
433 if (lhs.empty() || rhs.empty())
436 std::vector<double>
out(lhs.size() + rhs.size() - 1, 0.0);
437 for (
size_t i = 0; i < lhs.size(); ++i) {
438 for (
size_t j = 0; j < rhs.size(); ++j)
439 out[i + j] += lhs[i] * rhs[j];
446 size_t order =
a.size();
447 while (order > 1 &&
a[order - 1] == 0.0)
453 const double a0 =
a[0];
456 return std::abs(
a[1] / a0);
459 const double p =
a[1] / a0;
460 const double q =
a[2] / a0;
461 const double disc = p * p - 4.0 *
q;
463 const double r = std::sqrt(disc);
464 return std::max(std::abs((-p + r) * 0.5), std::abs((-p - r) * 0.5));
469 const Eigen::Index n =
static_cast<Eigen::Index
>(order) - 1;
470 Eigen::MatrixXd companion = Eigen::MatrixXd::Zero(n, n);
471 for (Eigen::Index i = 0; i < n; ++i)
472 companion(0, i) = -
a[
static_cast<size_t>(i) + 1] / a0;
473 for (Eigen::Index i = 1; i < n; ++i)
474 companion(i, i - 1) = 1.0;
476 Eigen::EigenSolver<Eigen::MatrixXd> solver(companion,
false);
478 for (Eigen::Index i = 0; i < solver.eigenvalues().
size(); ++i)
Digital filter coefficient design for MayaFlux::Kinesis.
std::weak_ptr< Core::Window > window
Discrete taper (window) coefficient generation and in-place application for MayaFlux::Kinesis.
std::vector< float > * out
void lagrange_weights(double delay, std::span< double > weights) noexcept
Lagrange interpolation weights written to an array, without allocating.
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.
std::vector< double > moving_average_weights(size_t count)
Equal weights summing to 1: Taper::rectangular scaled by 1 / count.
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.
std::vector< double > backward_difference_weights(size_t order)
Weights of the backward difference of a given order.
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.
double decay_from_q(double frequency, double q, double sample_rate) noexcept
Ring time of a bandpass section of a given Q.
double pole_radius_from_decay(double seconds, double sample_rate) noexcept
Pole radius whose impulse response falls by 1/e in a given time.
double decay_from_pole_radius(double radius, double sample_rate) noexcept
Time for an impulse response of a given pole radius to fall by 1/e.
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.
std::vector< double > rectangular(size_t n, size_t support, size_t offset)
Rectangular (boxcar) taper 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.
double q_from_decay(double frequency, double seconds, double sample_rate) noexcept
Q that gives the bandpass section a stated ring time.
std::vector< double > geometric_weights(size_t count, double ratio, bool unit_sum)
Weights that fall by a constant ratio with age.
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.
double decay_per_sample(double seconds, double sample_rate) noexcept
Per-sample factor that falls by 1/e in a given time.
std::vector< double > sinc_bandpass(double low_hz, double high_hz, double sample_rate, size_t taps)
Windowed-sinc bandpass FIR coefficients.
Tendency< D, float > scale(const Tendency< D, float > &t, float factor)
Uniform scaling of a scalar-output tendency.
Second order section with a[0] normalised to 1, returned by value.