MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Coefficients.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * @file Coefficients.hpp
5 * @brief Digital filter coefficient design for MayaFlux::Kinesis
6 *
7 * Pure numerical functions producing difference-equation coefficient vectors.
8 * No MayaFlux type dependencies. Domain-agnostic: the resulting coefficients
9 * describe a recurrence over any sampled sequence, not specifically audio.
10 *
11 * All biquad designs follow the RBJ Audio EQ Cookbook bilinear transform and
12 * are normalised so that a[0] == 1.0 on return. Coefficients are written into
13 * caller-supplied vectors, which are resized to the required order.
14 *
15 * Convention matches the direct-form recurrence:
16 * y[n] = sum(b[i] * x[n-i]) - sum(a[i] * y[n-i]) for i >= 1
17 *
18 * SIMD notes:
19 * Design functions are scalar and called at configuration time, not per
20 * sample. cascade() is a small convolution and is not hot-path.
21 * max_pole_magnitude() carries an Eigen dependency confined to the .cpp.
22 */
23
25
26// ============================================================================
27// Biquad design
28// ============================================================================
29
30/**
31 * @brief Second-order lowpass coefficients
32 * @param frequency Cutoff in Hz, clamped to (0, sample_rate/2)
33 * @param q Quality factor; 0.7071 is maximally flat
34 * @param sample_rate Sampling rate in Hz
35 * @param a Denominator output, resized to 3, a[0] == 1.0
36 * @param b Numerator output, resized to 3
37 */
38MAYAFLUX_API void biquad_lowpass(double frequency, double q, double sample_rate,
39 std::vector<double>& a, std::vector<double>& b);
40
41/**
42 * @brief Second-order highpass coefficients
43 * @param frequency Cutoff in Hz, clamped to (0, sample_rate/2)
44 * @param q Quality factor; 0.7071 is maximally flat
45 * @param sample_rate Sampling rate in Hz
46 * @param a Denominator output, resized to 3, a[0] == 1.0
47 * @param b Numerator output, resized to 3
48 */
49MAYAFLUX_API void biquad_highpass(double frequency, double q, double sample_rate,
50 std::vector<double>& a, std::vector<double>& b);
51
52/**
53 * @brief Second-order bandpass coefficients, constant 0 dB peak gain
54 * @param frequency Centre frequency in Hz, clamped to (0, sample_rate/2)
55 * @param q Quality factor; higher is narrower
56 * @param sample_rate Sampling rate in Hz
57 * @param a Denominator output, resized to 3, a[0] == 1.0
58 * @param b Numerator output, resized to 3
59 */
60MAYAFLUX_API void biquad_bandpass(double frequency, double q, double sample_rate,
61 std::vector<double>& a, std::vector<double>& b);
62
63/**
64 * @brief Second-order notch coefficients
65 * @param frequency Rejection frequency in Hz, clamped to (0, sample_rate/2)
66 * @param q Quality factor; higher is narrower
67 * @param sample_rate Sampling rate in Hz
68 * @param a Denominator output, resized to 3, a[0] == 1.0
69 * @param b Numerator output, resized to 3
70 */
71MAYAFLUX_API void biquad_notch(double frequency, double q, double sample_rate,
72 std::vector<double>& a, std::vector<double>& b);
73
74/**
75 * @brief Second-order allpass coefficients
76 *
77 * Unity magnitude at all frequencies with frequency-dependent phase shift.
78 * Useful for phase manipulation and dispersion without spectral change.
79 *
80 * @param frequency Centre of phase transition in Hz
81 * @param q Sharpness of the phase transition
82 * @param sample_rate Sampling rate in Hz
83 * @param a Denominator output, resized to 3, a[0] == 1.0
84 * @param b Numerator output, resized to 3
85 */
86MAYAFLUX_API void biquad_allpass(double frequency, double q, double sample_rate,
87 std::vector<double>& a, std::vector<double>& b);
88
89/**
90 * @brief Second-order peaking coefficients
91 * @param frequency Centre frequency in Hz
92 * @param q Bandwidth control; higher is narrower
93 * @param gain_db Boost (positive) or cut (negative) at centre, in dB
94 * @param sample_rate Sampling rate in Hz
95 * @param a Denominator output, resized to 3, a[0] == 1.0
96 * @param b Numerator output, resized to 3
97 */
98MAYAFLUX_API void biquad_peaking(double frequency, double q, double gain_db,
99 double sample_rate, std::vector<double>& a, std::vector<double>& b);
100
101/**
102 * @brief Second-order low shelf coefficients
103 * @param frequency Shelf midpoint in Hz
104 * @param slope Shelf slope; 1.0 is the steepest without overshoot
105 * @param gain_db Shelf level relative to unity, in dB
106 * @param sample_rate Sampling rate in Hz
107 * @param a Denominator output, resized to 3, a[0] == 1.0
108 * @param b Numerator output, resized to 3
109 */
110MAYAFLUX_API void biquad_low_shelf(double frequency, double slope, double gain_db,
111 double sample_rate, std::vector<double>& a, std::vector<double>& b);
112
113/**
114 * @brief Second-order high shelf coefficients
115 * @param frequency Shelf midpoint in Hz
116 * @param slope Shelf slope; 1.0 is the steepest without overshoot
117 * @param gain_db Shelf level relative to unity, in dB
118 * @param sample_rate Sampling rate in Hz
119 * @param a Denominator output, resized to 3, a[0] == 1.0
120 * @param b Numerator output, resized to 3
121 */
122MAYAFLUX_API void biquad_high_shelf(double frequency, double slope, double gain_db,
123 double sample_rate, std::vector<double>& a, std::vector<double>& b);
124
125// ============================================================================
126// Windowed-sinc FIR design
127// ============================================================================
128
129/**
130 * @brief Windowed-sinc lowpass FIR coefficients
131 *
132 * Truncates the ideal lowpass impulse response to @p taps and applies a Hann
133 * taper. Transition width narrows as taps increases; stopband attenuation is
134 * set by the taper, not the length.
135 *
136 * Returned in FIR tap order: index 0 multiplies the newest sample. Group
137 * delay is (taps - 1) / 2 samples.
138 *
139 * @param frequency Cutoff in Hz, clamped to (0, sample_rate/2)
140 * @param sample_rate Sampling rate in Hz
141 * @param taps Kernel length; odd values give an integer group delay
142 * @return Coefficient vector of length taps
143 */
144[[nodiscard]] MAYAFLUX_API std::vector<double> sinc_lowpass(
145 double frequency, double sample_rate, size_t taps);
146
147/**
148 * @brief Windowed-sinc highpass FIR coefficients
149 *
150 * Spectral inversion of the corresponding lowpass. Requires odd @p taps for
151 * the inversion to be exact; even lengths are incremented internally.
152 *
153 * @param frequency Cutoff in Hz, clamped to (0, sample_rate/2)
154 * @param sample_rate Sampling rate in Hz
155 * @param taps Kernel length, forced odd
156 * @return Coefficient vector of length taps or taps + 1
157 */
158[[nodiscard]] MAYAFLUX_API std::vector<double> sinc_highpass(
159 double frequency, double sample_rate, size_t taps);
160
161/**
162 * @brief Windowed-sinc bandpass FIR coefficients
163 *
164 * Difference of two lowpass kernels. Requires low_hz < high_hz; the arguments
165 * are swapped internally if supplied in the other order.
166 *
167 * @param low_hz Lower cutoff in Hz
168 * @param high_hz Upper cutoff in Hz
169 * @param sample_rate Sampling rate in Hz
170 * @param taps Kernel length, forced odd
171 * @return Coefficient vector of length taps or taps + 1
172 */
173[[nodiscard]] MAYAFLUX_API std::vector<double> sinc_bandpass(
174 double low_hz, double high_hz, double sample_rate, size_t taps);
175
176// ============================================================================
177// Least-squares polynomial kernels
178// ============================================================================
179
180/**
181 * @brief Savitzky-Golay FIR coefficients for smoothing or differentiation
182 *
183 * Fits a polynomial of degree @p poly_order to a sliding window by least
184 * squares and evaluates the requested derivative of that fit at the window
185 * centre. With derivative 0 this smooths while preserving peak shape better
186 * than a boxcar of the same length; with derivative 1 or 2 it estimates rate
187 * of change with far less noise amplification than a raw finite difference,
188 * since the differentiation acts on the fitted polynomial rather than on the
189 * samples.
190 *
191 * This is the block and node form of the kernel that
192 * Kinesis::Differential::backward_difference computes per sample on glm types.
193 * The streaming form divides by an observed dt and handles irregular timing;
194 * this form assumes uniform spacing and returns coefficients in sample units,
195 * so a caller needing physical units scales by 1 / dt^derivative.
196 *
197 * Returned in FIR tap order: index 0 multiplies the newest sample. Group
198 * delay is (window - 1) / 2 samples.
199 *
200 * @param window Kernel length, forced odd, must exceed poly_order
201 * @param poly_order Degree of the fitted polynomial
202 * @param derivative Which derivative of the fit to evaluate; 0 smooths
203 * @return Coefficient vector of length window, or empty if window <= poly_order
204 */
205[[nodiscard]] MAYAFLUX_API std::vector<double> savitzky_golay(
206 size_t window, size_t poly_order, size_t derivative = 0);
207
208/**
209 * @brief Lagrange fractional-delay FIR coefficients
210 *
211 * Interpolates between samples at a non-integer offset by fitting a polynomial
212 * through @p order + 1 consecutive points. Order 1 is linear interpolation;
213 * order 3 at a fractional position reproduces the Catmull-Rom weights held as
214 * a matrix in Kinesis::BasisMatrices, generalised to any order.
215 *
216 * Accuracy is highest when @p delay falls near the centre of the tap span,
217 * so callers wanting a delay of D samples with order N typically use an
218 * integer delay line of D - N/2 followed by this kernel for the remainder.
219 *
220 * @param delay Delay in samples, need not be integral; clamped to [0, order]
221 * @param order Polynomial order; produces order + 1 taps
222 * @return Coefficient vector of length order + 1
223 */
224[[nodiscard]] MAYAFLUX_API std::vector<double> lagrange_delay(
225 double delay, size_t order);
226
227// ============================================================================
228// Direct pole placement
229// ============================================================================
230
231/**
232 * @brief Two-pole resonator specified by z-plane pole position
233 *
234 * Places a conjugate pole pair at radius @p pole_radius and angle
235 * @p pole_angle rather than deriving them from a cutoff and Q. Ring time
236 * grows as the radius approaches 1.0; the resonant frequency is
237 * pole_angle * sample_rate / (2 * pi).
238 *
239 * The numerator is a single scalar normalising peak magnitude to unity, so
240 * b has length 1 and no zeros are placed. For a resonator with zeros at
241 * DC and Nyquist, use biquad_bandpass instead.
242 *
243 * @param pole_radius Pole magnitude, clamped to [0, 0.9999]
244 * @param pole_angle Pole angle in radians, in [0, pi]
245 * @param a Denominator output, resized to 3, a[0] == 1.0
246 * @param b Numerator output, resized to 1
247 */
248MAYAFLUX_API void resonator(double pole_radius, double pole_angle,
249 std::vector<double>& a, std::vector<double>& b);
250
251/**
252 * @brief One-pole filter specified by pole position
253 *
254 * A positive @p pole gives a lowpass whose smoothing increases as the value
255 * approaches 1.0; a negative pole gives a highpass. The numerator normalises
256 * DC gain to unity for positive poles and Nyquist gain to unity for negative.
257 *
258 * @param pole Pole position on the real axis, clamped to (-0.9999, 0.9999)
259 * @param a Denominator output, resized to 2, a[0] == 1.0
260 * @param b Numerator output, resized to 1
261 */
262MAYAFLUX_API void one_pole(double pole,
263 std::vector<double>& a, std::vector<double>& b);
264
265/**
266 * @brief DC blocking filter
267 *
268 * A zero at DC with a pole just inside it, removing constant offset while
269 * leaving everything above the corner essentially untouched. The corner
270 * frequency falls as @p pole approaches 1.0.
271 *
272 * @param pole Pole position, clamped to [0, 0.9999]; 0.995 is a typical value
273 * @param a Denominator output, resized to 2, a[0] == 1.0
274 * @param b Numerator output, resized to 2
275 */
276MAYAFLUX_API void dc_block(double pole,
277 std::vector<double>& a, std::vector<double>& b);
278
279// ============================================================================
280// Composition
281// ============================================================================
282
283/**
284 * @brief Polynomial product of two coefficient vectors
285 *
286 * Cascading two sections is multiplication of their transfer functions, which
287 * is convolution of the numerators and of the denominators separately. Apply
288 * once to the a pair and once to the b pair to collapse two sections into a
289 * single higher-order recurrence.
290 *
291 * @param lhs First coefficient vector
292 * @param rhs Second coefficient vector
293 * @return Vector of length lhs.size() + rhs.size() - 1
294 */
295[[nodiscard]] MAYAFLUX_API std::vector<double> cascade(
296 std::span<const double> lhs, std::span<const double> rhs);
297
298// ============================================================================
299// Stability
300// ============================================================================
301
302/**
303 * @brief Largest pole magnitude of a denominator polynomial
304 *
305 * Roots of a[0] + a[1]z^-1 + ... are found via the companion matrix
306 * eigenvalues. A return value below 1.0 indicates a stable recurrence;
307 * at or above 1.0 the recurrence diverges.
308 *
309 * Orders 1 and 2 are closed-form. Higher orders carry an Eigen dependency.
310 *
311 * @param a Denominator coefficients, a[0] must be non-zero
312 * @return Largest |root|, or 0.0 for a constant polynomial
313 */
314[[nodiscard]] MAYAFLUX_API double max_pole_magnitude(std::span<const double> a);
315
316} // namespace MayaFlux::Kinesis::Discrete
size_t a
size_t b
double frequency
double q
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 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.