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}
164
165use crate::types::q15;
170
171pub fn biquad_peak_gain(coeffs: &[f32; 5], num_points: usize) -> f32 {
173 let pts = num_points.max(16);
174 let mut max_mag = 0.0f32;
175 for i in 0..=pts {
176 let f = (i as f32) * 0.5 / (pts as f32);
177 let resp = biquad_frequency_response(coeffs, f);
178 let mag = (resp.real * resp.real + resp.imag * resp.imag).sqrt();
179 if mag > max_mag {
180 max_mag = mag;
181 }
182 }
183 max_mag
184}
185
186pub fn biquad_l2_norm(coeffs: &[f32; 5], num_points: usize) -> f32 {
188 let pts = num_points.max(16);
189 let mut sum_sq = 0.0f32;
190 for i in 0..=pts {
191 let f = (i as f32) * 0.5 / (pts as f32);
192 let resp = biquad_frequency_response(coeffs, f);
193 sum_sq += resp.real * resp.real + resp.imag * resp.imag;
194 }
195 (sum_sq / (pts as f32 + 1.0)).sqrt()
196}
197
198pub fn estimate_biquad_headroom_bits(coeffs: &[f32; 5]) -> (u8, f32) {
203 let peak = biquad_peak_gain(coeffs, 64);
204 if peak <= 1.0 {
205 (0, peak)
206 } else {
207 let mut bits = 0u8;
209 let mut threshold = 1.0f32;
210 while threshold < peak && bits < 14 {
211 bits += 1;
212 threshold *= 2.0;
213 }
214 (bits, peak)
215 }
216}
217
218pub fn biquad_q15_frequency_response(
220 coeffs_q15: &[q15; 5],
221 post_shift: u8,
222 freq_norm: f32,
223) -> Complex<f32> {
224 let scale = (1u32 << post_shift.min(14)) as f32;
225 let b0 = coeffs_q15[0].to_num::<f32>() * scale;
226 let b1 = coeffs_q15[1].to_num::<f32>() * scale;
227 let b2 = coeffs_q15[2].to_num::<f32>() * scale;
228 let a1 = coeffs_q15[3].to_num::<f32>() * scale;
229 let a2 = coeffs_q15[4].to_num::<f32>() * scale;
230
231 let float_coeffs = [b0, b1, b2, a1, a2];
232 biquad_frequency_response(&float_coeffs, freq_norm)
233}
234
235pub fn biquad_quantization_snr_db(
238 sos_f32: &[f32],
239 sos_q15: &[q15],
240 post_shift: u8,
241 num_points: usize,
242) -> f32 {
243 if sos_f32.len() != sos_q15.len() || sos_f32.is_empty() || sos_f32.len() % 5 != 0 {
244 return 0.0;
245 }
246
247 let num_stages = sos_f32.len() / 5;
248 let pts = num_points.max(32);
249 let mut sig_pow = 0.0f32;
250 let mut err_pow = 0.0f32;
251
252 for i in 0..=pts {
253 let f = (i as f32) * 0.5 / (pts as f32);
254
255 let mut h_ideal = Complex::new(1.0f32, 0.0f32);
257 for stage in 0..num_stages {
258 let idx = stage * 5;
259 let section = [
260 sos_f32[idx],
261 sos_f32[idx + 1],
262 sos_f32[idx + 2],
263 sos_f32[idx + 3],
264 sos_f32[idx + 4],
265 ];
266 h_ideal = h_ideal * biquad_frequency_response(§ion, f);
267 }
268
269 let mut h_quant = Complex::new(1.0f32, 0.0f32);
271 for stage in 0..num_stages {
272 let idx = stage * 5;
273 let section = [
274 sos_q15[idx],
275 sos_q15[idx + 1],
276 sos_q15[idx + 2],
277 sos_q15[idx + 3],
278 sos_q15[idx + 4],
279 ];
280 h_quant = h_quant * biquad_q15_frequency_response(§ion, post_shift, f);
281 }
282
283 let mag_sq = h_ideal.real * h_ideal.real + h_ideal.imag * h_ideal.imag;
284 let diff_re = h_ideal.real - h_quant.real;
285 let diff_im = h_ideal.imag - h_quant.imag;
286 let err_sq = diff_re * diff_re + diff_im * diff_im;
287
288 sig_pow += mag_sq;
289 err_pow += err_sq;
290 }
291
292 if err_pow < 1e-20 {
293 return 120.0; }
295 10.0 * (sig_pow / err_pow).log10()
296}
297
298pub fn fir_quantization_snr_db(taps_f32: &[f32], taps_q15: &[q15], num_points: usize) -> f32 {
300 if taps_f32.len() != taps_q15.len() || taps_f32.is_empty() {
301 return 0.0;
302 }
303
304 let pts = num_points.max(32);
305 let mut sig_pow = 0.0f32;
306 let mut err_pow = 0.0f32;
307
308 for i in 0..=pts {
309 let f = (i as f32) * 0.5 / (pts as f32);
310 let h_ideal = fir_frequency_response(taps_f32, f);
311
312 let omega = 2.0 * core::f32::consts::PI * f;
314 let mut q_re = 0.0f32;
315 let mut q_im = 0.0f32;
316 for (k, &tap) in taps_q15.iter().enumerate() {
317 let tap_f = tap.to_num::<f32>();
318 let angle = omega * k as f32;
319 q_re += tap_f * angle.cos();
320 q_im -= tap_f * angle.sin();
321 }
322
323 let mag_sq = h_ideal.real * h_ideal.real + h_ideal.imag * h_ideal.imag;
324 let diff_re = h_ideal.real - q_re;
325 let diff_im = h_ideal.imag - q_im;
326 let err_sq = diff_re * diff_re + diff_im * diff_im;
327
328 sig_pow += mag_sq;
329 err_pow += err_sq;
330 }
331
332 if err_pow < 1e-20 {
333 return 120.0;
334 }
335 10.0 * (sig_pow / err_pow).log10()
336}