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| {
551 let x = n as f32 - m;
552 2.0 * high_norm * sinc(2.0 * high_norm * x) - 2.0 * low_norm * sinc(2.0 * low_norm * x)
553 }).collect();
554 window.apply(&mut coeffs);
555 let fc = 0.5 * (low_norm + high_norm);
557 let gain: f32 = coeffs.iter().enumerate()
558 .map(|(n, &c)| c * (2.0 * PI * fc * (n as f32 - m)).cos())
559 .sum();
560 if gain.abs() > 1e-10 {
561 for c in coeffs.iter_mut() { *c /= gain; }
562 }
563 FirFilter::new(coeffs)
564 }
565
566 pub fn bandstop_windowed(low_norm: f32, high_norm: f32, num_taps: usize, window: WindowFunction) -> FirFilter {
568 let lp = Self::lowpass_windowed(low_norm, num_taps, window);
569 let hp = Self::highpass_windowed(high_norm, num_taps, window);
570 let coeffs: Vec<f32> = lp.coefficients.iter().zip(hp.coefficients.iter())
571 .map(|(&a, &b)| a + b)
572 .collect();
573 FirFilter::new(coeffs)
574 }
575
576 pub fn equiripple_lowpass(cutoff_norm: f32, num_taps: usize) -> FirFilter {
579 let a_stop = 80.0f32; let beta = if a_stop > 50.0 {
585 0.1102 * (a_stop - 8.7)
586 } else if a_stop >= 21.0 {
587 0.5842 * (a_stop - 21.0).powf(0.4) + 0.07886 * (a_stop - 21.0)
588 } else {
589 0.0
590 };
591 Self::lowpass_windowed(cutoff_norm, num_taps, WindowFunction::Kaiser(beta))
592 }
593
594 pub fn differentiator(num_taps: usize) -> FirFilter {
596 let m = (num_taps - 1) as f32 / 2.0;
597 let mut coeffs: Vec<f32> = (0..num_taps).map(|n| {
598 let x = n as f32 - m;
599 if x.abs() < 1e-10 { 0.0 } else { (PI * x).cos() / x - (PI * x).sin() / (PI * x * x) }
600 }).collect();
601 WindowFunction::Hamming.apply(&mut coeffs);
602 FirFilter::new(coeffs)
603 }
604
605 pub fn hilbert(num_taps: usize) -> FirFilter {
607 assert!(num_taps % 2 == 1, "Hilbert FIR requires odd number of taps");
608 let m = (num_taps - 1) / 2;
609 let mut coeffs: Vec<f32> = (0..num_taps).map(|n| {
610 let k = n as i32 - m as i32;
611 if k == 0 { 0.0 }
612 else if k % 2 == 0 { 0.0 }
613 else { 2.0 / (PI * k as f32) }
614 }).collect();
615 WindowFunction::Hamming.apply(&mut coeffs);
616 FirFilter::new(coeffs)
617 }
618}
619
620pub struct Convolution;
626
627impl Convolution {
628 pub fn convolve_direct(signal: &[f32], kernel: &[f32]) -> Vec<f32> {
630 if signal.is_empty() || kernel.is_empty() { return Vec::new(); }
631 let out_len = signal.len() + kernel.len() - 1;
632 let mut out = vec![0.0f32; out_len];
633 for (i, &s) in signal.iter().enumerate() {
634 for (j, &k) in kernel.iter().enumerate() {
635 out[i + j] += s * k;
636 }
637 }
638 out
639 }
640
641 pub fn convolve(signal: &[f32], kernel: &[f32]) -> Vec<f32> {
643 if signal.is_empty() || kernel.is_empty() { return Vec::new(); }
644 let out_len = signal.len() + kernel.len() - 1;
646 if out_len <= 64 {
647 return Self::convolve_direct(signal, kernel);
648 }
649 let n = next_power_of_two(out_len);
650 let mut a: Vec<Complex32> = signal.iter().map(|&x| Complex32::new(x, 0.0)).collect();
651 a.resize(n, Complex32::zero());
652 let mut b: Vec<Complex32> = kernel.iter().map(|&x| Complex32::new(x, 0.0)).collect();
653 b.resize(n, Complex32::zero());
654 Fft::forward(&mut a);
655 Fft::forward(&mut b);
656 for (ai, bi) in a.iter_mut().zip(b.iter()) { *ai = *ai * *bi; }
657 Fft::inverse(&mut a);
658 a[..out_len].iter().map(|c| c.re).collect()
659 }
660
661 pub fn correlate(a: &[f32], b: &[f32]) -> Vec<f32> {
663 let b_rev: Vec<f32> = b.iter().rev().copied().collect();
664 Self::convolve(a, &b_rev)
665 }
666}
667
668pub struct OlaConvolver {
674 kernel_fft: Vec<Complex32>,
675 fft_size: usize,
676 block_size: usize,
677 overlap: Vec<f32>,
678}
679
680impl OlaConvolver {
681 pub fn new(kernel: &[f32], block_size: usize) -> Self {
683 let fft_size = next_power_of_two(block_size + kernel.len() - 1);
684 let mut kernel_padded: Vec<Complex32> = kernel.iter().map(|&x| Complex32::new(x, 0.0)).collect();
685 kernel_padded.resize(fft_size, Complex32::zero());
686 Fft::forward(&mut kernel_padded);
687 Self {
688 kernel_fft: kernel_padded,
689 fft_size,
690 block_size,
691 overlap: vec![0.0; fft_size],
692 }
693 }
694
695 pub fn process_block(&mut self, input: &[f32]) -> Vec<f32> {
697 assert_eq!(input.len(), self.block_size);
698 let mut buf: Vec<Complex32> = input.iter().map(|&x| Complex32::new(x, 0.0)).collect();
699 buf.resize(self.fft_size, Complex32::zero());
700 Fft::forward(&mut buf);
701 for (b, &k) in buf.iter_mut().zip(self.kernel_fft.iter()) {
702 *b = *b * k;
703 }
704 Fft::inverse(&mut buf);
705 let mut out = Vec::with_capacity(self.block_size);
707 for i in 0..self.block_size {
708 out.push(buf[i].re + self.overlap[i]);
709 }
710 for i in 0..self.fft_size - self.block_size {
712 self.overlap[i] = buf[self.block_size + i].re;
713 }
714 out
715 }
716
717 pub fn reset(&mut self) {
719 self.overlap.fill(0.0);
720 }
721}
722
723#[derive(Debug, Clone, Copy, PartialEq)]
729pub enum SvfMode {
730 LowPass,
731 HighPass,
732 BandPass,
733 Notch,
734 Peak,
735 AllPass,
736}
737
738#[derive(Debug, Clone)]
740pub struct SvfFilter {
741 pub cutoff_hz: f32,
742 pub resonance: f32,
743 pub mode: SvfMode,
744 sample_rate: f32,
745 ic1eq: f32,
747 ic2eq: f32,
748}
749
750impl SvfFilter {
751 pub fn new(cutoff_hz: f32, resonance: f32, mode: SvfMode, sample_rate: f32) -> Self {
752 Self { cutoff_hz, resonance, mode, sample_rate, ic1eq: 0.0, ic2eq: 0.0 }
753 }
754
755 pub fn set_cutoff(&mut self, hz: f32) { self.cutoff_hz = hz; }
757 pub fn set_resonance(&mut self, r: f32) { self.resonance = r; }
759
760 pub fn process_sample(&mut self, x: f32) -> f32 {
762 let g = (PI * self.cutoff_hz / self.sample_rate).tan();
763 let k = 2.0 - 2.0 * self.resonance.min(0.9999);
764 let a1 = 1.0 / (1.0 + g * (g + k));
765 let a2 = g * a1;
766 let a3 = g * a2;
767
768 let v3 = x - self.ic2eq;
769 let v1 = a1 * self.ic1eq + a2 * v3;
770 let v2 = self.ic2eq + a2 * self.ic1eq + a3 * v3;
771 self.ic1eq = 2.0 * v1 - self.ic1eq;
772 self.ic2eq = 2.0 * v2 - self.ic2eq;
773
774 match self.mode {
775 SvfMode::LowPass => v2,
776 SvfMode::HighPass => x - k * v1 - v2,
777 SvfMode::BandPass => v1,
778 SvfMode::Notch => x - k * v1,
779 SvfMode::Peak => v2 - (x - k * v1 - v2),
780 SvfMode::AllPass => x - 2.0 * k * v1,
781 }
782 }
783
784 pub fn process(&mut self, buffer: &mut [f32]) {
786 for s in buffer.iter_mut() { *s = self.process_sample(*s); }
787 }
788
789 pub fn reset(&mut self) { self.ic1eq = 0.0; self.ic2eq = 0.0; }
791}
792
793#[derive(Debug, Clone, Copy, PartialEq)]
799pub enum CombMode {
800 FeedForward,
801 FeedBack,
802}
803
804#[derive(Debug, Clone)]
806pub struct CombFilter {
807 pub delay_samples: usize,
808 pub gain: f32,
809 pub mode: CombMode,
810 delay_line: Vec<f32>,
811 write_pos: usize,
812}
813
814impl CombFilter {
815 pub fn new(delay_samples: usize, gain: f32, mode: CombMode) -> Self {
816 Self {
817 delay_samples,
818 gain,
819 mode,
820 delay_line: vec![0.0; delay_samples + 1],
821 write_pos: 0,
822 }
823 }
824
825 pub fn process_sample(&mut self, x: f32) -> f32 {
827 let n = self.delay_line.len();
828 let read_pos = (self.write_pos + n - self.delay_samples) % n;
829 let delayed = self.delay_line[read_pos];
830 let y = match self.mode {
831 CombMode::FeedForward => x + self.gain * delayed,
832 CombMode::FeedBack => x + self.gain * delayed,
833 };
834 self.delay_line[self.write_pos] = match self.mode {
835 CombMode::FeedForward => x,
836 CombMode::FeedBack => y,
837 };
838 self.write_pos = (self.write_pos + 1) % n;
839 y
840 }
841
842 pub fn process(&mut self, buffer: &mut [f32]) {
844 for s in buffer.iter_mut() { *s = self.process_sample(*s); }
845 }
846
847 pub fn reset(&mut self) {
849 self.delay_line.fill(0.0);
850 self.write_pos = 0;
851 }
852}
853
854#[derive(Debug, Clone)]
860pub struct AllpassDelay {
861 pub delay_samples: usize,
862 pub feedback: f32,
863 delay_line: Vec<f32>,
864 write_pos: usize,
865}
866
867impl AllpassDelay {
868 pub fn new(delay_samples: usize, feedback: f32) -> Self {
869 Self {
870 delay_samples,
871 feedback,
872 delay_line: vec![0.0; delay_samples + 1],
873 write_pos: 0,
874 }
875 }
876
877 pub fn process_sample(&mut self, x: f32) -> f32 {
879 let n = self.delay_line.len();
880 let read_pos = (self.write_pos + n - self.delay_samples) % n;
881 let buf = self.delay_line[read_pos];
882 let w = x + self.feedback * buf;
886 let out = -self.feedback * w + buf;
887 self.delay_line[self.write_pos] = w;
888 self.write_pos = (self.write_pos + 1) % n;
889 out
890 }
891
892 pub fn process(&mut self, buffer: &mut [f32]) {
894 for s in buffer.iter_mut() { *s = self.process_sample(*s); }
895 }
896
897 pub fn reset(&mut self) {
899 self.delay_line.fill(0.0);
900 self.write_pos = 0;
901 }
902}
903
904#[derive(Debug, Clone)]
910pub struct MovingAverage {
911 pub window_size: usize,
912 buffer: Vec<f32>,
913 write_pos: usize,
914 sum: f32,
915 count: usize,
916}
917
918impl MovingAverage {
919 pub fn new(window_size: usize) -> Self {
920 assert!(window_size > 0);
921 Self {
922 window_size,
923 buffer: vec![0.0; window_size],
924 write_pos: 0,
925 sum: 0.0,
926 count: 0,
927 }
928 }
929
930 pub fn process(&mut self, x: f32) -> f32 {
932 self.sum -= self.buffer[self.write_pos];
933 self.buffer[self.write_pos] = x;
934 self.sum += x;
935 self.write_pos = (self.write_pos + 1) % self.window_size;
936 if self.count < self.window_size { self.count += 1; }
937 self.sum / self.count as f32
938 }
939
940 pub fn process_buffer(&mut self, input: &[f32]) -> Vec<f32> {
942 input.iter().map(|&x| self.process(x)).collect()
943 }
944
945 pub fn value(&self) -> f32 {
947 if self.count == 0 { 0.0 } else { self.sum / self.count as f32 }
948 }
949
950 pub fn reset(&mut self) {
952 self.buffer.fill(0.0);
953 self.write_pos = 0;
954 self.sum = 0.0;
955 self.count = 0;
956 }
957}
958
959#[derive(Debug, Clone)]
969pub struct KalmanFilter1D {
970 pub x: f32,
972 pub p: f32,
974 pub q: f32,
976 pub r: f32,
978}
979
980impl KalmanFilter1D {
981 pub fn new(initial_estimate: f32, q: f32, r: f32) -> Self {
986 Self { x: initial_estimate, p: 1.0, q, r }
987 }
988
989 pub fn predict(&mut self, _dt: f32) {
991 self.p += self.q;
993 }
994
995 pub fn update(&mut self, measurement: f32) {
997 let k = self.p / (self.p + self.r);
999 self.x += k * (measurement - self.x);
1001 self.p *= 1.0 - k;
1003 }
1004
1005 pub fn filter(&mut self, measurement: f32, dt: f32) -> f32 {
1007 self.predict(dt);
1008 self.update(measurement);
1009 self.x
1010 }
1011
1012 pub fn estimate(&self) -> f32 { self.x }
1014
1015 pub fn filter_buffer(&mut self, measurements: &[f32], dt: f32) -> Vec<f32> {
1017 measurements.iter().map(|&m| self.filter(m, dt)).collect()
1018 }
1019
1020 pub fn reset(&mut self, initial: f32) {
1022 self.x = initial;
1023 self.p = 1.0;
1024 }
1025}
1026
1027#[derive(Debug, Clone)]
1035pub struct PllFilter {
1036 pub natural_freq_hz: f32,
1038 pub damping: f32,
1040 sample_rate: f32,
1041 phase: f32,
1043 freq: f32,
1045 integrator: f32,
1047 kp: f32,
1049 ki: f32,
1050}
1051
1052impl PllFilter {
1053 pub fn new(center_freq_hz: f32, natural_freq_hz: f32, damping: f32, sample_rate: f32) -> Self {
1058 let wn = 2.0 * PI * natural_freq_hz / sample_rate;
1059 let kp = 2.0 * damping * wn;
1060 let ki = wn * wn;
1061 Self {
1062 natural_freq_hz,
1063 damping,
1064 sample_rate,
1065 phase: 0.0,
1066 freq: 2.0 * PI * center_freq_hz / sample_rate,
1067 integrator: 2.0 * PI * center_freq_hz / sample_rate,
1068 kp,
1069 ki,
1070 }
1071 }
1072
1073 pub fn process_sample(&mut self, input: f32) -> f32 {
1076 let vco_i = self.phase.cos();
1078 let vco_q = self.phase.sin();
1079 let _phase_error_unused = input * vco_q - 0.0 * vco_i; let phase_error = input * (-self.phase).sin(); self.integrator += self.ki * phase_error;
1083 self.freq = self.integrator + self.kp * phase_error;
1084 self.phase += self.freq;
1086 self.phase = Self::wrap_phase(self.phase);
1087 self.phase.cos()
1088 }
1089
1090 pub fn process_buffer(&mut self, input: &[f32]) -> Vec<f32> {
1092 input.iter().map(|&x| self.process_sample(x)).collect()
1093 }
1094
1095 pub fn frequency_hz(&self) -> f32 {
1097 self.freq * self.sample_rate / (2.0 * PI)
1098 }
1099
1100 pub fn phase(&self) -> f32 { self.phase }
1102
1103 pub fn reset(&mut self) {
1105 self.phase = 0.0;
1106 self.integrator = self.freq;
1107 }
1108
1109 fn wrap_phase(p: f32) -> f32 {
1111 let mut p = p;
1112 while p > PI { p -= 2.0 * PI; }
1113 while p < -PI { p += 2.0 * PI; }
1114 p
1115 }
1116}
1117
1118#[cfg(test)]
1123mod tests {
1124 use super::*;
1125 use crate::dsp::SignalGenerator;
1126
1127 fn sine_buf(freq_hz: f32, sr: f32, len: usize) -> Vec<f32> {
1128 (0..len).map(|i| (2.0 * PI * freq_hz * i as f32 / sr).sin()).collect()
1129 }
1130
1131 fn rms(buf: &[f32]) -> f32 {
1132 let sum: f32 = buf.iter().map(|&x| x * x).sum();
1133 (sum / buf.len() as f32).sqrt()
1134 }
1135
1136 #[test]
1139 fn test_biquad_identity() {
1140 let mut bq = Biquad::identity();
1141 let input = vec![1.0, 0.5, -0.3, 0.8];
1142 let mut buf = input.clone();
1143 bq.process(&mut buf);
1144 for (&a, &b) in input.iter().zip(buf.iter()) {
1145 assert!((a - b).abs() < 1e-6);
1146 }
1147 }
1148
1149 #[test]
1150 fn test_biquad_lowpass_attenuates_high_freq() {
1151 let sr = 44100.0;
1152 let mut lp = BiquadDesign::lowpass(500.0, 0.707, sr);
1153 let hi_freq = sine_buf(10000.0, sr, 4410);
1154 let mut buf = hi_freq.clone();
1155 lp.process(&mut buf);
1156 assert!(rms(&buf) < rms(&hi_freq) * 0.5);
1158 }
1159
1160 #[test]
1161 fn test_biquad_highpass_passes_high_freq() {
1162 let sr = 44100.0;
1163 let mut hp = BiquadDesign::highpass(1000.0, 0.707, sr);
1164 let hi_buf = sine_buf(10000.0, sr, 4410);
1165 let mut buf = hi_buf.clone();
1166 hp.process(&mut buf);
1167 assert!(rms(&buf) > rms(&hi_buf) * 0.5);
1169 }
1170
1171 #[test]
1172 fn test_biquad_reset() {
1173 let sr = 44100.0;
1174 let mut lp = BiquadDesign::lowpass(1000.0, 0.707, sr);
1175 let mut buf = vec![1.0f32; 100];
1176 lp.process(&mut buf);
1177 lp.reset();
1178 assert_eq!(lp.z1, 0.0);
1179 assert_eq!(lp.z2, 0.0);
1180 }
1181
1182 #[test]
1183 fn test_biquad_notch_attenuates_center() {
1184 let sr = 44100.0;
1185 let center = 1000.0f32;
1186 let mut notch = BiquadDesign::notch(center, 10.0, sr);
1187 let buf_in = sine_buf(center, sr, 44100);
1188 let mut buf = buf_in.clone();
1189 for _ in 0..1000 { notch.process_sample(0.0); }
1191 notch.reset();
1192 notch.process(&mut buf);
1193 assert!(rms(&buf) < rms(&buf_in) * 0.3);
1195 }
1196
1197 #[test]
1198 fn test_filter_chain() {
1199 let sr = 44100.0;
1200 let mut chain = FilterChain::new();
1201 chain.push(BiquadDesign::lowpass(1000.0, 0.707, sr));
1202 chain.push(BiquadDesign::lowpass(1000.0, 0.707, sr));
1203 let buf_in = sine_buf(10000.0, sr, 4410);
1204 let mut buf = buf_in.clone();
1205 chain.process(&mut buf);
1206 let mut single = BiquadDesign::lowpass(1000.0, 0.707, sr);
1208 let mut buf2 = buf_in.clone();
1209 single.process(&mut buf2);
1210 assert!(rms(&buf) < rms(&buf2));
1211 }
1212
1213 #[test]
1214 fn test_butterworth_lowpass() {
1215 let sr = 44100.0;
1216 let mut filt = Butterworth::lowpass(4, 1000.0, sr);
1217 let buf_in = sine_buf(10000.0, sr, 4410);
1218 let mut buf = buf_in.clone();
1219 filt.process(&mut buf);
1220 assert!(rms(&buf) < rms(&buf_in) * 0.1);
1221 }
1222
1223 #[test]
1224 fn test_fir_lowpass_dc_gain() {
1225 let fir = FirDesign::lowpass_windowed(0.25, 63, WindowFunction::Hamming);
1227 let dc: Vec<f32> = vec![1.0; 512];
1228 let mut buf = dc.clone();
1229 let mut f = fir;
1230 f.process(&mut buf);
1231 let steady = &buf[200..];
1233 let avg: f32 = steady.iter().sum::<f32>() / steady.len() as f32;
1234 assert!((avg - 1.0).abs() < 0.01, "avg={}", avg);
1235 }
1236
1237 #[test]
1238 fn test_fir_highpass_attenuates_dc() {
1239 let fir = FirDesign::highpass_windowed(0.25, 63, WindowFunction::Hann);
1240 let dc = vec![1.0f32; 512];
1241 let mut buf = dc.clone();
1242 let mut f = fir;
1243 f.process(&mut buf);
1244 let avg: f32 = buf[200..].iter().sum::<f32>() / buf[200..].len() as f32;
1245 assert!(avg.abs() < 0.05, "avg={}", avg);
1246 }
1247
1248 #[test]
1249 fn test_convolution_impulse() {
1250 let sig = vec![1.0f32, 2.0, 3.0, 4.0];
1252 let kernel = vec![1.0f32, 0.0, 0.0];
1253 let out = Convolution::convolve(&sig, &kernel);
1254 assert_eq!(out[0], 1.0);
1255 assert_eq!(out[1], 2.0);
1256 assert_eq!(out[2], 3.0);
1257 assert_eq!(out[3], 4.0);
1258 }
1259
1260 #[test]
1261 fn test_convolution_matches_direct() {
1262 let sig: Vec<f32> = (0..20).map(|i| i as f32 * 0.1).collect();
1263 let kernel: Vec<f32> = vec![0.25, 0.5, 0.25];
1264 let fft_result = Convolution::convolve(&sig, &kernel);
1265 let direct_result = Convolution::convolve_direct(&sig, &kernel);
1266 assert_eq!(fft_result.len(), direct_result.len());
1267 for (a, b) in fft_result.iter().zip(direct_result.iter()) {
1268 assert!((a - b).abs() < 1e-4, "a={}, b={}", a, b);
1269 }
1270 }
1271
1272 #[test]
1273 fn test_ola_convolver_block_processing() {
1274 let kernel = vec![0.25f32, 0.5, 0.25];
1275 let block_size = 64;
1276 let mut ola = OlaConvolver::new(&kernel, block_size);
1277 let input = vec![1.0f32; block_size];
1278 let out = ola.process_block(&input);
1279 assert_eq!(out.len(), block_size);
1280 }
1281
1282 #[test]
1283 fn test_svf_lowpass() {
1284 let sr = 44100.0;
1285 let mut svf = SvfFilter::new(1000.0, 0.0, SvfMode::LowPass, sr);
1286 let hi = sine_buf(10000.0, sr, 4410);
1287 let mut buf = hi.clone();
1288 svf.process(&mut buf);
1289 assert!(rms(&buf) < rms(&hi) * 0.3);
1290 }
1291
1292 #[test]
1293 fn test_svf_highpass() {
1294 let sr = 44100.0;
1295 let mut svf = SvfFilter::new(1000.0, 0.0, SvfMode::HighPass, sr);
1296 let lo = sine_buf(100.0, sr, 4410);
1297 let mut buf = lo.clone();
1298 svf.process(&mut buf);
1299 assert!(rms(&buf) < rms(&lo) * 0.5);
1301 }
1302
1303 #[test]
1304 fn test_comb_feedforward() {
1305 let mut comb = CombFilter::new(100, 0.5, CombMode::FeedForward);
1306 let impulse: Vec<f32> = {
1307 let mut v = vec![0.0f32; 200];
1308 v[0] = 1.0;
1309 v
1310 };
1311 let mut buf = impulse.clone();
1312 comb.process(&mut buf);
1313 assert!((buf[100] - 0.5).abs() < 1e-5);
1315 }
1316
1317 #[test]
1318 fn test_allpass_delay_unity_magnitude() {
1319 let mut ap = AllpassDelay::new(50, 0.5);
1320 let impulse: Vec<f32> = {
1323 let mut v = vec![0.0f32; 400];
1324 v[0] = 1.0;
1325 v
1326 };
1327 let mut buf = impulse.clone();
1328 ap.process(&mut buf);
1329 let energy_in: f32 = impulse.iter().map(|&x| x * x).sum();
1331 let energy_out: f32 = buf.iter().map(|&x| x * x).sum();
1332 assert!((energy_in - energy_out).abs() < 0.01);
1333 }
1334
1335 #[test]
1336 fn test_moving_average_settling() {
1337 let mut ma = MovingAverage::new(8);
1338 for _ in 0..100 { ma.process(1.0); }
1339 assert!((ma.value() - 1.0).abs() < 1e-5);
1340 }
1341
1342 #[test]
1343 fn test_moving_average_step() {
1344 let mut ma = MovingAverage::new(4);
1345 for _ in 0..4 { ma.process(0.0); }
1347 for i in 0..4 {
1348 let v = ma.process(1.0);
1349 assert!(v <= 1.0 && v >= 0.0, "i={} v={}", i, v);
1350 }
1351 assert!((ma.value() - 1.0).abs() < 1e-5);
1352 }
1353
1354 #[test]
1355 fn test_kalman_smoothing() {
1356 let mut kf = KalmanFilter1D::new(0.0, 0.001, 1.0);
1358 let dt = 1.0 / 44100.0;
1359 for _ in 0..1000 {
1360 kf.filter(1.0, dt);
1361 }
1362 assert!((kf.estimate() - 1.0).abs() < 0.05, "est={}", kf.estimate());
1363 }
1364
1365 #[test]
1366 fn test_pll_frequency_lock() {
1367 let sr = 44100.0;
1368 let target_hz = 440.0;
1369 let mut pll = PllFilter::new(target_hz, 5.0, 0.707, sr);
1370 let sig = sine_buf(target_hz, sr, 44100);
1371 for &s in &sig[..22050] { pll.process_sample(s); }
1373 let est_freq = pll.frequency_hz();
1374 assert!(est_freq > 200.0 && est_freq < 1000.0, "est_freq={}", est_freq);
1376 }
1377
1378 #[test]
1379 fn test_biquad_peak_eq() {
1380 let sr = 44100.0;
1381 let mut peak = BiquadDesign::peak_eq(1000.0, 6.0, 1.0, sr);
1382 let buf_in = sine_buf(1000.0, sr, 4410);
1383 let mut buf = buf_in.clone();
1384 peak.process(&mut buf);
1385 assert!(rms(&buf) > rms(&buf_in) * 1.3);
1387 }
1388
1389 #[test]
1390 fn test_chebyshev1_lowpass() {
1391 let sr = 44100.0;
1392 let mut ch = Chebyshev1::lowpass(4, 1000.0, 3.0, sr);
1393 let hi = sine_buf(10000.0, sr, 4410);
1394 let mut buf = hi.clone();
1395 ch.process(&mut buf);
1396 assert!(rms(&buf) < rms(&hi) * 0.1);
1397 }
1398
1399 #[test]
1400 fn test_bessel_lowpass_dc_gain() {
1401 let sr = 44100.0;
1402 let mut bessel = Bessel::lowpass(4, 2000.0, sr);
1403 let dc = vec![1.0f32; 4410];
1404 let mut buf = dc.clone();
1405 bessel.process(&mut buf);
1406 let avg: f32 = buf[1000..].iter().sum::<f32>() / buf[1000..].len() as f32;
1407 assert!((avg - 1.0).abs() < 0.1, "avg={}", avg);
1408 }
1409
1410 #[test]
1411 fn test_fir_bandpass() {
1412 let sr = 44100.0;
1413 let fir = FirDesign::bandpass_windowed(0.1, 0.3, 127, WindowFunction::Blackman);
1414 let lo = sine_buf(100.0, sr, 4410);
1418 let hi = sine_buf(20000.0, sr, 4410);
1419 let mid = sine_buf(8820.0, sr, 4410);
1420 let process = |f: &FirFilter, buf: &[f32]| -> f32 {
1421 let mut b = buf.to_vec();
1422 let mut ff = f.clone();
1423 ff.process(&mut b);
1424 rms(&b)
1425 };
1426 assert!(process(&fir, &mid) > process(&fir, &lo));
1427 assert!(process(&fir, &mid) > process(&fir, &hi));
1428 }
1429
1430 #[test]
1431 fn test_hilbert_fir_length() {
1432 let h = FirDesign::hilbert(63);
1433 assert_eq!(h.num_taps(), 63);
1434 }
1435}