embedded_dsp/
filter_analysis.rs1#[allow(unused_imports)]
9use crate::math::FloatMath;
10use crate::types::Complex;
11
12pub fn fir_frequency_response(taps: &[f32], freq_norm: f32) -> Complex<f32> {
18 let omega = 2.0 * core::f32::consts::PI * freq_norm;
19 let mut real = 0.0f32;
20 let mut imag = 0.0f32;
21 for (k, &tap) in taps.iter().enumerate() {
22 let angle = omega * k as f32;
23 real += tap * angle.cos();
24 imag -= tap * angle.sin();
25 }
26 Complex::new(real, imag)
27}
28
29pub fn biquad_frequency_response(coeffs: &[f32; 5], freq_norm: f32) -> Complex<f32> {
35 let omega = 2.0 * core::f32::consts::PI * freq_norm;
36 let cos1 = omega.cos();
37 let sin1 = omega.sin();
38 let cos2 = (2.0 * omega).cos();
39 let sin2 = (2.0 * omega).sin();
40
41 let num = Complex::new(
42 coeffs[0] + coeffs[1] * cos1 + coeffs[2] * cos2,
43 -coeffs[1] * sin1 - coeffs[2] * sin2,
44 );
45 let den = Complex::new(
47 1.0 - coeffs[3] * cos1 - coeffs[4] * cos2,
48 coeffs[3] * sin1 + coeffs[4] * sin2,
49 );
50 complex_divide(num, den)
51}
52
53pub fn biquad_cascade_frequency_response(coeffs: &[f32], freq_norm: f32) -> Complex<f32> {
58 let mut total = Complex::new(1.0f32, 0.0f32);
59 for stage in coeffs.chunks_exact(5) {
60 let section: [f32; 5] = [stage[0], stage[1], stage[2], stage[3], stage[4]];
61 total = complex_multiply(total, biquad_frequency_response(§ion, freq_norm));
62 }
63 total
64}
65
66pub fn response_magnitude(h: Complex<f32>) -> f32 {
68 (h.real * h.real + h.imag * h.imag).sqrt()
69}
70
71pub fn response_magnitude_db(h: Complex<f32>) -> f32 {
73 20.0 * response_magnitude(h).max(1e-20).log10()
74}
75
76pub fn response_phase(h: Complex<f32>) -> f32 {
79 h.imag.atan2(h.real)
80}
81
82fn complex_multiply(a: Complex<f32>, b: Complex<f32>) -> Complex<f32> {
83 Complex::new(
84 a.real * b.real - a.imag * b.imag,
85 a.real * b.imag + a.imag * b.real,
86 )
87}
88
89fn complex_divide(a: Complex<f32>, b: Complex<f32>) -> Complex<f32> {
90 let denom = b.real * b.real + b.imag * b.imag;
91 if denom < 1e-20 {
92 return Complex::new(0.0, 0.0);
93 }
94 let inv_denom = 1.0 / denom;
95 Complex::new(
96 (a.real * b.real + a.imag * b.imag) * inv_denom,
97 (a.imag * b.real - a.real * b.imag) * inv_denom,
98 )
99}
100
101pub fn fir_group_delay(taps: &[f32], freq_norm: f32) -> f32 {
108 let omega = 2.0 * core::f32::consts::PI * freq_norm;
109 let mut h_re = 0.0f32;
110 let mut h_im = 0.0f32;
111 let mut b_re = 0.0f32;
112 let mut b_im = 0.0f32;
113 for (k, &tap) in taps.iter().enumerate() {
114 let n = k as f32;
115 let angle = omega * n;
116 let c = angle.cos();
117 let s = angle.sin();
118 h_re += tap * c;
119 h_im -= tap * s;
120 b_re += n * tap * c;
121 b_im -= n * tap * s;
122 }
123 let denom = h_re * h_re + h_im * h_im;
124 if denom < 1e-20 {
125 return 0.0;
126 }
127 (b_re * h_re + b_im * h_im) / denom
128}
129
130pub fn biquad_pole_radius(coeffs: &[f32; 5]) -> f32 {
137 let a1 = coeffs[3];
138 let a2 = coeffs[4];
139 let discriminant = a1 * a1 + 4.0 * a2;
140 if discriminant >= 0.0 {
141 let sqrt_d = discriminant.sqrt();
142 let p1 = (a1 + sqrt_d) / 2.0;
143 let p2 = (a1 - sqrt_d) / 2.0;
144 p1.abs().max(p2.abs())
145 } else {
146 (-a2).sqrt()
148 }
149}
150
151pub fn biquad_is_stable(coeffs: &[f32; 5]) -> bool {
154 biquad_pole_radius(coeffs) < 1.0
155}
156
157pub fn biquad_cascade_is_stable(coeffs: &[f32]) -> bool {
160 coeffs
161 .chunks_exact(5)
162 .all(|stage| biquad_is_stable(&[stage[0], stage[1], stage[2], stage[3], stage[4]]))
163}