MayaFlux 0.5.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 struct BiquadTerms {
12 double w0;
13 double cosw0;
14 double sinw0;
15 double alpha;
16 };
17
18 [[nodiscard]] BiquadTerms terms(double frequency, double q, double sample_rate)
19 {
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) };
26 }
27
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)
31 {
32 a.assign({ 1.0, a1 / a0, a2 / a0 });
33 b.assign({ b0 / a0, b1 / a0, b2 / a0 });
34 }
35
36 [[nodiscard]] double db_to_amplitude_sqrt(double gain_db)
37 {
38 return std::pow(10.0, gain_db / 40.0);
39 }
40
41 [[nodiscard]] double sinc(double x)
42 {
43 if (std::abs(x) < 1e-12)
44 return 1.0;
45 const double px = std::numbers::pi * x;
46 return std::sin(px) / px;
47 }
48
49 [[nodiscard]] size_t force_odd(size_t n)
50 {
51 if (n < 3)
52 return 3;
53 return (n % 2 == 0) ? n + 1 : n;
54 }
55
56 [[nodiscard]] std::vector<double> lowpass_kernel(double frequency, double sample_rate, size_t taps)
57 {
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;
62
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));
66
68
69 double total = 0.0;
70 for (double v : h)
71 total += v;
72 if (std::abs(total) > 1e-12) {
73 for (double& v : h)
74 v /= total;
75 }
76 return h;
77 }
78
79} // namespace
80
81void biquad_lowpass(double frequency, double q, double sample_rate,
82 std::vector<double>& a, std::vector<double>& b)
83{
84 const auto t = terms(frequency, q, sample_rate);
85 const double k = (1.0 - t.cosw0) * 0.5;
86 emit(a, b,
87 1.0 + t.alpha, -2.0 * t.cosw0, 1.0 - t.alpha,
88 k, 1.0 - t.cosw0, k);
89}
90
91void biquad_highpass(double frequency, double q, double sample_rate,
92 std::vector<double>& a, std::vector<double>& b)
93{
94 const auto t = terms(frequency, q, sample_rate);
95 const double k = (1.0 + t.cosw0) * 0.5;
96 emit(a, b,
97 1.0 + t.alpha, -2.0 * t.cosw0, 1.0 - t.alpha,
98 k, -(1.0 + t.cosw0), k);
99}
100
101void biquad_bandpass(double frequency, double q, double sample_rate,
102 std::vector<double>& a, std::vector<double>& b)
103{
104 const auto t = terms(frequency, q, sample_rate);
105 emit(a, b,
106 1.0 + t.alpha, -2.0 * t.cosw0, 1.0 - t.alpha,
107 t.alpha, 0.0, -t.alpha);
108}
109
110void biquad_notch(double frequency, double q, double sample_rate,
111 std::vector<double>& a, std::vector<double>& b)
112{
113 const auto t = terms(frequency, q, sample_rate);
114 emit(a, b,
115 1.0 + t.alpha, -2.0 * t.cosw0, 1.0 - t.alpha,
116 1.0, -2.0 * t.cosw0, 1.0);
117}
118
119void biquad_allpass(double frequency, double q, double sample_rate,
120 std::vector<double>& a, std::vector<double>& b)
121{
122 const auto t = terms(frequency, q, sample_rate);
123 emit(a, b,
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);
126}
127
128void biquad_peaking(double frequency, double q, double gain_db,
129 double sample_rate, std::vector<double>& a, std::vector<double>& b)
130{
131 const auto t = terms(frequency, q, sample_rate);
132 const double amp = db_to_amplitude_sqrt(gain_db);
133 emit(a, b,
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);
136}
137
138void biquad_low_shelf(double frequency, double slope, double gain_db,
139 double sample_rate, std::vector<double>& a, std::vector<double>& b)
140{
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;
150
151 emit(a, b,
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));
158}
159
160void biquad_high_shelf(double frequency, double slope, double gain_db,
161 double sample_rate, std::vector<double>& a, std::vector<double>& b)
162{
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;
172
173 emit(a, b,
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));
180}
181
182std::vector<double> sinc_lowpass(double frequency, double sample_rate, size_t taps)
183{
184 return lowpass_kernel(frequency, sample_rate, force_odd(taps));
185}
186
187std::vector<double> sinc_highpass(double frequency, double sample_rate, size_t taps)
188{
189 const size_t n = force_odd(taps);
190 std::vector<double> h = lowpass_kernel(frequency, sample_rate, n);
191
192 for (double& v : h)
193 v = -v;
194 h[(n - 1) / 2] += 1.0;
195
196 return h;
197}
198
199std::vector<double> sinc_bandpass(double low_hz, double high_hz, double sample_rate, size_t taps)
200{
201 double lo = low_hz;
202 double hi = high_hz;
203 if (lo > hi)
204 std::swap(lo, hi);
205
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);
209
210 for (size_t i = 0; i < n; ++i)
211 upper[i] -= lower[i];
212
213 return upper;
214}
215
216std::vector<double> savitzky_golay(size_t window, size_t poly_order, size_t derivative)
217{
218 const size_t n = force_odd(window);
219 if (n <= poly_order || derivative > poly_order)
220 return {};
221
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;
225
226 Eigen::MatrixXd vandermonde(rows, cols);
227 for (Eigen::Index i = 0; i < rows; ++i) {
228 const double z = static_cast<double>(i) - half;
229 double p = 1.0;
230 for (Eigen::Index j = 0; j < cols; ++j) {
231 vandermonde(i, j) = p;
232 p *= z;
233 }
234 }
235
236 const Eigen::MatrixXd pseudo = vandermonde
237 .completeOrthogonalDecomposition()
238 .pseudoInverse();
239
240 double factorial = 1.0;
241 for (size_t k = 2; k <= derivative; ++k)
242 factorial *= static_cast<double>(k);
243
244 const Eigen::VectorXd row = pseudo.row(static_cast<Eigen::Index>(derivative)) * factorial;
245
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));
249
250 return h;
251}
252
253std::vector<double> lagrange_delay(double delay, size_t order)
254{
255 const size_t taps = order + 1;
256 const double d = std::clamp(delay, 0.0, static_cast<double>(order));
257
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) {
261 if (j == k)
262 continue;
263 h[k] *= (d - static_cast<double>(j))
264 / (static_cast<double>(k) - static_cast<double>(j));
265 }
266 }
267 return h;
268}
269
270void resonator(double pole_radius, double pole_angle,
271 std::vector<double>& a, std::vector<double>& b)
272{
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);
276
277 a.assign({ 1.0, -2.0 * r * cos_theta, r * r });
278
279 const double gain = (1.0 - r)
280 * std::sqrt(1.0 - 2.0 * r * std::cos(2.0 * theta) + r * r);
281 b.assign({ gain });
282}
283
284void one_pole(double pole, std::vector<double>& a, std::vector<double>& b)
285{
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) });
289}
290
291void dc_block(double pole, std::vector<double>& a, std::vector<double>& b)
292{
293 const double p = std::clamp(pole, 0.0, 0.9999);
294 a.assign({ 1.0, -p });
295 b.assign({ 1.0, -1.0 });
296}
297
298std::vector<double> cascade(std::span<const double> lhs, std::span<const double> rhs)
299{
300 if (lhs.empty() || rhs.empty())
301 return {};
302
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];
307 }
308 return out;
309}
310
311double max_pole_magnitude(std::span<const double> a)
312{
313 size_t order = a.size();
314 while (order > 1 && a[order - 1] == 0.0)
315 --order;
316
317 if (order <= 1)
318 return 0.0;
319
320 const double a0 = a[0];
321
322 if (order == 2)
323 return std::abs(a[1] / a0);
324
325 if (order == 3) {
326 const double p = a[1] / a0;
327 const double q = a[2] / a0;
328 const double disc = p * p - 4.0 * q;
329 if (disc >= 0.0) {
330 const double r = std::sqrt(disc);
331 return std::max(std::abs((-p + r) * 0.5), std::abs((-p - r) * 0.5));
332 }
333 return std::sqrt(q);
334 }
335
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;
342
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)));
347 return largest;
348}
349
350} // namespace MayaFlux::Kinesis::Discrete
double cosw0
double alpha
double sinw0
double w0
Digital filter coefficient design for MayaFlux::Kinesis.
uint32_t h
Definition InkPress.cpp:28
size_t a
size_t b
double frequency
double q
Discrete taper (window) coefficient generation and in-place application for MayaFlux::Kinesis.
float lo
float k
float hi
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.
Definition Taper.cpp:97
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.