MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Coefficients.cpp
Go to the documentation of this file.
1#include "Coefficients.hpp"
2
3#include "Taper.hpp"
4
5#include <Eigen/Dense>
6
8
9namespace {
10
11 constexpr size_t reciprocal_factorial_count = 24;
12
13 constexpr std::array<double, reciprocal_factorial_count> reciprocal_factorials()
14 {
15 std::array<double, reciprocal_factorial_count> table {};
16 double factorial = 1.0;
17 table[0] = 1.0;
18 for (size_t n = 1; n < table.size(); ++n) {
19 factorial *= static_cast<double>(n);
20 table[n] = 1.0 / factorial;
21 }
22 return table;
23 }
24
25 constexpr auto reciprocal_factorial = reciprocal_factorials();
26
27 struct BiquadTerms {
28 double w0;
29 double cosw0;
30 double sinw0;
31 double alpha;
32 };
33
34 [[nodiscard]] BiquadTerms terms(double frequency, double q, double sample_rate)
35 {
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) };
42 }
43
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)
47 {
48 a.assign({ 1.0, a1 / a0, a2 / a0 });
49 b.assign({ b0 / a0, b1 / a0, b2 / a0 });
50 }
51
52 [[nodiscard]] double db_to_amplitude_sqrt(double gain_db)
53 {
54 return std::pow(10.0, gain_db / 40.0);
55 }
56
57 [[nodiscard]] double sinc(double x)
58 {
59 if (std::abs(x) < 1e-12)
60 return 1.0;
61 const double px = std::numbers::pi * x;
62 return std::sin(px) / px;
63 }
64
65 [[nodiscard]] size_t force_odd(size_t n)
66 {
67 if (n < 3)
68 return 3;
69 return (n % 2 == 0) ? n + 1 : n;
70 }
71
72 [[nodiscard]] std::vector<double> lowpass_kernel(double frequency, double sample_rate, size_t taps)
73 {
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;
78
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));
82
84
85 double total = 0.0;
86 for (double v : h)
87 total += v;
88 if (std::abs(total) > 1e-12) {
89 for (double& v : h)
90 v /= total;
91 }
92 return h;
93 }
94
95} // namespace
96
97void biquad_lowpass(double frequency, double q, double sample_rate,
98 std::vector<double>& a, std::vector<double>& b)
99{
100 const auto t = terms(frequency, q, sample_rate);
101 const double k = (1.0 - t.cosw0) * 0.5;
102 emit(a, b,
103 1.0 + t.alpha, -2.0 * t.cosw0, 1.0 - t.alpha,
104 k, 1.0 - t.cosw0, k);
105}
106
107void biquad_highpass(double frequency, double q, double sample_rate,
108 std::vector<double>& a, std::vector<double>& b)
109{
110 const auto t = terms(frequency, q, sample_rate);
111 const double k = (1.0 + t.cosw0) * 0.5;
112 emit(a, b,
113 1.0 + t.alpha, -2.0 * t.cosw0, 1.0 - t.alpha,
114 k, -(1.0 + t.cosw0), k);
115}
116
117void biquad_bandpass(double frequency, double q, double sample_rate,
118 std::vector<double>& a, std::vector<double>& b)
119{
120 const auto biquad = biquad_bandpass(frequency, q, sample_rate);
121 a.assign({ 1.0, biquad.a1, biquad.a2 });
122 b.assign({ biquad.b0, biquad.b1, biquad.b2 });
123}
124
125Biquad biquad_bandpass(double frequency, double q, double sample_rate) noexcept
126{
127 const auto t = terms(frequency, q, sample_rate);
128 const double a0 = 1.0 + t.alpha;
129 return {
130 .a1 = (-2.0 * t.cosw0) / a0,
131 .a2 = (1.0 - t.alpha) / a0,
132 .b0 = t.alpha / a0,
133 .b1 = 0.0,
134 .b2 = (-t.alpha) / a0,
135 };
136}
137
138double decay_per_sample(double seconds, double sample_rate) noexcept
139{
140 return std::exp(-1.0 / (seconds * sample_rate));
141}
142
143double pole_radius_from_decay(double seconds, double sample_rate) noexcept
144{
145 const double tau = std::max(seconds, 1.0 / sample_rate);
146 return std::min(decay_per_sample(tau, sample_rate), 1.0 - 1e-12);
147}
148
149double decay_from_pole_radius(double radius, double sample_rate) noexcept
150{
151 const double r = std::min(radius, 1.0 - 1e-12);
152 if (r <= 0.0)
153 return 0.0;
154 return -1.0 / (sample_rate * std::log(r));
155}
156
157double q_from_decay(double frequency, double seconds, double sample_rate) noexcept
158{
159 const double radius = pole_radius_from_decay(seconds, sample_rate);
160 const double radius_sq = radius * radius;
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);
164}
165
166double decay_from_q(double frequency, double q, double sample_rate) noexcept
167{
168 const auto t = terms(frequency, q, sample_rate);
169 if (t.alpha >= 1.0)
170 return 0.0;
171 const double radius = std::sqrt((1.0 - t.alpha) / (1.0 + t.alpha));
172 return decay_from_pole_radius(radius, sample_rate);
173}
174
175std::vector<double> backward_difference_weights(size_t order)
176{
177 std::vector<double> weights(order + 1);
178 double binomial = 1.0;
179 for (size_t k = 0; k <= order; ++k) {
180 if (k > 0)
181 binomial = binomial * static_cast<double>(order - k + 1) / static_cast<double>(k);
182 weights.at(k) = (k % 2 == 0) ? binomial : -binomial;
183 }
184 return weights;
185}
186
187std::vector<double> geometric_weights(size_t count, double ratio, bool unit_sum)
188{
189 std::vector<double> weights(count);
190 double weight = 1.0;
191 double total = 0.0;
192 for (size_t k = 0; k < count; ++k) {
193 weights.at(k) = weight;
194 total += weight;
195 weight *= ratio;
196 }
197
198 if (unit_sum && std::abs(total) > 1e-12) {
199 for (double& w : weights)
200 w /= total;
201 }
202 return weights;
203}
204
205std::vector<double> moving_average_weights(size_t count)
206{
207 if (count == 0)
208 return {};
209
210 auto weights = rectangular(count);
211 const double each = 1.0 / static_cast<double>(count);
212 for (double& w : weights)
213 w *= each;
214 return weights;
215}
216
217void biquad_notch(double frequency, double q, double sample_rate,
218 std::vector<double>& a, std::vector<double>& b)
219{
220 const auto t = terms(frequency, q, sample_rate);
221 emit(a, b,
222 1.0 + t.alpha, -2.0 * t.cosw0, 1.0 - t.alpha,
223 1.0, -2.0 * t.cosw0, 1.0);
224}
225
226void biquad_allpass(double frequency, double q, double sample_rate,
227 std::vector<double>& a, std::vector<double>& b)
228{
229 const auto t = terms(frequency, q, sample_rate);
230 emit(a, b,
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);
233}
234
235void biquad_peaking(double frequency, double q, double gain_db,
236 double sample_rate, std::vector<double>& a, std::vector<double>& b)
237{
238 const auto t = terms(frequency, q, sample_rate);
239 const double amp = db_to_amplitude_sqrt(gain_db);
240 emit(a, b,
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);
243}
244
245void biquad_low_shelf(double frequency, double slope, double gain_db,
246 double sample_rate, std::vector<double>& a, std::vector<double>& b)
247{
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;
257
258 emit(a, b,
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));
265}
266
267void biquad_high_shelf(double frequency, double slope, double gain_db,
268 double sample_rate, std::vector<double>& a, std::vector<double>& b)
269{
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;
279
280 emit(a, b,
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));
287}
288
289std::vector<double> sinc_lowpass(double frequency, double sample_rate, size_t taps)
290{
291 return lowpass_kernel(frequency, sample_rate, force_odd(taps));
292}
293
294std::vector<double> sinc_highpass(double frequency, double sample_rate, size_t taps)
295{
296 const size_t n = force_odd(taps);
297 std::vector<double> h = lowpass_kernel(frequency, sample_rate, n);
298
299 for (double& v : h)
300 v = -v;
301 h[(n - 1) / 2] += 1.0;
302
303 return h;
304}
305
306std::vector<double> sinc_bandpass(double low_hz, double high_hz, double sample_rate, size_t taps)
307{
308 double lo = low_hz;
309 double hi = high_hz;
310 if (lo > hi)
311 std::swap(lo, hi);
312
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);
316
317 for (size_t i = 0; i < n; ++i)
318 upper[i] -= lower[i];
319
320 return upper;
321}
322
323std::vector<double> savitzky_golay(size_t window, size_t poly_order, size_t derivative)
324{
325 const size_t n = force_odd(window);
326 if (n <= poly_order || derivative > poly_order)
327 return {};
328
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;
332
333 Eigen::MatrixXd vandermonde(rows, cols);
334 for (Eigen::Index i = 0; i < rows; ++i) {
335 const double z = static_cast<double>(i) - half;
336 double p = 1.0;
337 for (Eigen::Index j = 0; j < cols; ++j) {
338 vandermonde(i, j) = p;
339 p *= z;
340 }
341 }
342
343 const Eigen::MatrixXd pseudo = vandermonde
344 .completeOrthogonalDecomposition()
345 .pseudoInverse();
346
347 double factorial = 1.0;
348 for (size_t k = 2; k <= derivative; ++k)
349 factorial *= static_cast<double>(k);
350
351 const Eigen::VectorXd row = pseudo.row(static_cast<Eigen::Index>(derivative)) * factorial;
352
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));
356
357 return h;
358}
359
360std::vector<double> lagrange_delay(double delay, size_t order)
361{
362 std::vector<double> h(order + 1);
364 return h;
365}
366
367void lagrange_weights(double delay, std::span<double> weights) noexcept
368{
369 const size_t taps = weights.size();
370 if (taps == 0)
371 return;
372
373 const double d = std::clamp(delay, 0.0, static_cast<double>(taps - 1));
374
375 if (taps > reciprocal_factorial.size()) {
376 for (size_t k = 0; k < taps; ++k) {
377 weights[k] = 1.0;
378 for (size_t j = 0; j < taps; ++j) {
379 if (j == k)
380 continue;
381 weights[k] *= (d - static_cast<double>(j))
382 / (static_cast<double>(k) - static_cast<double>(j));
383 }
384 }
385 return;
386 }
387
388 double before = 1.0;
389 for (size_t k = 0; k < taps; ++k) {
390 weights[k] = before;
391 before *= d - static_cast<double>(k);
392 }
393
394 double after = 1.0;
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);
400 }
401}
402
403void resonator(double pole_radius, double pole_angle,
404 std::vector<double>& a, std::vector<double>& b)
405{
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);
409
410 a.assign({ 1.0, -2.0 * r * cos_theta, r * r });
411
412 const double gain = (1.0 - r)
413 * std::sqrt(1.0 - 2.0 * r * std::cos(2.0 * theta) + r * r);
414 b.assign({ gain });
415}
416
417void one_pole(double pole, std::vector<double>& a, std::vector<double>& b)
418{
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) });
422}
423
424void dc_block(double pole, std::vector<double>& a, std::vector<double>& b)
425{
426 const double p = std::clamp(pole, 0.0, 0.9999);
427 a.assign({ 1.0, -p });
428 b.assign({ 1.0, -1.0 });
429}
430
431std::vector<double> cascade(std::span<const double> lhs, std::span<const double> rhs)
432{
433 if (lhs.empty() || rhs.empty())
434 return {};
435
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];
440 }
441 return out;
442}
443
444double max_pole_magnitude(std::span<const double> a)
445{
446 size_t order = a.size();
447 while (order > 1 && a[order - 1] == 0.0)
448 --order;
449
450 if (order <= 1)
451 return 0.0;
452
453 const double a0 = a[0];
454
455 if (order == 2)
456 return std::abs(a[1] / a0);
457
458 if (order == 3) {
459 const double p = a[1] / a0;
460 const double q = a[2] / a0;
461 const double disc = p * p - 4.0 * q;
462 if (disc >= 0.0) {
463 const double r = std::sqrt(disc);
464 return std::max(std::abs((-p + r) * 0.5), std::abs((-p - r) * 0.5));
465 }
466 return std::sqrt(q);
467 }
468
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;
475
476 Eigen::EigenSolver<Eigen::MatrixXd> solver(companion, false);
477 double largest = 0.0;
478 for (Eigen::Index i = 0; i < solver.eigenvalues().size(); ++i)
479 largest = std::max(largest, std::abs(solver.eigenvalues()(i)));
480 return largest;
481}
482
483} // namespace MayaFlux::Kinesis::Discrete
double cosw0
double alpha
double sinw0
double w0
Digital filter coefficient design for MayaFlux::Kinesis.
bool size
uint32_t rows
float radius
uint32_t h
Definition InkPress.cpp:29
std::weak_ptr< Core::Window > window
Definition Picker.cpp:23
uint32_t total
size_t a
double weight
size_t b
double frequency
double q
Discrete taper (window) coefficient generation and in-place application for MayaFlux::Kinesis.
std::vector< float > * out
size_t count
uint32_t px
uint32_t largest
float gain
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.
Definition Taper.cpp:52
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.
Definition Taper.cpp:97
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.
Definition Tendency.hpp:98
Second order section with a[0] normalised to 1, returned by value.