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(high_norm * x) - sinc(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;
639 if out_len <= 64 {
640 return Self::convolve_direct(signal, kernel);
641 }
642 let n = next_power_of_two(out_len);
643 let mut a: Vec<Complex32> = signal.iter().map(|&x| Complex32::new(x, 0.0)).collect();
644 a.resize(n, Complex32::zero());
645 let mut b: Vec<Complex32> = kernel.iter().map(|&x| Complex32::new(x, 0.0)).collect();
646 b.resize(n, Complex32::zero());
647 Fft::forward(&mut a);
648 Fft::forward(&mut b);
649 for (ai, bi) in a.iter_mut().zip(b.iter()) { *ai = *ai * *bi; }
650 Fft::inverse(&mut a);
651 a[..out_len].iter().map(|c| c.re).collect()
652 }
653
654 pub fn correlate(a: &[f32], b: &[f32]) -> Vec<f32> {
656 let b_rev: Vec<f32> = b.iter().rev().copied().collect();
657 Self::convolve(a, &b_rev)
658 }
659}
660
661pub struct OlaConvolver {
667 kernel_fft: Vec<Complex32>,
668 fft_size: usize,
669 block_size: usize,
670 overlap: Vec<f32>,
671}
672
673impl OlaConvolver {
674 pub fn new(kernel: &[f32], block_size: usize) -> Self {
676 let fft_size = next_power_of_two(block_size + kernel.len() - 1);
677 let mut kernel_padded: Vec<Complex32> = kernel.iter().map(|&x| Complex32::new(x, 0.0)).collect();
678 kernel_padded.resize(fft_size, Complex32::zero());
679 Fft::forward(&mut kernel_padded);
680 Self {
681 kernel_fft: kernel_padded,
682 fft_size,
683 block_size,
684 overlap: vec![0.0; fft_size],
685 }
686 }
687
688 pub fn process_block(&mut self, input: &[f32]) -> Vec<f32> {
690 assert_eq!(input.len(), self.block_size);
691 let mut buf: Vec<Complex32> = input.iter().map(|&x| Complex32::new(x, 0.0)).collect();
692 buf.resize(self.fft_size, Complex32::zero());
693 Fft::forward(&mut buf);
694 for (b, &k) in buf.iter_mut().zip(self.kernel_fft.iter()) {
695 *b = *b * k;
696 }
697 Fft::inverse(&mut buf);
698 let mut out = Vec::with_capacity(self.block_size);
700 for i in 0..self.block_size {
701 out.push(buf[i].re + self.overlap[i]);
702 }
703 for i in 0..self.fft_size - self.block_size {
705 self.overlap[i] = buf[self.block_size + i].re;
706 }
707 out
708 }
709
710 pub fn reset(&mut self) {
712 self.overlap.fill(0.0);
713 }
714}
715
716#[derive(Debug, Clone, Copy, PartialEq)]
722pub enum SvfMode {
723 LowPass,
724 HighPass,
725 BandPass,
726 Notch,
727 Peak,
728 AllPass,
729}
730
731#[derive(Debug, Clone)]
733pub struct SvfFilter {
734 pub cutoff_hz: f32,
735 pub resonance: f32,
736 pub mode: SvfMode,
737 sample_rate: f32,
738 ic1eq: f32,
740 ic2eq: f32,
741}
742
743impl SvfFilter {
744 pub fn new(cutoff_hz: f32, resonance: f32, mode: SvfMode, sample_rate: f32) -> Self {
745 Self { cutoff_hz, resonance, mode, sample_rate, ic1eq: 0.0, ic2eq: 0.0 }
746 }
747
748 pub fn set_cutoff(&mut self, hz: f32) { self.cutoff_hz = hz; }
750 pub fn set_resonance(&mut self, r: f32) { self.resonance = r; }
752
753 pub fn process_sample(&mut self, x: f32) -> f32 {
755 let g = (PI * self.cutoff_hz / self.sample_rate).tan();
756 let k = 2.0 - 2.0 * self.resonance.min(0.9999);
757 let a1 = 1.0 / (1.0 + g * (g + k));
758 let a2 = g * a1;
759 let a3 = g * a2;
760
761 let v3 = x - self.ic2eq;
762 let v1 = a1 * self.ic1eq + a2 * v3;
763 let v2 = self.ic2eq + a2 * self.ic1eq + a3 * v3;
764 self.ic1eq = 2.0 * v1 - self.ic1eq;
765 self.ic2eq = 2.0 * v2 - self.ic2eq;
766
767 match self.mode {
768 SvfMode::LowPass => v2,
769 SvfMode::HighPass => x - k * v1 - v2,
770 SvfMode::BandPass => v1,
771 SvfMode::Notch => x - k * v1,
772 SvfMode::Peak => v2 - (x - k * v1 - v2),
773 SvfMode::AllPass => x - 2.0 * k * v1,
774 }
775 }
776
777 pub fn process(&mut self, buffer: &mut [f32]) {
779 for s in buffer.iter_mut() { *s = self.process_sample(*s); }
780 }
781
782 pub fn reset(&mut self) { self.ic1eq = 0.0; self.ic2eq = 0.0; }
784}
785
786#[derive(Debug, Clone, Copy, PartialEq)]
792pub enum CombMode {
793 FeedForward,
794 FeedBack,
795}
796
797#[derive(Debug, Clone)]
799pub struct CombFilter {
800 pub delay_samples: usize,
801 pub gain: f32,
802 pub mode: CombMode,
803 delay_line: Vec<f32>,
804 write_pos: usize,
805}
806
807impl CombFilter {
808 pub fn new(delay_samples: usize, gain: f32, mode: CombMode) -> Self {
809 Self {
810 delay_samples,
811 gain,
812 mode,
813 delay_line: vec![0.0; delay_samples + 1],
814 write_pos: 0,
815 }
816 }
817
818 pub fn process_sample(&mut self, x: f32) -> f32 {
820 let n = self.delay_line.len();
821 let read_pos = (self.write_pos + n - self.delay_samples) % n;
822 let delayed = self.delay_line[read_pos];
823 let y = match self.mode {
824 CombMode::FeedForward => x + self.gain * delayed,
825 CombMode::FeedBack => x + self.gain * delayed,
826 };
827 self.delay_line[self.write_pos] = match self.mode {
828 CombMode::FeedForward => x,
829 CombMode::FeedBack => y,
830 };
831 self.write_pos = (self.write_pos + 1) % n;
832 y
833 }
834
835 pub fn process(&mut self, buffer: &mut [f32]) {
837 for s in buffer.iter_mut() { *s = self.process_sample(*s); }
838 }
839
840 pub fn reset(&mut self) {
842 self.delay_line.fill(0.0);
843 self.write_pos = 0;
844 }
845}
846
847#[derive(Debug, Clone)]
853pub struct AllpassDelay {
854 pub delay_samples: usize,
855 pub feedback: f32,
856 delay_line: Vec<f32>,
857 write_pos: usize,
858}
859
860impl AllpassDelay {
861 pub fn new(delay_samples: usize, feedback: f32) -> Self {
862 Self {
863 delay_samples,
864 feedback,
865 delay_line: vec![0.0; delay_samples + 1],
866 write_pos: 0,
867 }
868 }
869
870 pub fn process_sample(&mut self, x: f32) -> f32 {
872 let n = self.delay_line.len();
873 let read_pos = (self.write_pos + n - self.delay_samples) % n;
874 let buf = self.delay_line[read_pos];
875 let out = -self.feedback * x + buf;
876 self.delay_line[self.write_pos] = x + self.feedback * buf;
877 self.write_pos = (self.write_pos + 1) % n;
878 out
879 }
880
881 pub fn process(&mut self, buffer: &mut [f32]) {
883 for s in buffer.iter_mut() { *s = self.process_sample(*s); }
884 }
885
886 pub fn reset(&mut self) {
888 self.delay_line.fill(0.0);
889 self.write_pos = 0;
890 }
891}
892
893#[derive(Debug, Clone)]
899pub struct MovingAverage {
900 pub window_size: usize,
901 buffer: Vec<f32>,
902 write_pos: usize,
903 sum: f32,
904 count: usize,
905}
906
907impl MovingAverage {
908 pub fn new(window_size: usize) -> Self {
909 assert!(window_size > 0);
910 Self {
911 window_size,
912 buffer: vec![0.0; window_size],
913 write_pos: 0,
914 sum: 0.0,
915 count: 0,
916 }
917 }
918
919 pub fn process(&mut self, x: f32) -> f32 {
921 self.sum -= self.buffer[self.write_pos];
922 self.buffer[self.write_pos] = x;
923 self.sum += x;
924 self.write_pos = (self.write_pos + 1) % self.window_size;
925 if self.count < self.window_size { self.count += 1; }
926 self.sum / self.count as f32
927 }
928
929 pub fn process_buffer(&mut self, input: &[f32]) -> Vec<f32> {
931 input.iter().map(|&x| self.process(x)).collect()
932 }
933
934 pub fn value(&self) -> f32 {
936 if self.count == 0 { 0.0 } else { self.sum / self.count as f32 }
937 }
938
939 pub fn reset(&mut self) {
941 self.buffer.fill(0.0);
942 self.write_pos = 0;
943 self.sum = 0.0;
944 self.count = 0;
945 }
946}
947
948#[derive(Debug, Clone)]
958pub struct KalmanFilter1D {
959 pub x: f32,
961 pub p: f32,
963 pub q: f32,
965 pub r: f32,
967}
968
969impl KalmanFilter1D {
970 pub fn new(initial_estimate: f32, q: f32, r: f32) -> Self {
975 Self { x: initial_estimate, p: 1.0, q, r }
976 }
977
978 pub fn predict(&mut self, _dt: f32) {
980 self.p += self.q;
982 }
983
984 pub fn update(&mut self, measurement: f32) {
986 let k = self.p / (self.p + self.r);
988 self.x += k * (measurement - self.x);
990 self.p *= 1.0 - k;
992 }
993
994 pub fn filter(&mut self, measurement: f32, dt: f32) -> f32 {
996 self.predict(dt);
997 self.update(measurement);
998 self.x
999 }
1000
1001 pub fn estimate(&self) -> f32 { self.x }
1003
1004 pub fn filter_buffer(&mut self, measurements: &[f32], dt: f32) -> Vec<f32> {
1006 measurements.iter().map(|&m| self.filter(m, dt)).collect()
1007 }
1008
1009 pub fn reset(&mut self, initial: f32) {
1011 self.x = initial;
1012 self.p = 1.0;
1013 }
1014}
1015
1016#[derive(Debug, Clone)]
1024pub struct PllFilter {
1025 pub natural_freq_hz: f32,
1027 pub damping: f32,
1029 sample_rate: f32,
1030 phase: f32,
1032 freq: f32,
1034 integrator: f32,
1036 kp: f32,
1038 ki: f32,
1039}
1040
1041impl PllFilter {
1042 pub fn new(center_freq_hz: f32, natural_freq_hz: f32, damping: f32, sample_rate: f32) -> Self {
1047 let wn = 2.0 * PI * natural_freq_hz / sample_rate;
1048 let kp = 2.0 * damping * wn;
1049 let ki = wn * wn;
1050 Self {
1051 natural_freq_hz,
1052 damping,
1053 sample_rate,
1054 phase: 0.0,
1055 freq: 2.0 * PI * center_freq_hz / sample_rate,
1056 integrator: 2.0 * PI * center_freq_hz / sample_rate,
1057 kp,
1058 ki,
1059 }
1060 }
1061
1062 pub fn process_sample(&mut self, input: f32) -> f32 {
1065 let vco_i = self.phase.cos();
1067 let vco_q = self.phase.sin();
1068 let _phase_error_unused = input * vco_q - 0.0 * vco_i; let phase_error = input * (-self.phase).sin(); self.integrator += self.ki * phase_error;
1072 self.freq = self.integrator + self.kp * phase_error;
1073 self.phase += self.freq;
1075 self.phase = Self::wrap_phase(self.phase);
1076 self.phase.cos()
1077 }
1078
1079 pub fn process_buffer(&mut self, input: &[f32]) -> Vec<f32> {
1081 input.iter().map(|&x| self.process_sample(x)).collect()
1082 }
1083
1084 pub fn frequency_hz(&self) -> f32 {
1086 self.freq * self.sample_rate / (2.0 * PI)
1087 }
1088
1089 pub fn phase(&self) -> f32 { self.phase }
1091
1092 pub fn reset(&mut self) {
1094 self.phase = 0.0;
1095 self.integrator = self.freq;
1096 }
1097
1098 fn wrap_phase(p: f32) -> f32 {
1100 let mut p = p;
1101 while p > PI { p -= 2.0 * PI; }
1102 while p < -PI { p += 2.0 * PI; }
1103 p
1104 }
1105}
1106
1107#[cfg(test)]
1112mod tests {
1113 use super::*;
1114 use crate::dsp::SignalGenerator;
1115
1116 fn sine_buf(freq_hz: f32, sr: f32, len: usize) -> Vec<f32> {
1117 (0..len).map(|i| (2.0 * PI * freq_hz * i as f32 / sr).sin()).collect()
1118 }
1119
1120 fn rms(buf: &[f32]) -> f32 {
1121 let sum: f32 = buf.iter().map(|&x| x * x).sum();
1122 (sum / buf.len() as f32).sqrt()
1123 }
1124
1125 #[test]
1128 fn test_biquad_identity() {
1129 let mut bq = Biquad::identity();
1130 let input = vec![1.0, 0.5, -0.3, 0.8];
1131 let mut buf = input.clone();
1132 bq.process(&mut buf);
1133 for (&a, &b) in input.iter().zip(buf.iter()) {
1134 assert!((a - b).abs() < 1e-6);
1135 }
1136 }
1137
1138 #[test]
1139 fn test_biquad_lowpass_attenuates_high_freq() {
1140 let sr = 44100.0;
1141 let mut lp = BiquadDesign::lowpass(500.0, 0.707, sr);
1142 let hi_freq = sine_buf(10000.0, sr, 4410);
1143 let mut buf = hi_freq.clone();
1144 lp.process(&mut buf);
1145 assert!(rms(&buf) < rms(&hi_freq) * 0.5);
1147 }
1148
1149 #[test]
1150 fn test_biquad_highpass_passes_high_freq() {
1151 let sr = 44100.0;
1152 let mut hp = BiquadDesign::highpass(1000.0, 0.707, sr);
1153 let hi_buf = sine_buf(10000.0, sr, 4410);
1154 let mut buf = hi_buf.clone();
1155 hp.process(&mut buf);
1156 assert!(rms(&buf) > rms(&hi_buf) * 0.5);
1158 }
1159
1160 #[test]
1161 fn test_biquad_reset() {
1162 let sr = 44100.0;
1163 let mut lp = BiquadDesign::lowpass(1000.0, 0.707, sr);
1164 let mut buf = vec![1.0f32; 100];
1165 lp.process(&mut buf);
1166 lp.reset();
1167 assert_eq!(lp.z1, 0.0);
1168 assert_eq!(lp.z2, 0.0);
1169 }
1170
1171 #[test]
1172 fn test_biquad_notch_attenuates_center() {
1173 let sr = 44100.0;
1174 let center = 1000.0f32;
1175 let mut notch = BiquadDesign::notch(center, 10.0, sr);
1176 let buf_in = sine_buf(center, sr, 44100);
1177 let mut buf = buf_in.clone();
1178 for _ in 0..1000 { notch.process_sample(0.0); }
1180 notch.reset();
1181 notch.process(&mut buf);
1182 assert!(rms(&buf) < rms(&buf_in) * 0.3);
1184 }
1185
1186 #[test]
1187 fn test_filter_chain() {
1188 let sr = 44100.0;
1189 let mut chain = FilterChain::new();
1190 chain.push(BiquadDesign::lowpass(1000.0, 0.707, sr));
1191 chain.push(BiquadDesign::lowpass(1000.0, 0.707, sr));
1192 let buf_in = sine_buf(10000.0, sr, 4410);
1193 let mut buf = buf_in.clone();
1194 chain.process(&mut buf);
1195 let mut single = BiquadDesign::lowpass(1000.0, 0.707, sr);
1197 let mut buf2 = buf_in.clone();
1198 single.process(&mut buf2);
1199 assert!(rms(&buf) < rms(&buf2));
1200 }
1201
1202 #[test]
1203 fn test_butterworth_lowpass() {
1204 let sr = 44100.0;
1205 let mut filt = Butterworth::lowpass(4, 1000.0, sr);
1206 let buf_in = sine_buf(10000.0, sr, 4410);
1207 let mut buf = buf_in.clone();
1208 filt.process(&mut buf);
1209 assert!(rms(&buf) < rms(&buf_in) * 0.1);
1210 }
1211
1212 #[test]
1213 fn test_fir_lowpass_dc_gain() {
1214 let fir = FirDesign::lowpass_windowed(0.25, 63, WindowFunction::Hamming);
1216 let dc: Vec<f32> = vec![1.0; 512];
1217 let mut buf = dc.clone();
1218 let mut f = fir;
1219 f.process(&mut buf);
1220 let steady = &buf[200..];
1222 let avg: f32 = steady.iter().sum::<f32>() / steady.len() as f32;
1223 assert!((avg - 1.0).abs() < 0.01, "avg={}", avg);
1224 }
1225
1226 #[test]
1227 fn test_fir_highpass_attenuates_dc() {
1228 let fir = FirDesign::highpass_windowed(0.25, 63, WindowFunction::Hann);
1229 let dc = vec![1.0f32; 512];
1230 let mut buf = dc.clone();
1231 let mut f = fir;
1232 f.process(&mut buf);
1233 let avg: f32 = buf[200..].iter().sum::<f32>() / buf[200..].len() as f32;
1234 assert!(avg.abs() < 0.05, "avg={}", avg);
1235 }
1236
1237 #[test]
1238 fn test_convolution_impulse() {
1239 let sig = vec![1.0f32, 2.0, 3.0, 4.0];
1241 let kernel = vec![1.0f32, 0.0, 0.0];
1242 let out = Convolution::convolve(&sig, &kernel);
1243 assert_eq!(out[0], 1.0);
1244 assert_eq!(out[1], 2.0);
1245 assert_eq!(out[2], 3.0);
1246 assert_eq!(out[3], 4.0);
1247 }
1248
1249 #[test]
1250 fn test_convolution_matches_direct() {
1251 let sig: Vec<f32> = (0..20).map(|i| i as f32 * 0.1).collect();
1252 let kernel: Vec<f32> = vec![0.25, 0.5, 0.25];
1253 let fft_result = Convolution::convolve(&sig, &kernel);
1254 let direct_result = Convolution::convolve_direct(&sig, &kernel);
1255 assert_eq!(fft_result.len(), direct_result.len());
1256 for (a, b) in fft_result.iter().zip(direct_result.iter()) {
1257 assert!((a - b).abs() < 1e-4, "a={}, b={}", a, b);
1258 }
1259 }
1260
1261 #[test]
1262 fn test_ola_convolver_block_processing() {
1263 let kernel = vec![0.25f32, 0.5, 0.25];
1264 let block_size = 64;
1265 let mut ola = OlaConvolver::new(&kernel, block_size);
1266 let input = vec![1.0f32; block_size];
1267 let out = ola.process_block(&input);
1268 assert_eq!(out.len(), block_size);
1269 }
1270
1271 #[test]
1272 fn test_svf_lowpass() {
1273 let sr = 44100.0;
1274 let mut svf = SvfFilter::new(1000.0, 0.0, SvfMode::LowPass, sr);
1275 let hi = sine_buf(10000.0, sr, 4410);
1276 let mut buf = hi.clone();
1277 svf.process(&mut buf);
1278 assert!(rms(&buf) < rms(&hi) * 0.3);
1279 }
1280
1281 #[test]
1282 fn test_svf_highpass() {
1283 let sr = 44100.0;
1284 let mut svf = SvfFilter::new(1000.0, 0.0, SvfMode::HighPass, sr);
1285 let lo = sine_buf(100.0, sr, 4410);
1286 let mut buf = lo.clone();
1287 svf.process(&mut buf);
1288 assert!(rms(&buf) < rms(&lo) * 0.5);
1290 }
1291
1292 #[test]
1293 fn test_comb_feedforward() {
1294 let mut comb = CombFilter::new(100, 0.5, CombMode::FeedForward);
1295 let impulse: Vec<f32> = {
1296 let mut v = vec![0.0f32; 200];
1297 v[0] = 1.0;
1298 v
1299 };
1300 let mut buf = impulse.clone();
1301 comb.process(&mut buf);
1302 assert!((buf[100] - 0.5).abs() < 1e-5);
1304 }
1305
1306 #[test]
1307 fn test_allpass_delay_unity_magnitude() {
1308 let mut ap = AllpassDelay::new(50, 0.5);
1309 let impulse: Vec<f32> = {
1310 let mut v = vec![0.0f32; 200];
1311 v[0] = 1.0;
1312 v
1313 };
1314 let mut buf = impulse.clone();
1315 ap.process(&mut buf);
1316 let energy_in: f32 = impulse.iter().map(|&x| x * x).sum();
1318 let energy_out: f32 = buf.iter().map(|&x| x * x).sum();
1319 assert!((energy_in - energy_out).abs() < 0.01);
1320 }
1321
1322 #[test]
1323 fn test_moving_average_settling() {
1324 let mut ma = MovingAverage::new(8);
1325 for _ in 0..100 { ma.process(1.0); }
1326 assert!((ma.value() - 1.0).abs() < 1e-5);
1327 }
1328
1329 #[test]
1330 fn test_moving_average_step() {
1331 let mut ma = MovingAverage::new(4);
1332 for _ in 0..4 { ma.process(0.0); }
1334 for i in 0..4 {
1335 let v = ma.process(1.0);
1336 assert!(v <= 1.0 && v >= 0.0, "i={} v={}", i, v);
1337 }
1338 assert!((ma.value() - 1.0).abs() < 1e-5);
1339 }
1340
1341 #[test]
1342 fn test_kalman_smoothing() {
1343 let mut kf = KalmanFilter1D::new(0.0, 0.001, 1.0);
1345 let dt = 1.0 / 44100.0;
1346 for _ in 0..1000 {
1347 kf.filter(1.0, dt);
1348 }
1349 assert!((kf.estimate() - 1.0).abs() < 0.05, "est={}", kf.estimate());
1350 }
1351
1352 #[test]
1353 fn test_pll_frequency_lock() {
1354 let sr = 44100.0;
1355 let target_hz = 440.0;
1356 let mut pll = PllFilter::new(target_hz, 5.0, 0.707, sr);
1357 let sig = sine_buf(target_hz, sr, 44100);
1358 for &s in &sig[..22050] { pll.process_sample(s); }
1360 let est_freq = pll.frequency_hz();
1361 assert!(est_freq > 200.0 && est_freq < 1000.0, "est_freq={}", est_freq);
1363 }
1364
1365 #[test]
1366 fn test_biquad_peak_eq() {
1367 let sr = 44100.0;
1368 let mut peak = BiquadDesign::peak_eq(1000.0, 6.0, 1.0, sr);
1369 let buf_in = sine_buf(1000.0, sr, 4410);
1370 let mut buf = buf_in.clone();
1371 peak.process(&mut buf);
1372 assert!(rms(&buf) > rms(&buf_in) * 1.3);
1374 }
1375
1376 #[test]
1377 fn test_chebyshev1_lowpass() {
1378 let sr = 44100.0;
1379 let mut ch = Chebyshev1::lowpass(4, 1000.0, 3.0, sr);
1380 let hi = sine_buf(10000.0, sr, 4410);
1381 let mut buf = hi.clone();
1382 ch.process(&mut buf);
1383 assert!(rms(&buf) < rms(&hi) * 0.1);
1384 }
1385
1386 #[test]
1387 fn test_bessel_lowpass_dc_gain() {
1388 let sr = 44100.0;
1389 let mut bessel = Bessel::lowpass(4, 2000.0, sr);
1390 let dc = vec![1.0f32; 4410];
1391 let mut buf = dc.clone();
1392 bessel.process(&mut buf);
1393 let avg: f32 = buf[1000..].iter().sum::<f32>() / buf[1000..].len() as f32;
1394 assert!((avg - 1.0).abs() < 0.1, "avg={}", avg);
1395 }
1396
1397 #[test]
1398 fn test_fir_bandpass() {
1399 let sr = 44100.0;
1400 let fir = FirDesign::bandpass_windowed(0.1, 0.3, 127, WindowFunction::Blackman);
1401 let lo = sine_buf(100.0, sr, 4410);
1402 let hi = sine_buf(10000.0, sr, 4410);
1403 let mid = sine_buf(3000.0, sr, 4410);
1404 let process = |f: &FirFilter, buf: &[f32]| -> f32 {
1405 let mut b = buf.to_vec();
1406 let mut ff = f.clone();
1407 ff.process(&mut b);
1408 rms(&b)
1409 };
1410 assert!(process(&fir, &mid) > process(&fir, &lo));
1411 assert!(process(&fir, &mid) > process(&fir, &hi));
1412 }
1413
1414 #[test]
1415 fn test_hilbert_fir_length() {
1416 let h = FirDesign::hilbert(63);
1417 assert_eq!(h.num_taps(), 63);
1418 }
1419}