1use std::f32::consts::PI;
5use super::{WindowFunction, sinc, next_power_of_two};
6use super::fft::{Fft, Complex32};
7
8#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum BiquadType {
15 LowPass,
16 HighPass,
17 BandPass,
18 Notch,
19 Peak,
20 LowShelf,
21 HighShelf,
22 AllPass,
23}
24
25#[derive(Debug, Clone)]
33pub struct Biquad {
34 pub b0: f32,
36 pub b1: f32,
37 pub b2: f32,
38 pub a1: f32,
40 pub a2: f32,
41 pub z1: f32,
43 pub z2: f32,
44 pub filter_type: BiquadType,
46}
47
48impl Biquad {
49 pub fn new(b0: f32, b1: f32, b2: f32, a1: f32, a2: f32, filter_type: BiquadType) -> Self {
51 Self { b0, b1, b2, a1, a2, z1: 0.0, z2: 0.0, filter_type }
52 }
53
54 pub fn identity() -> Self {
56 Self::new(1.0, 0.0, 0.0, 0.0, 0.0, BiquadType::AllPass)
57 }
58
59 #[inline]
61 pub fn process_sample(&mut self, x: f32) -> f32 {
62 let y = self.b0 * x + self.z1;
63 self.z1 = self.b1 * x - self.a1 * y + self.z2;
64 self.z2 = self.b2 * x - self.a2 * y;
65 y
66 }
67
68 pub fn process(&mut self, buffer: &mut [f32]) {
70 for s in buffer.iter_mut() {
71 *s = self.process_sample(*s);
72 }
73 }
74
75 pub fn reset(&mut self) {
77 self.z1 = 0.0;
78 self.z2 = 0.0;
79 }
80
81 pub fn magnitude_response(&self, omega: f32) -> f32 {
83 let z = Complex32::from_polar(1.0, omega);
84 let z_inv = z.conj(); let z_inv2 = z_inv * z_inv;
86 let num = Complex32::new(self.b0, 0.0)
87 + Complex32::new(self.b1, 0.0) * z_inv
88 + Complex32::new(self.b2, 0.0) * z_inv2;
89 let den = Complex32::new(1.0, 0.0)
90 + Complex32::new(self.a1, 0.0) * z_inv
91 + Complex32::new(self.a2, 0.0) * z_inv2;
92 (num / den).norm()
93 }
94}
95
96pub struct BiquadDesign;
102
103impl BiquadDesign {
104 fn omega(cutoff_hz: f32, sample_rate: f32) -> f32 {
105 2.0 * PI * cutoff_hz / sample_rate
106 }
107
108 pub fn lowpass(cutoff_hz: f32, q: f32, sample_rate: f32) -> Biquad {
110 let w0 = Self::omega(cutoff_hz, sample_rate);
111 let cos_w0 = w0.cos();
112 let sin_w0 = w0.sin();
113 let alpha = sin_w0 / (2.0 * q);
114 let b1 = 1.0 - cos_w0;
115 let b0 = b1 / 2.0;
116 let b2 = b0;
117 let a0 = 1.0 + alpha;
118 Biquad::new(
119 b0 / a0, b1 / a0, b2 / a0,
120 (-2.0 * cos_w0) / a0, (1.0 - alpha) / a0,
121 BiquadType::LowPass,
122 )
123 }
124
125 pub fn highpass(cutoff_hz: f32, q: f32, sample_rate: f32) -> Biquad {
127 let w0 = Self::omega(cutoff_hz, sample_rate);
128 let cos_w0 = w0.cos();
129 let sin_w0 = w0.sin();
130 let alpha = sin_w0 / (2.0 * q);
131 let b0 = (1.0 + cos_w0) / 2.0;
132 let b1 = -(1.0 + cos_w0);
133 let b2 = b0;
134 let a0 = 1.0 + alpha;
135 Biquad::new(
136 b0 / a0, b1 / a0, b2 / a0,
137 (-2.0 * cos_w0) / a0, (1.0 - alpha) / a0,
138 BiquadType::HighPass,
139 )
140 }
141
142 pub fn bandpass(center_hz: f32, bandwidth_hz: f32, sample_rate: f32) -> Biquad {
144 let q = center_hz / bandwidth_hz.max(1e-3);
145 let w0 = Self::omega(center_hz, sample_rate);
146 let cos_w0 = w0.cos();
147 let sin_w0 = w0.sin();
148 let alpha = sin_w0 / (2.0 * q);
149 let b0 = alpha;
150 let b1 = 0.0;
151 let b2 = -alpha;
152 let a0 = 1.0 + alpha;
153 Biquad::new(
154 b0 / a0, b1 / a0, b2 / a0,
155 (-2.0 * cos_w0) / a0, (1.0 - alpha) / a0,
156 BiquadType::BandPass,
157 )
158 }
159
160 pub fn notch(center_hz: f32, q: f32, sample_rate: f32) -> Biquad {
162 let w0 = Self::omega(center_hz, sample_rate);
163 let cos_w0 = w0.cos();
164 let sin_w0 = w0.sin();
165 let alpha = sin_w0 / (2.0 * q);
166 let b0 = 1.0;
167 let b1 = -2.0 * cos_w0;
168 let b2 = 1.0;
169 let a0 = 1.0 + alpha;
170 Biquad::new(
171 b0 / a0, b1 / a0, b2 / a0,
172 (-2.0 * cos_w0) / a0, (1.0 - alpha) / a0,
173 BiquadType::Notch,
174 )
175 }
176
177 pub fn peak_eq(center_hz: f32, gain_db: f32, q: f32, sample_rate: f32) -> Biquad {
179 let w0 = Self::omega(center_hz, sample_rate);
180 let cos_w0 = w0.cos();
181 let sin_w0 = w0.sin();
182 let a_lin = 10.0f32.powf(gain_db / 40.0);
183 let alpha = sin_w0 / (2.0 * q);
184 let b0 = 1.0 + alpha * a_lin;
185 let b1 = -2.0 * cos_w0;
186 let b2 = 1.0 - alpha * a_lin;
187 let a0 = 1.0 + alpha / a_lin;
188 let a1_r = -2.0 * cos_w0;
189 let a2_r = 1.0 - alpha / a_lin;
190 Biquad::new(
191 b0 / a0, b1 / a0, b2 / a0,
192 a1_r / a0, a2_r / a0,
193 BiquadType::Peak,
194 )
195 }
196
197 pub fn low_shelf(cutoff_hz: f32, gain_db: f32, slope: f32, sample_rate: f32) -> Biquad {
199 let w0 = Self::omega(cutoff_hz, sample_rate);
200 let cos_w0 = w0.cos();
201 let sin_w0 = w0.sin();
202 let a_lin = 10.0f32.powf(gain_db / 40.0);
203 let alpha = sin_w0 / 2.0 * ((a_lin + 1.0 / a_lin) * (1.0 / slope - 1.0) + 2.0).sqrt();
204 let a_p1 = a_lin + 1.0;
205 let a_m1 = a_lin - 1.0;
206 let b0 = a_lin * (a_p1 - a_m1 * cos_w0 + 2.0 * a_lin.sqrt() * alpha);
207 let b1 = 2.0 * a_lin * (a_m1 - a_p1 * cos_w0);
208 let b2 = a_lin * (a_p1 - a_m1 * cos_w0 - 2.0 * a_lin.sqrt() * alpha);
209 let a0 = a_p1 + a_m1 * cos_w0 + 2.0 * a_lin.sqrt() * alpha;
210 let a1_r = -2.0 * (a_m1 + a_p1 * cos_w0);
211 let a2_r = a_p1 + a_m1 * cos_w0 - 2.0 * a_lin.sqrt() * alpha;
212 Biquad::new(
213 b0 / a0, b1 / a0, b2 / a0,
214 a1_r / a0, a2_r / a0,
215 BiquadType::LowShelf,
216 )
217 }
218
219 pub fn high_shelf(cutoff_hz: f32, gain_db: f32, slope: f32, sample_rate: f32) -> Biquad {
221 let w0 = Self::omega(cutoff_hz, sample_rate);
222 let cos_w0 = w0.cos();
223 let sin_w0 = w0.sin();
224 let a_lin = 10.0f32.powf(gain_db / 40.0);
225 let alpha = sin_w0 / 2.0 * ((a_lin + 1.0 / a_lin) * (1.0 / slope - 1.0) + 2.0).sqrt();
226 let a_p1 = a_lin + 1.0;
227 let a_m1 = a_lin - 1.0;
228 let b0 = a_lin * (a_p1 + a_m1 * cos_w0 + 2.0 * a_lin.sqrt() * alpha);
229 let b1 = -2.0 * a_lin * (a_m1 + a_p1 * cos_w0);
230 let b2 = a_lin * (a_p1 + a_m1 * cos_w0 - 2.0 * a_lin.sqrt() * alpha);
231 let a0 = a_p1 - a_m1 * cos_w0 + 2.0 * a_lin.sqrt() * alpha;
232 let a1_r = 2.0 * (a_m1 - a_p1 * cos_w0);
233 let a2_r = a_p1 - a_m1 * cos_w0 - 2.0 * a_lin.sqrt() * alpha;
234 Biquad::new(
235 b0 / a0, b1 / a0, b2 / a0,
236 a1_r / a0, a2_r / a0,
237 BiquadType::HighShelf,
238 )
239 }
240
241 pub fn allpass(cutoff_hz: f32, q: f32, sample_rate: f32) -> Biquad {
243 let w0 = Self::omega(cutoff_hz, sample_rate);
244 let cos_w0 = w0.cos();
245 let sin_w0 = w0.sin();
246 let alpha = sin_w0 / (2.0 * q);
247 let b0 = 1.0 - alpha;
248 let b1 = -2.0 * cos_w0;
249 let b2 = 1.0 + alpha;
250 let a0 = 1.0 + alpha;
251 Biquad::new(
252 b0 / a0, b1 / a0, b2 / a0,
253 (-2.0 * cos_w0) / a0, (1.0 - alpha) / a0,
254 BiquadType::AllPass,
255 )
256 }
257}
258
259#[derive(Debug, Clone)]
265pub struct FilterChain {
266 pub stages: Vec<Biquad>,
267}
268
269impl FilterChain {
270 pub fn new() -> Self { Self { stages: Vec::new() } }
271
272 pub fn with_capacity(n: usize) -> Self {
273 Self { stages: Vec::with_capacity(n) }
274 }
275
276 pub fn push(&mut self, biquad: Biquad) {
278 self.stages.push(biquad);
279 }
280
281 #[inline]
283 pub fn process_sample(&mut self, x: f32) -> f32 {
284 let mut y = x;
285 for stage in self.stages.iter_mut() {
286 y = stage.process_sample(y);
287 }
288 y
289 }
290
291 pub fn process(&mut self, buffer: &mut [f32]) {
293 for s in buffer.iter_mut() {
294 *s = self.process_sample(*s);
295 }
296 }
297
298 pub fn reset(&mut self) {
300 for stage in self.stages.iter_mut() { stage.reset(); }
301 }
302
303 pub fn num_stages(&self) -> usize { self.stages.len() }
305
306 pub fn magnitude_response(&self, omega: f32) -> f32 {
308 self.stages.iter().map(|b| b.magnitude_response(omega)).product()
309 }
310}
311
312impl Default for FilterChain {
313 fn default() -> Self { Self::new() }
314}
315
316pub struct Butterworth;
322
323impl Butterworth {
324 pub fn lowpass(order: u32, cutoff_hz: f32, sample_rate: f32) -> FilterChain {
326 let mut chain = FilterChain::with_capacity(order as usize / 2 + 1);
327 let n_stages = order / 2;
328 for k in 1..=n_stages {
329 let theta = PI * (2 * k + order - 1) as f32 / (2 * order) as f32;
331 let q = -1.0 / (2.0 * theta.cos()); chain.push(BiquadDesign::lowpass(cutoff_hz, q, sample_rate));
333 }
334 if order % 2 == 1 {
335 chain.push(BiquadDesign::lowpass(cutoff_hz, 0.5, sample_rate));
337 }
338 chain
339 }
340
341 pub fn highpass(order: u32, cutoff_hz: f32, sample_rate: f32) -> FilterChain {
343 let mut chain = FilterChain::with_capacity(order as usize / 2 + 1);
344 let n_stages = order / 2;
345 for k in 1..=n_stages {
346 let theta = PI * (2 * k + order - 1) as f32 / (2 * order) as f32;
347 let q = -1.0 / (2.0 * theta.cos());
348 chain.push(BiquadDesign::highpass(cutoff_hz, q, sample_rate));
349 }
350 if order % 2 == 1 {
351 chain.push(BiquadDesign::highpass(cutoff_hz, 0.5, sample_rate));
352 }
353 chain
354 }
355
356 pub fn bandpass(order: u32, center_hz: f32, bandwidth_hz: f32, sample_rate: f32) -> FilterChain {
358 let mut chain = FilterChain::with_capacity(order as usize);
359 let n_stages = order / 2;
360 for k in 1..=n_stages {
361 let theta = PI * (2 * k + order - 1) as f32 / (2 * order) as f32;
362 let q = -1.0 / (2.0 * theta.cos());
363 chain.push(BiquadDesign::bandpass(center_hz, bandwidth_hz / q, sample_rate));
365 }
366 chain
367 }
368}
369
370pub struct Chebyshev1;
376
377impl Chebyshev1 {
378 pub fn lowpass(order: u32, cutoff_hz: f32, ripple_db: f32, sample_rate: f32) -> FilterChain {
380 let epsilon = (10.0f32.powf(ripple_db / 10.0) - 1.0).sqrt();
381 let n_stages = order / 2;
382 let mut chain = FilterChain::with_capacity(n_stages as usize + 1);
383 let asinh_inv_eps = (1.0 / epsilon).asinh();
384
385 for k in 1..=n_stages {
386 let theta_k = PI * (2 * k - 1) as f32 / (2 * order) as f32;
389 let sigma = -(asinh_inv_eps / order as f32).sinh() * theta_k.sin();
390 let omega = (asinh_inv_eps / order as f32).cosh() * theta_k.cos();
391 let pole_norm = (sigma * sigma + omega * omega).sqrt();
393 let q_analog = pole_norm / (-2.0 * sigma).max(1e-6);
394 let wd = 2.0 * sample_rate * (PI * cutoff_hz / sample_rate).tan() * pole_norm;
396 let wn = wd / (2.0 * PI);
397 let q = q_analog.max(0.5);
398 chain.push(BiquadDesign::lowpass(wn, q, sample_rate));
399 }
400 if order % 2 == 1 {
401 chain.push(BiquadDesign::lowpass(cutoff_hz, 0.5, sample_rate));
402 }
403 chain
404 }
405}
406
407pub struct Bessel;
413
414impl Bessel {
415 pub fn lowpass(order: u32, cutoff_hz: f32, sample_rate: f32) -> FilterChain {
418 let q_values: &[f32] = match order {
421 1 => &[],
422 2 => &[0.5773],
423 3 => &[0.6910],
424 4 => &[0.5219, 0.8055],
425 5 => &[0.5639, 0.9165],
426 6 => &[0.5103, 0.6112, 1.0234],
427 7 => &[0.5324, 0.6608, 1.1262],
428 8 => &[0.5062, 0.5612, 0.7109, 1.2258],
429 _ => &[0.7071], };
431
432 let mut chain = FilterChain::new();
433 for &q in q_values {
434 chain.push(BiquadDesign::lowpass(cutoff_hz, q, sample_rate));
435 }
436 if order % 2 == 1 {
437 chain.push(BiquadDesign::lowpass(cutoff_hz, 0.5, sample_rate));
438 }
439 chain
440 }
441}
442
443#[derive(Debug, Clone)]
449pub struct FirFilter {
450 pub coefficients: Vec<f32>,
452 delay_line: Vec<f32>,
454 write_pos: usize,
456}
457
458impl FirFilter {
459 pub fn new(coefficients: Vec<f32>) -> Self {
461 let n = coefficients.len();
462 Self {
463 coefficients,
464 delay_line: vec![0.0; n],
465 write_pos: 0,
466 }
467 }
468
469 #[inline]
471 pub fn process_sample(&mut self, x: f32) -> f32 {
472 let n = self.coefficients.len();
473 self.delay_line[self.write_pos] = x;
474 let mut acc = 0.0f32;
475 let mut read_pos = self.write_pos;
476 for k in 0..n {
477 acc += self.coefficients[k] * self.delay_line[read_pos];
478 if read_pos == 0 { read_pos = n - 1; } else { read_pos -= 1; }
479 }
480 self.write_pos = (self.write_pos + 1) % n;
481 acc
482 }
483
484 pub fn process(&mut self, buffer: &mut [f32]) {
486 for s in buffer.iter_mut() {
487 *s = self.process_sample(*s);
488 }
489 }
490
491 pub fn reset(&mut self) {
493 self.delay_line.fill(0.0);
494 self.write_pos = 0;
495 }
496
497 pub fn num_taps(&self) -> usize { self.coefficients.len() }
499
500 pub fn group_delay(&self) -> f32 {
502 (self.coefficients.len() - 1) as f32 / 2.0
503 }
504}
505
506pub struct FirDesign;
512
513impl FirDesign {
514 pub fn lowpass_windowed(cutoff_norm: f32, num_taps: usize, window: WindowFunction) -> FirFilter {
517 let m = (num_taps - 1) as f32 / 2.0;
518 let mut coeffs: Vec<f32> = (0..num_taps).map(|n| {
519 let x = n as f32 - m;
520 sinc(2.0 * cutoff_norm * x)
521 }).collect();
522 window.apply(&mut coeffs);
524 let sum: f32 = coeffs.iter().sum();
526 if sum.abs() > 1e-10 {
527 for c in coeffs.iter_mut() { *c /= sum; }
528 }
529 FirFilter::new(coeffs)
530 }
531
532 pub fn highpass_windowed(cutoff_norm: f32, num_taps: usize, window: WindowFunction) -> FirFilter {
534 let mut lp = Self::lowpass_windowed(cutoff_norm, num_taps, window);
536 let m = (num_taps - 1) / 2;
537 for (i, c) in lp.coefficients.iter_mut().enumerate() {
538 *c = if i == m { 1.0 - *c } else { -*c };
539 }
540 FirFilter::new(lp.coefficients)
541 }
542
543 pub fn bandpass_windowed(low_norm: f32, high_norm: f32, num_taps: usize, window: WindowFunction) -> FirFilter {
545 let m = (num_taps - 1) as f32 / 2.0;
546 let mut coeffs: Vec<f32> = (0..num_taps).map(|n| {
547 let x = n as f32 - m;
548 sinc(2.0 * high_norm * x) - sinc(2.0 * low_norm * x)
549 }).collect();
550 window.apply(&mut coeffs);
551 let peak: f32 = coeffs.iter().map(|&c| c.abs()).fold(0.0, f32::max);
553 if peak > 1e-10 {
554 for c in coeffs.iter_mut() { *c /= peak; }
555 }
556 FirFilter::new(coeffs)
557 }
558
559 pub fn bandstop_windowed(low_norm: f32, high_norm: f32, num_taps: usize, window: WindowFunction) -> FirFilter {
561 let lp = Self::lowpass_windowed(low_norm, num_taps, window);
562 let hp = Self::highpass_windowed(high_norm, num_taps, window);
563 let coeffs: Vec<f32> = lp.coefficients.iter().zip(hp.coefficients.iter())
564 .map(|(&a, &b)| a + b)
565 .collect();
566 FirFilter::new(coeffs)
567 }
568
569 pub fn equiripple_lowpass(cutoff_norm: f32, num_taps: usize) -> FirFilter {
572 let a_stop = 80.0f32; let beta = if a_stop > 50.0 {
578 0.1102 * (a_stop - 8.7)
579 } else if a_stop >= 21.0 {
580 0.5842 * (a_stop - 21.0).powf(0.4) + 0.07886 * (a_stop - 21.0)
581 } else {
582 0.0
583 };
584 Self::lowpass_windowed(cutoff_norm, num_taps, WindowFunction::Kaiser(beta))
585 }
586
587 pub fn differentiator(num_taps: usize) -> FirFilter {
589 let m = (num_taps - 1) as f32 / 2.0;
590 let mut coeffs: Vec<f32> = (0..num_taps).map(|n| {
591 let x = n as f32 - m;
592 if x.abs() < 1e-10 { 0.0 } else { (PI * x).cos() / x - (PI * x).sin() / (PI * x * x) }
593 }).collect();
594 WindowFunction::Hamming.apply(&mut coeffs);
595 FirFilter::new(coeffs)
596 }
597
598 pub fn hilbert(num_taps: usize) -> FirFilter {
600 assert!(num_taps % 2 == 1, "Hilbert FIR requires odd number of taps");
601 let m = (num_taps - 1) / 2;
602 let mut coeffs: Vec<f32> = (0..num_taps).map(|n| {
603 let k = n as i32 - m as i32;
604 if k == 0 { 0.0 }
605 else if k % 2 == 0 { 0.0 }
606 else { 2.0 / (PI * k as f32) }
607 }).collect();
608 WindowFunction::Hamming.apply(&mut coeffs);
609 FirFilter::new(coeffs)
610 }
611}
612
613pub struct Convolution;
619
620impl Convolution {
621 pub fn convolve_direct(signal: &[f32], kernel: &[f32]) -> Vec<f32> {
623 if signal.is_empty() || kernel.is_empty() { return Vec::new(); }
624 let out_len = signal.len() + kernel.len() - 1;
625 let mut out = vec![0.0f32; out_len];
626 for (i, &s) in signal.iter().enumerate() {
627 for (j, &k) in kernel.iter().enumerate() {
628 out[i + j] += s * k;
629 }
630 }
631 out
632 }
633
634 pub fn convolve(signal: &[f32], kernel: &[f32]) -> Vec<f32> {
636 if signal.is_empty() || kernel.is_empty() { return Vec::new(); }
637 let out_len = signal.len() + kernel.len() - 1;
638 let n = next_power_of_two(out_len);
639 let mut a: Vec<Complex32> = signal.iter().map(|&x| Complex32::new(x, 0.0)).collect();
640 a.resize(n, Complex32::zero());
641 let mut b: Vec<Complex32> = kernel.iter().map(|&x| Complex32::new(x, 0.0)).collect();
642 b.resize(n, Complex32::zero());
643 Fft::forward(&mut a);
644 Fft::forward(&mut b);
645 for (ai, bi) in a.iter_mut().zip(b.iter()) { *ai = *ai * *bi; }
646 Fft::inverse(&mut a);
647 a[..out_len].iter().map(|c| c.re).collect()
648 }
649
650 pub fn correlate(a: &[f32], b: &[f32]) -> Vec<f32> {
652 let b_rev: Vec<f32> = b.iter().rev().copied().collect();
653 Self::convolve(a, &b_rev)
654 }
655}
656
657pub struct OlaConvolver {
663 kernel_fft: Vec<Complex32>,
664 fft_size: usize,
665 block_size: usize,
666 overlap: Vec<f32>,
667}
668
669impl OlaConvolver {
670 pub fn new(kernel: &[f32], block_size: usize) -> Self {
672 let fft_size = next_power_of_two(block_size + kernel.len() - 1);
673 let mut kernel_padded: Vec<Complex32> = kernel.iter().map(|&x| Complex32::new(x, 0.0)).collect();
674 kernel_padded.resize(fft_size, Complex32::zero());
675 Fft::forward(&mut kernel_padded);
676 Self {
677 kernel_fft: kernel_padded,
678 fft_size,
679 block_size,
680 overlap: vec![0.0; fft_size],
681 }
682 }
683
684 pub fn process_block(&mut self, input: &[f32]) -> Vec<f32> {
686 assert_eq!(input.len(), self.block_size);
687 let mut buf: Vec<Complex32> = input.iter().map(|&x| Complex32::new(x, 0.0)).collect();
688 buf.resize(self.fft_size, Complex32::zero());
689 Fft::forward(&mut buf);
690 for (b, &k) in buf.iter_mut().zip(self.kernel_fft.iter()) {
691 *b = *b * k;
692 }
693 Fft::inverse(&mut buf);
694 let mut out = Vec::with_capacity(self.block_size);
696 for i in 0..self.block_size {
697 out.push(buf[i].re + self.overlap[i]);
698 }
699 for i in 0..self.fft_size - self.block_size {
701 self.overlap[i] = buf[self.block_size + i].re;
702 }
703 out
704 }
705
706 pub fn reset(&mut self) {
708 self.overlap.fill(0.0);
709 }
710}
711
712#[derive(Debug, Clone, Copy, PartialEq)]
718pub enum SvfMode {
719 LowPass,
720 HighPass,
721 BandPass,
722 Notch,
723 Peak,
724 AllPass,
725}
726
727#[derive(Debug, Clone)]
729pub struct SvfFilter {
730 pub cutoff_hz: f32,
731 pub resonance: f32,
732 pub mode: SvfMode,
733 sample_rate: f32,
734 ic1eq: f32,
736 ic2eq: f32,
737}
738
739impl SvfFilter {
740 pub fn new(cutoff_hz: f32, resonance: f32, mode: SvfMode, sample_rate: f32) -> Self {
741 Self { cutoff_hz, resonance, mode, sample_rate, ic1eq: 0.0, ic2eq: 0.0 }
742 }
743
744 pub fn set_cutoff(&mut self, hz: f32) { self.cutoff_hz = hz; }
746 pub fn set_resonance(&mut self, r: f32) { self.resonance = r; }
748
749 pub fn process_sample(&mut self, x: f32) -> f32 {
751 let g = (PI * self.cutoff_hz / self.sample_rate).tan();
752 let k = 2.0 - 2.0 * self.resonance.min(0.9999);
753 let a1 = 1.0 / (1.0 + g * (g + k));
754 let a2 = g * a1;
755 let a3 = g * a2;
756
757 let v3 = x - self.ic2eq;
758 let v1 = a1 * self.ic1eq + a2 * v3;
759 let v2 = self.ic2eq + a2 * self.ic1eq + a3 * v3;
760 self.ic1eq = 2.0 * v1 - self.ic1eq;
761 self.ic2eq = 2.0 * v2 - self.ic2eq;
762
763 match self.mode {
764 SvfMode::LowPass => v2,
765 SvfMode::HighPass => x - k * v1 - v2,
766 SvfMode::BandPass => v1,
767 SvfMode::Notch => x - k * v1,
768 SvfMode::Peak => v2 - (x - k * v1 - v2),
769 SvfMode::AllPass => x - 2.0 * k * v1,
770 }
771 }
772
773 pub fn process(&mut self, buffer: &mut [f32]) {
775 for s in buffer.iter_mut() { *s = self.process_sample(*s); }
776 }
777
778 pub fn reset(&mut self) { self.ic1eq = 0.0; self.ic2eq = 0.0; }
780}
781
782#[derive(Debug, Clone, Copy, PartialEq)]
788pub enum CombMode {
789 FeedForward,
790 FeedBack,
791}
792
793#[derive(Debug, Clone)]
795pub struct CombFilter {
796 pub delay_samples: usize,
797 pub gain: f32,
798 pub mode: CombMode,
799 delay_line: Vec<f32>,
800 write_pos: usize,
801}
802
803impl CombFilter {
804 pub fn new(delay_samples: usize, gain: f32, mode: CombMode) -> Self {
805 Self {
806 delay_samples,
807 gain,
808 mode,
809 delay_line: vec![0.0; delay_samples + 1],
810 write_pos: 0,
811 }
812 }
813
814 pub fn process_sample(&mut self, x: f32) -> f32 {
816 let n = self.delay_line.len();
817 let read_pos = (self.write_pos + n - self.delay_samples) % n;
818 let delayed = self.delay_line[read_pos];
819 let y = match self.mode {
820 CombMode::FeedForward => x + self.gain * delayed,
821 CombMode::FeedBack => x + self.gain * delayed,
822 };
823 self.delay_line[self.write_pos] = match self.mode {
824 CombMode::FeedForward => x,
825 CombMode::FeedBack => y,
826 };
827 self.write_pos = (self.write_pos + 1) % n;
828 y
829 }
830
831 pub fn process(&mut self, buffer: &mut [f32]) {
833 for s in buffer.iter_mut() { *s = self.process_sample(*s); }
834 }
835
836 pub fn reset(&mut self) {
838 self.delay_line.fill(0.0);
839 self.write_pos = 0;
840 }
841}
842
843#[derive(Debug, Clone)]
849pub struct AllpassDelay {
850 pub delay_samples: usize,
851 pub feedback: f32,
852 delay_line: Vec<f32>,
853 write_pos: usize,
854}
855
856impl AllpassDelay {
857 pub fn new(delay_samples: usize, feedback: f32) -> Self {
858 Self {
859 delay_samples,
860 feedback,
861 delay_line: vec![0.0; delay_samples + 1],
862 write_pos: 0,
863 }
864 }
865
866 pub fn process_sample(&mut self, x: f32) -> f32 {
868 let n = self.delay_line.len();
869 let read_pos = (self.write_pos + n - self.delay_samples) % n;
870 let buf = self.delay_line[read_pos];
871 let out = -x + buf;
872 self.delay_line[self.write_pos] = x + self.feedback * buf;
873 self.write_pos = (self.write_pos + 1) % n;
874 out
875 }
876
877 pub fn process(&mut self, buffer: &mut [f32]) {
879 for s in buffer.iter_mut() { *s = self.process_sample(*s); }
880 }
881
882 pub fn reset(&mut self) {
884 self.delay_line.fill(0.0);
885 self.write_pos = 0;
886 }
887}
888
889#[derive(Debug, Clone)]
895pub struct MovingAverage {
896 pub window_size: usize,
897 buffer: Vec<f32>,
898 write_pos: usize,
899 sum: f32,
900 count: usize,
901}
902
903impl MovingAverage {
904 pub fn new(window_size: usize) -> Self {
905 assert!(window_size > 0);
906 Self {
907 window_size,
908 buffer: vec![0.0; window_size],
909 write_pos: 0,
910 sum: 0.0,
911 count: 0,
912 }
913 }
914
915 pub fn process(&mut self, x: f32) -> f32 {
917 self.sum -= self.buffer[self.write_pos];
918 self.buffer[self.write_pos] = x;
919 self.sum += x;
920 self.write_pos = (self.write_pos + 1) % self.window_size;
921 if self.count < self.window_size { self.count += 1; }
922 self.sum / self.count as f32
923 }
924
925 pub fn process_buffer(&mut self, input: &[f32]) -> Vec<f32> {
927 input.iter().map(|&x| self.process(x)).collect()
928 }
929
930 pub fn value(&self) -> f32 {
932 if self.count == 0 { 0.0 } else { self.sum / self.count as f32 }
933 }
934
935 pub fn reset(&mut self) {
937 self.buffer.fill(0.0);
938 self.write_pos = 0;
939 self.sum = 0.0;
940 self.count = 0;
941 }
942}
943
944#[derive(Debug, Clone)]
954pub struct KalmanFilter1D {
955 pub x: f32,
957 pub p: f32,
959 pub q: f32,
961 pub r: f32,
963}
964
965impl KalmanFilter1D {
966 pub fn new(initial_estimate: f32, q: f32, r: f32) -> Self {
971 Self { x: initial_estimate, p: 1.0, q, r }
972 }
973
974 pub fn predict(&mut self, _dt: f32) {
976 self.p += self.q;
978 }
979
980 pub fn update(&mut self, measurement: f32) {
982 let k = self.p / (self.p + self.r);
984 self.x += k * (measurement - self.x);
986 self.p *= 1.0 - k;
988 }
989
990 pub fn filter(&mut self, measurement: f32, dt: f32) -> f32 {
992 self.predict(dt);
993 self.update(measurement);
994 self.x
995 }
996
997 pub fn estimate(&self) -> f32 { self.x }
999
1000 pub fn filter_buffer(&mut self, measurements: &[f32], dt: f32) -> Vec<f32> {
1002 measurements.iter().map(|&m| self.filter(m, dt)).collect()
1003 }
1004
1005 pub fn reset(&mut self, initial: f32) {
1007 self.x = initial;
1008 self.p = 1.0;
1009 }
1010}
1011
1012#[derive(Debug, Clone)]
1020pub struct PllFilter {
1021 pub natural_freq_hz: f32,
1023 pub damping: f32,
1025 sample_rate: f32,
1026 phase: f32,
1028 freq: f32,
1030 integrator: f32,
1032 kp: f32,
1034 ki: f32,
1035}
1036
1037impl PllFilter {
1038 pub fn new(center_freq_hz: f32, natural_freq_hz: f32, damping: f32, sample_rate: f32) -> Self {
1043 let wn = 2.0 * PI * natural_freq_hz / sample_rate;
1044 let kp = 2.0 * damping * wn;
1045 let ki = wn * wn;
1046 Self {
1047 natural_freq_hz,
1048 damping,
1049 sample_rate,
1050 phase: 0.0,
1051 freq: 2.0 * PI * center_freq_hz / sample_rate,
1052 integrator: 2.0 * PI * center_freq_hz / sample_rate,
1053 kp,
1054 ki,
1055 }
1056 }
1057
1058 pub fn process_sample(&mut self, input: f32) -> f32 {
1061 let vco_i = self.phase.cos();
1063 let vco_q = self.phase.sin();
1064 let _phase_error_unused = input * vco_q - 0.0 * vco_i; let phase_error = input * (-self.phase).sin(); self.integrator += self.ki * phase_error;
1068 self.freq = self.integrator + self.kp * phase_error;
1069 self.phase += self.freq;
1071 self.phase = Self::wrap_phase(self.phase);
1072 self.phase.cos()
1073 }
1074
1075 pub fn process_buffer(&mut self, input: &[f32]) -> Vec<f32> {
1077 input.iter().map(|&x| self.process_sample(x)).collect()
1078 }
1079
1080 pub fn frequency_hz(&self) -> f32 {
1082 self.freq * self.sample_rate / (2.0 * PI)
1083 }
1084
1085 pub fn phase(&self) -> f32 { self.phase }
1087
1088 pub fn reset(&mut self) {
1090 self.phase = 0.0;
1091 self.integrator = self.freq;
1092 }
1093
1094 fn wrap_phase(p: f32) -> f32 {
1096 let mut p = p;
1097 while p > PI { p -= 2.0 * PI; }
1098 while p < -PI { p += 2.0 * PI; }
1099 p
1100 }
1101}
1102
1103#[cfg(test)]
1108mod tests {
1109 use super::*;
1110 use crate::dsp::SignalGenerator;
1111
1112 fn sine_buf(freq_hz: f32, sr: f32, len: usize) -> Vec<f32> {
1113 (0..len).map(|i| (2.0 * PI * freq_hz * i as f32 / sr).sin()).collect()
1114 }
1115
1116 fn rms(buf: &[f32]) -> f32 {
1117 let sum: f32 = buf.iter().map(|&x| x * x).sum();
1118 (sum / buf.len() as f32).sqrt()
1119 }
1120
1121 #[test]
1124 fn test_biquad_identity() {
1125 let mut bq = Biquad::identity();
1126 let input = vec![1.0, 0.5, -0.3, 0.8];
1127 let mut buf = input.clone();
1128 bq.process(&mut buf);
1129 for (&a, &b) in input.iter().zip(buf.iter()) {
1130 assert!((a - b).abs() < 1e-6);
1131 }
1132 }
1133
1134 #[test]
1135 fn test_biquad_lowpass_attenuates_high_freq() {
1136 let sr = 44100.0;
1137 let mut lp = BiquadDesign::lowpass(500.0, 0.707, sr);
1138 let hi_freq = sine_buf(10000.0, sr, 4410);
1139 let mut buf = hi_freq.clone();
1140 lp.process(&mut buf);
1141 assert!(rms(&buf) < rms(&hi_freq) * 0.5);
1143 }
1144
1145 #[test]
1146 fn test_biquad_highpass_passes_high_freq() {
1147 let sr = 44100.0;
1148 let mut hp = BiquadDesign::highpass(1000.0, 0.707, sr);
1149 let hi_buf = sine_buf(10000.0, sr, 4410);
1150 let mut buf = hi_buf.clone();
1151 hp.process(&mut buf);
1152 assert!(rms(&buf) > rms(&hi_buf) * 0.5);
1154 }
1155
1156 #[test]
1157 fn test_biquad_reset() {
1158 let sr = 44100.0;
1159 let mut lp = BiquadDesign::lowpass(1000.0, 0.707, sr);
1160 let mut buf = vec![1.0f32; 100];
1161 lp.process(&mut buf);
1162 lp.reset();
1163 assert_eq!(lp.z1, 0.0);
1164 assert_eq!(lp.z2, 0.0);
1165 }
1166
1167 #[test]
1168 fn test_biquad_notch_attenuates_center() {
1169 let sr = 44100.0;
1170 let center = 1000.0f32;
1171 let mut notch = BiquadDesign::notch(center, 10.0, sr);
1172 let buf_in = sine_buf(center, sr, 44100);
1173 let mut buf = buf_in.clone();
1174 for _ in 0..1000 { notch.process_sample(0.0); }
1176 notch.reset();
1177 notch.process(&mut buf);
1178 assert!(rms(&buf) < rms(&buf_in) * 0.3);
1180 }
1181
1182 #[test]
1183 fn test_filter_chain() {
1184 let sr = 44100.0;
1185 let mut chain = FilterChain::new();
1186 chain.push(BiquadDesign::lowpass(1000.0, 0.707, sr));
1187 chain.push(BiquadDesign::lowpass(1000.0, 0.707, sr));
1188 let buf_in = sine_buf(10000.0, sr, 4410);
1189 let mut buf = buf_in.clone();
1190 chain.process(&mut buf);
1191 let mut single = BiquadDesign::lowpass(1000.0, 0.707, sr);
1193 let mut buf2 = buf_in.clone();
1194 single.process(&mut buf2);
1195 assert!(rms(&buf) < rms(&buf2));
1196 }
1197
1198 #[test]
1199 fn test_butterworth_lowpass() {
1200 let sr = 44100.0;
1201 let mut filt = Butterworth::lowpass(4, 1000.0, sr);
1202 let buf_in = sine_buf(10000.0, sr, 4410);
1203 let mut buf = buf_in.clone();
1204 filt.process(&mut buf);
1205 assert!(rms(&buf) < rms(&buf_in) * 0.1);
1206 }
1207
1208 #[test]
1209 fn test_fir_lowpass_dc_gain() {
1210 let fir = FirDesign::lowpass_windowed(0.25, 63, WindowFunction::Hamming);
1212 let dc: Vec<f32> = vec![1.0; 512];
1213 let mut buf = dc.clone();
1214 let mut f = fir;
1215 f.process(&mut buf);
1216 let steady = &buf[200..];
1218 let avg: f32 = steady.iter().sum::<f32>() / steady.len() as f32;
1219 assert!((avg - 1.0).abs() < 0.01, "avg={}", avg);
1220 }
1221
1222 #[test]
1223 fn test_fir_highpass_attenuates_dc() {
1224 let fir = FirDesign::highpass_windowed(0.25, 63, WindowFunction::Hann);
1225 let dc = vec![1.0f32; 512];
1226 let mut buf = dc.clone();
1227 let mut f = fir;
1228 f.process(&mut buf);
1229 let avg: f32 = buf[200..].iter().sum::<f32>() / buf[200..].len() as f32;
1230 assert!(avg.abs() < 0.05, "avg={}", avg);
1231 }
1232
1233 #[test]
1234 fn test_convolution_impulse() {
1235 let sig = vec![1.0f32, 2.0, 3.0, 4.0];
1237 let kernel = vec![1.0f32, 0.0, 0.0];
1238 let out = Convolution::convolve(&sig, &kernel);
1239 assert_eq!(out[0], 1.0);
1240 assert_eq!(out[1], 2.0);
1241 assert_eq!(out[2], 3.0);
1242 assert_eq!(out[3], 4.0);
1243 }
1244
1245 #[test]
1246 fn test_convolution_matches_direct() {
1247 let sig: Vec<f32> = (0..20).map(|i| i as f32 * 0.1).collect();
1248 let kernel: Vec<f32> = vec![0.25, 0.5, 0.25];
1249 let fft_result = Convolution::convolve(&sig, &kernel);
1250 let direct_result = Convolution::convolve_direct(&sig, &kernel);
1251 assert_eq!(fft_result.len(), direct_result.len());
1252 for (a, b) in fft_result.iter().zip(direct_result.iter()) {
1253 assert!((a - b).abs() < 1e-4, "a={}, b={}", a, b);
1254 }
1255 }
1256
1257 #[test]
1258 fn test_ola_convolver_block_processing() {
1259 let kernel = vec![0.25f32, 0.5, 0.25];
1260 let block_size = 64;
1261 let mut ola = OlaConvolver::new(&kernel, block_size);
1262 let input = vec![1.0f32; block_size];
1263 let out = ola.process_block(&input);
1264 assert_eq!(out.len(), block_size);
1265 }
1266
1267 #[test]
1268 fn test_svf_lowpass() {
1269 let sr = 44100.0;
1270 let mut svf = SvfFilter::new(1000.0, 0.0, SvfMode::LowPass, sr);
1271 let hi = sine_buf(10000.0, sr, 4410);
1272 let mut buf = hi.clone();
1273 svf.process(&mut buf);
1274 assert!(rms(&buf) < rms(&hi) * 0.3);
1275 }
1276
1277 #[test]
1278 fn test_svf_highpass() {
1279 let sr = 44100.0;
1280 let mut svf = SvfFilter::new(1000.0, 0.0, SvfMode::HighPass, sr);
1281 let lo = sine_buf(100.0, sr, 4410);
1282 let mut buf = lo.clone();
1283 svf.process(&mut buf);
1284 assert!(rms(&buf) < rms(&lo) * 0.5);
1286 }
1287
1288 #[test]
1289 fn test_comb_feedforward() {
1290 let mut comb = CombFilter::new(100, 0.5, CombMode::FeedForward);
1291 let impulse: Vec<f32> = {
1292 let mut v = vec![0.0f32; 200];
1293 v[0] = 1.0;
1294 v
1295 };
1296 let mut buf = impulse.clone();
1297 comb.process(&mut buf);
1298 assert!((buf[100] - 0.5).abs() < 1e-5);
1300 }
1301
1302 #[test]
1303 fn test_allpass_delay_unity_magnitude() {
1304 let mut ap = AllpassDelay::new(50, 0.5);
1305 let impulse: Vec<f32> = {
1306 let mut v = vec![0.0f32; 200];
1307 v[0] = 1.0;
1308 v
1309 };
1310 let mut buf = impulse.clone();
1311 ap.process(&mut buf);
1312 let energy_in: f32 = impulse.iter().map(|&x| x * x).sum();
1314 let energy_out: f32 = buf.iter().map(|&x| x * x).sum();
1315 assert!((energy_in - energy_out).abs() < 0.01);
1316 }
1317
1318 #[test]
1319 fn test_moving_average_settling() {
1320 let mut ma = MovingAverage::new(8);
1321 for _ in 0..100 { ma.process(1.0); }
1322 assert!((ma.value() - 1.0).abs() < 1e-5);
1323 }
1324
1325 #[test]
1326 fn test_moving_average_step() {
1327 let mut ma = MovingAverage::new(4);
1328 for _ in 0..4 { ma.process(0.0); }
1330 for i in 0..4 {
1331 let v = ma.process(1.0);
1332 assert!(v <= 1.0 && v >= 0.0, "i={} v={}", i, v);
1333 }
1334 assert!((ma.value() - 1.0).abs() < 1e-5);
1335 }
1336
1337 #[test]
1338 fn test_kalman_smoothing() {
1339 let mut kf = KalmanFilter1D::new(0.0, 0.001, 1.0);
1341 let dt = 1.0 / 44100.0;
1342 for _ in 0..1000 {
1343 kf.filter(1.0, dt);
1344 }
1345 assert!((kf.estimate() - 1.0).abs() < 0.05, "est={}", kf.estimate());
1346 }
1347
1348 #[test]
1349 fn test_pll_frequency_lock() {
1350 let sr = 44100.0;
1351 let target_hz = 440.0;
1352 let mut pll = PllFilter::new(target_hz, 5.0, 0.707, sr);
1353 let sig = sine_buf(target_hz, sr, 44100);
1354 for &s in &sig[..22050] { pll.process_sample(s); }
1356 let est_freq = pll.frequency_hz();
1357 assert!(est_freq > 200.0 && est_freq < 1000.0, "est_freq={}", est_freq);
1359 }
1360
1361 #[test]
1362 fn test_biquad_peak_eq() {
1363 let sr = 44100.0;
1364 let mut peak = BiquadDesign::peak_eq(1000.0, 6.0, 1.0, sr);
1365 let buf_in = sine_buf(1000.0, sr, 4410);
1366 let mut buf = buf_in.clone();
1367 peak.process(&mut buf);
1368 assert!(rms(&buf) > rms(&buf_in) * 1.3);
1370 }
1371
1372 #[test]
1373 fn test_chebyshev1_lowpass() {
1374 let sr = 44100.0;
1375 let mut ch = Chebyshev1::lowpass(4, 1000.0, 3.0, sr);
1376 let hi = sine_buf(10000.0, sr, 4410);
1377 let mut buf = hi.clone();
1378 ch.process(&mut buf);
1379 assert!(rms(&buf) < rms(&hi) * 0.1);
1380 }
1381
1382 #[test]
1383 fn test_bessel_lowpass_dc_gain() {
1384 let sr = 44100.0;
1385 let mut bessel = Bessel::lowpass(4, 2000.0, sr);
1386 let dc = vec![1.0f32; 4410];
1387 let mut buf = dc.clone();
1388 bessel.process(&mut buf);
1389 let avg: f32 = buf[1000..].iter().sum::<f32>() / buf[1000..].len() as f32;
1390 assert!((avg - 1.0).abs() < 0.1, "avg={}", avg);
1391 }
1392
1393 #[test]
1394 fn test_fir_bandpass() {
1395 let sr = 44100.0;
1396 let fir = FirDesign::bandpass_windowed(0.1, 0.3, 127, WindowFunction::Blackman);
1397 let lo = sine_buf(100.0, sr, 4410);
1398 let hi = sine_buf(10000.0, sr, 4410);
1399 let mid = sine_buf(3000.0, sr, 4410);
1400 let process = |f: &FirFilter, buf: &[f32]| -> f32 {
1401 let mut b = buf.to_vec();
1402 let mut ff = f.clone();
1403 ff.process(&mut b);
1404 rms(&b)
1405 };
1406 assert!(process(&fir, &mid) > process(&fir, &lo));
1407 assert!(process(&fir, &mid) > process(&fir, &hi));
1408 }
1409
1410 #[test]
1411 fn test_hilbert_fir_length() {
1412 let h = FirDesign::hilbert(63);
1413 assert_eq!(h.num_taps(), 63);
1414 }
1415}