1#[allow(unused_imports)]
4use crate::math::FloatMath;
5
6pub fn biquad_lowpass_coeffs(cutoff_freq: f32, sample_rate: f32, q: f32) -> [f32; 5] {
12 let w0 = 2.0 * core::f32::consts::PI * cutoff_freq / sample_rate;
13 let cos_w0 = w0.cos();
14 let sin_w0 = w0.sin();
15 let alpha = sin_w0 / (2.0 * q);
16
17 let a0 = 1.0 + alpha;
18 let b0 = (1.0 - cos_w0) / 2.0 / a0;
19 let b1 = (1.0 - cos_w0) / a0;
20 let b2 = (1.0 - cos_w0) / 2.0 / a0;
21 let a1 = (2.0 * cos_w0) / a0;
23 let a2 = -(1.0 - alpha) / a0;
24
25 [b0, b1, b2, a1, a2]
26}
27
28pub fn biquad_highpass_coeffs(cutoff_freq: f32, sample_rate: f32, q: f32) -> [f32; 5] {
30 let w0 = 2.0 * core::f32::consts::PI * cutoff_freq / sample_rate;
31 let cos_w0 = w0.cos();
32 let sin_w0 = w0.sin();
33 let alpha = sin_w0 / (2.0 * q);
34
35 let a0 = 1.0 + alpha;
36 let b0 = (1.0 + cos_w0) / 2.0 / a0;
37 let b1 = -(1.0 + cos_w0) / a0;
38 let b2 = (1.0 + cos_w0) / 2.0 / a0;
39 let a1 = (2.0 * cos_w0) / a0;
40 let a2 = -(1.0 - alpha) / a0;
41
42 [b0, b1, b2, a1, a2]
43}
44
45pub fn biquad_bandpass_coeffs(center_freq: f32, sample_rate: f32, q: f32) -> [f32; 5] {
47 let w0 = 2.0 * core::f32::consts::PI * center_freq / sample_rate;
48 let cos_w0 = w0.cos();
49 let sin_w0 = w0.sin();
50 let alpha = sin_w0 / (2.0 * q);
51
52 let a0 = 1.0 + alpha;
53 let b0 = alpha / a0;
54 let b1 = 0.0;
55 let b2 = -alpha / a0;
56 let a1 = (2.0 * cos_w0) / a0;
57 let a2 = -(1.0 - alpha) / a0;
58
59 [b0, b1, b2, a1, a2]
60}
61
62pub fn biquad_notch_coeffs(center_freq: f32, sample_rate: f32, q: f32) -> [f32; 5] {
64 let w0 = 2.0 * core::f32::consts::PI * center_freq / sample_rate;
65 let cos_w0 = w0.cos();
66 let sin_w0 = w0.sin();
67 let alpha = sin_w0 / (2.0 * q);
68
69 let a0 = 1.0 + alpha;
70 let b0 = 1.0 / a0;
71 let b1 = (-2.0 * cos_w0) / a0;
72 let b2 = 1.0 / a0;
73 let a1 = (2.0 * cos_w0) / a0;
74 let a2 = -(1.0 - alpha) / a0;
75
76 [b0, b1, b2, a1, a2]
77}
78
79pub fn biquad_peaking_coeffs(center_freq: f32, sample_rate: f32, q: f32, gain_db: f32) -> [f32; 5] {
81 let w0 = 2.0 * core::f32::consts::PI * center_freq / sample_rate;
82 let cos_w0 = w0.cos();
83 let sin_w0 = w0.sin();
84 let a = (10.0f32).powf(gain_db / 40.0);
85 let alpha = sin_w0 / (2.0 * q);
86
87 let a0 = 1.0 + alpha / a;
88 let b0 = (1.0 + alpha * a) / a0;
89 let b1 = (-2.0 * cos_w0) / a0;
90 let b2 = (1.0 - alpha * a) / a0;
91 let a1 = (2.0 * cos_w0) / a0;
92 let a2 = -(1.0 - alpha / a) / a0;
93
94 [b0, b1, b2, a1, a2]
95}
96
97pub fn biquad_allpass_coeffs(center_freq: f32, sample_rate: f32, q: f32) -> [f32; 5] {
99 let w0 = 2.0 * core::f32::consts::PI * center_freq / sample_rate;
100 let cos_w0 = w0.cos();
101 let sin_w0 = w0.sin();
102 let alpha = sin_w0 / (2.0 * q);
103
104 let a0 = 1.0 + alpha;
105 let b0 = (1.0 - alpha) / a0;
106 let b1 = (-2.0 * cos_w0) / a0;
107 let b2 = (1.0 + alpha) / a0;
108 let a1 = (2.0 * cos_w0) / a0;
109 let a2 = -(1.0 - alpha) / a0;
110
111 [b0, b1, b2, a1, a2]
112}
113
114pub fn butterworth_lowpass_biquads(
117 cutoff_freq: f32,
118 sample_rate: f32,
119 order: usize,
120 out_coeffs: &mut [f32],
121) {
122 let num_stages = order / 2;
123 assert!(
124 out_coeffs.len() >= num_stages * 5,
125 "out_coeffs buffer too small"
126 );
127
128 for k in 0..num_stages {
129 let angle = core::f32::consts::PI * (2 * k + 1) as f32 / (2 * order) as f32;
130 let q = 1.0 / (2.0 * angle.sin());
131 let coeffs = biquad_lowpass_coeffs(cutoff_freq, sample_rate, q);
132 out_coeffs[k * 5..(k + 1) * 5].copy_from_slice(&coeffs);
133 }
134}
135
136pub fn chebyshev_biquad_stage(
152 cutoff_norm: f32,
153 high_pass: bool,
154 ripple_percent: f32,
155 num_poles: u32,
156 pole_pair: u32,
157) -> [f32; 5] {
158 let pi = core::f32::consts::PI;
159 let np = num_poles as f32;
160 let p = pole_pair as f32;
161
162 let angle = pi / (2.0 * np) + (p - 1.0) * pi / np;
164 let mut rp = -angle.cos();
165 let mut ip = angle.sin();
166
167 if ripple_percent != 0.0 {
169 let es = ((100.0 / (100.0 - ripple_percent)).powf(2.0) - 1.0).sqrt();
170 let vx = (1.0 / np) * ((1.0 / es) + ((1.0 / (es * es)) + 1.0).sqrt()).ln();
171 let kx_raw = (1.0 / np) * ((1.0 / es) + ((1.0 / (es * es)) - 1.0).sqrt()).ln();
172 let kx = (kx_raw.exp() + (-kx_raw).exp()) / 2.0;
173 rp *= ((vx.exp() - (-vx).exp()) / 2.0) / kx;
174 ip *= ((vx.exp() + (-vx).exp()) / 2.0) / kx;
175 }
176
177 let t = 2.0 * (0.5f32).tan();
179 let w = 2.0 * pi * cutoff_norm;
180 let m = rp * rp + ip * ip;
181 let d = 4.0 - 4.0 * rp * t + m * t * t;
182 let x0 = t * t / d;
183 let x1 = 2.0 * t * t / d;
184 let x2 = t * t / d;
185 let y1 = (8.0 - 2.0 * m * t * t) / d;
186 let y2 = (-4.0 - 4.0 * rp * t - m * t * t) / d;
187
188 let k = if high_pass {
190 -(w / 2.0 + 0.5).cos() / (w / 2.0 - 0.5).cos()
191 } else {
192 (0.5 - w / 2.0).sin() / (0.5 + w / 2.0).sin()
193 };
194
195 let d2 = 1.0 + y1 * k - y2 * k * k;
196 let b0 = (x0 - x1 * k + x2 * k * k) / d2;
197 let mut b1 = (-2.0 * x0 * k + x1 + x1 * k * k - 2.0 * x2 * k) / d2;
198 let b2 = (x0 * k * k - x1 * k + x2) / d2;
199 let mut a1 = (2.0 * k + y1 + y1 * k * k - 2.0 * y2 * k) / d2;
200 let a2 = (-(k * k) - y1 * k + y2) / d2;
201
202 if high_pass {
203 b1 = -b1;
204 a1 = -a1;
205 }
206
207 [b0, b1, b2, a1, a2]
208}
209
210pub fn chebyshev_lowpass_biquads(
216 cutoff_norm: f32,
217 ripple_percent: f32,
218 num_poles: u32,
219 out_coeffs: &mut [f32],
220) {
221 chebyshev_biquads(cutoff_norm, false, ripple_percent, num_poles, out_coeffs);
222}
223
224pub fn chebyshev_highpass_biquads(
227 cutoff_norm: f32,
228 ripple_percent: f32,
229 num_poles: u32,
230 out_coeffs: &mut [f32],
231) {
232 chebyshev_biquads(cutoff_norm, true, ripple_percent, num_poles, out_coeffs);
233}
234
235fn chebyshev_biquads(
236 cutoff_norm: f32,
237 high_pass: bool,
238 ripple_percent: f32,
239 num_poles: u32,
240 out_coeffs: &mut [f32],
241) {
242 let num_stages = (num_poles / 2) as usize;
243 assert!(
244 out_coeffs.len() >= num_stages * 5,
245 "out_coeffs buffer too small"
246 );
247
248 let mut total_gain = 1.0f32;
252 for k in 0..num_stages {
253 let stage = chebyshev_biquad_stage(
254 cutoff_norm,
255 high_pass,
256 ripple_percent,
257 num_poles,
258 (k + 1) as u32,
259 );
260 let [b0, b1, b2, a1, a2] = stage;
261 total_gain *= if high_pass {
262 (b0 - b1 + b2) / (1.0 + a1 - a2)
263 } else {
264 (b0 + b1 + b2) / (1.0 - a1 - a2)
265 };
266 out_coeffs[k * 5..(k + 1) * 5].copy_from_slice(&stage);
267 }
268
269 if total_gain != 0.0 {
270 let inv_gain = 1.0 / total_gain;
271 out_coeffs[0] *= inv_gain;
272 out_coeffs[1] *= inv_gain;
273 out_coeffs[2] *= inv_gain;
274 }
275}
276
277pub fn single_pole_decay_from_cutoff(cutoff_norm: f32) -> f32 {
282 (-2.0 * core::f32::consts::PI * cutoff_norm).exp()
283}
284
285pub fn single_pole_decay_from_time_constant(time_constant_samples: f32) -> f32 {
288 (-1.0 / time_constant_samples).exp()
289}
290
291pub fn prewarp_cutoff_f32(fc: f32, fs: f32) -> f32 {
294 let pi_fc_over_fs = core::f32::consts::PI * fc / fs;
295 2.0 * fs * pi_fc_over_fs.tan()
296}
297
298pub fn bilinear_transform_biquad(
301 a0: f32,
302 a1: f32,
303 a2: f32,
304 b0: f32,
305 b1: f32,
306 b2: f32,
307 sample_rate: f32,
308) -> [f32; 5] {
309 let fs = sample_rate;
310 let fs2 = fs * fs;
311
312 let ad0 = 4.0 * a2 * fs2 + 2.0 * a1 * fs + a0;
313 let ad1 = 2.0 * a0 - 8.0 * a2 * fs2;
314 let ad2 = 4.0 * a2 * fs2 - 2.0 * a1 * fs + a0;
315
316 let bd0 = 4.0 * b2 * fs2 + 2.0 * b1 * fs + b0;
317 let bd1 = 2.0 * b0 - 8.0 * b2 * fs2;
318 let bd2 = 4.0 * b2 * fs2 - 2.0 * b1 * fs + b0;
319
320 let inv_bd0 = 1.0 / bd0;
321
322 let b_0 = ad0 * inv_bd0;
323 let b_1 = ad1 * inv_bd0;
324 let b_2 = ad2 * inv_bd0;
325 let a_1 = -bd1 * inv_bd0;
326 let a_2 = -bd2 * inv_bd0;
327 [b_0, b_1, b_2, a_1, a_2]
328}
329
330use crate::types::Status;
333
334pub fn fir_windowed_sinc_lowpass(fc_norm: f32, out_taps: &mut [f32]) -> Status {
339 let m = out_taps.len();
340 if m < 3 || m % 2 == 0 || fc_norm <= 0.0 || fc_norm >= 0.5 {
341 return Status::ArgumentError;
342 }
343
344 let half = (m - 1) as f32 / 2.0;
345 let two_pi_fc = 2.0 * core::f32::consts::PI * fc_norm;
346 let two_pi_over_m = 2.0 * core::f32::consts::PI / (m - 1) as f32;
347
348 let mut sum = 0.0f32;
349 for i in 0..m {
350 let d = (i as f32) - half;
351 let sinc = if d == 0.0 {
352 two_pi_fc
353 } else {
354 (two_pi_fc * d).sin() / d
355 };
356
357 let w = 0.42 - 0.5 * (two_pi_over_m * i as f32).cos()
359 + 0.08 * (2.0 * two_pi_over_m * i as f32).cos();
360 let tap = sinc * w;
361 out_taps[i] = tap;
362 sum += tap;
363 }
364
365 if sum != 0.0 {
367 let inv_sum = 1.0 / sum;
368 for i in 0..m {
369 out_taps[i] *= inv_sum;
370 }
371 }
372
373 Status::Success
374}
375
376pub fn fir_windowed_sinc_highpass(fc_norm: f32, out_taps: &mut [f32]) -> Status {
381 let status = fir_windowed_sinc_lowpass(fc_norm, out_taps);
382 if status != Status::Success {
383 return status;
384 }
385
386 let m = out_taps.len();
387 let center = (m - 1) / 2;
388
389 for i in 0..m {
391 out_taps[i] = -out_taps[i];
392 }
393 out_taps[center] += 1.0;
394
395 Status::Success
396}
397
398pub fn fir_windowed_sinc_bandpass(
400 f_low_norm: f32,
401 f_high_norm: f32,
402 out_taps: &mut [f32],
403) -> Status {
404 let m = out_taps.len();
405 if m < 3 || m % 2 == 0 || f_low_norm <= 0.0 || f_high_norm >= 0.5 || f_low_norm >= f_high_norm {
406 return Status::ArgumentError;
407 }
408
409 let half = (m - 1) as f32 / 2.0;
410 let two_pi_flow = 2.0 * core::f32::consts::PI * f_low_norm;
411 let two_pi_fhigh = 2.0 * core::f32::consts::PI * f_high_norm;
412 let two_pi_over_m = 2.0 * core::f32::consts::PI / (m - 1) as f32;
413
414 for i in 0..m {
415 let d = (i as f32) - half;
416 let sinc_low = if d == 0.0 {
417 two_pi_flow
418 } else {
419 (two_pi_flow * d).sin() / d
420 };
421 let sinc_high = if d == 0.0 {
422 two_pi_fhigh
423 } else {
424 (two_pi_fhigh * d).sin() / d
425 };
426 let w = 0.42 - 0.5 * (two_pi_over_m * i as f32).cos()
427 + 0.08 * (2.0 * two_pi_over_m * i as f32).cos();
428 out_taps[i] = (sinc_high - sinc_low) * w;
429 }
430
431 let f_center = (f_low_norm + f_high_norm) / 2.0;
433 let mut real_gain = 0.0f32;
434 let mut imag_gain = 0.0f32;
435 for i in 0..m {
436 let angle = 2.0 * core::f32::consts::PI * f_center * (i as f32);
437 real_gain += out_taps[i] * angle.cos();
438 imag_gain -= out_taps[i] * angle.sin();
439 }
440 let mag = (real_gain * real_gain + imag_gain * imag_gain).sqrt();
441 if mag > 1e-12 {
442 let inv_mag = 1.0 / mag;
443 for i in 0..m {
444 out_taps[i] *= inv_mag;
445 }
446 }
447
448 Status::Success
449}
450
451pub fn fir_windowed_sinc_bandstop(
453 f_low_norm: f32,
454 f_high_norm: f32,
455 out_taps: &mut [f32],
456) -> Status {
457 let m = out_taps.len();
458 if m < 3 || m % 2 == 0 || f_low_norm <= 0.0 || f_high_norm >= 0.5 || f_low_norm >= f_high_norm {
459 return Status::ArgumentError;
460 }
461
462 let status = fir_windowed_sinc_bandpass(f_low_norm, f_high_norm, out_taps);
463 if status != Status::Success {
464 return status;
465 }
466
467 let center = (m - 1) / 2;
468 for i in 0..m {
469 out_taps[i] = -out_taps[i];
470 }
471 out_taps[center] += 1.0;
472
473 Status::Success
474}
475
476#[cfg(feature = "transform")]
493pub fn fir_custom_frequency_sampling(
494 desired_real: &[f32],
495 desired_imag: &[f32],
496 fft_len: usize,
497 out_taps: &mut [f32],
498) -> Status {
499 let m = out_taps.len();
500 if m < 3 || m % 2 == 0 {
501 return Status::ArgumentError;
502 }
503 if fft_len < 2 || (fft_len & (fft_len - 1)) != 0 || fft_len > 512 || fft_len < m {
504 return Status::ArgumentError;
505 }
506 let half_spec = fft_len / 2 + 1;
507 if desired_real.len() < half_spec || desired_imag.len() < half_spec {
508 return Status::LengthError;
509 }
510
511 let mut c_data = [0.0f32; 1024];
512 for k in 0..half_spec {
513 c_data[2 * k] = desired_real[k];
514 c_data[2 * k + 1] = desired_imag[k];
515 }
516 for k in half_spec..fft_len {
519 let mirror = fft_len - k;
520 c_data[2 * k] = desired_real[mirror];
521 c_data[2 * k + 1] = -desired_imag[mirror];
522 }
523
524 crate::transform::cfft_f32(&mut c_data[..2 * fft_len], fft_len, 1, 1);
525
526 let half = m / 2;
529 let two_pi_over_m = 2.0 * core::f32::consts::PI / (m - 1) as f32;
530 for i in 0..m {
531 let src_idx = (i + fft_len - half) % fft_len;
532 let w = 0.54 - 0.46 * (two_pi_over_m * i as f32).cos();
533 out_taps[i] = c_data[2 * src_idx] * w;
534 }
535
536 Status::Success
537}
538
539use crate::types::{q15, q31};
544
545#[derive(Debug, Clone, Copy, PartialEq, Eq)]
547pub enum ScalingStrategy {
548 LInfNorm,
550 L2Norm,
552 Direct,
554}
555
556pub fn biquad_quantize_and_scale_q15(
561 sos_f32: &[f32],
562 out_q15: &mut [q15],
563 strategy: ScalingStrategy,
564) -> Result<u8, Status> {
565 if sos_f32.len() != out_q15.len() || sos_f32.is_empty() || sos_f32.len() % 5 != 0 {
566 return Err(Status::LengthError);
567 }
568
569 let num_stages = sos_f32.len() / 5;
570 let mut max_coeff_mag = 0.0f32;
571
572 let mut scaled_f32 = [0.0f32; 128];
573 if sos_f32.len() > scaled_f32.len() {
574 return Err(Status::ArgumentError);
575 }
576
577 for stage in 0..num_stages {
578 let idx = stage * 5;
579 let mut b0 = sos_f32[idx];
580 let mut b1 = sos_f32[idx + 1];
581 let mut b2 = sos_f32[idx + 2];
582 let a1 = sos_f32[idx + 3];
583 let a2 = sos_f32[idx + 4];
584
585 let scale_factor = match strategy {
586 ScalingStrategy::LInfNorm => {
587 let peak = crate::filter_analysis::biquad_peak_gain(&[b0, b1, b2, a1, a2], 64);
588 if peak > 1.0 { 1.0 / peak } else { 1.0 }
589 }
590 ScalingStrategy::L2Norm => {
591 let l2 = crate::filter_analysis::biquad_l2_norm(&[b0, b1, b2, a1, a2], 64);
592 if l2 > 1.0 { 1.0 / l2 } else { 1.0 }
593 }
594 ScalingStrategy::Direct => 1.0,
595 };
596
597 b0 *= scale_factor;
598 b1 *= scale_factor;
599 b2 *= scale_factor;
600
601 scaled_f32[idx] = b0;
602 scaled_f32[idx + 1] = b1;
603 scaled_f32[idx + 2] = b2;
604 scaled_f32[idx + 3] = a1;
605 scaled_f32[idx + 4] = a2;
606
607 for k in 0..5 {
608 let mag = scaled_f32[idx + k].abs();
609 if mag > max_coeff_mag {
610 max_coeff_mag = mag;
611 }
612 }
613 }
614
615 let mut post_shift = 0u8;
616 let mut limit = 0.9999f32;
617 while limit < max_coeff_mag && post_shift < 14 {
618 post_shift += 1;
619 limit *= 2.0;
620 }
621
622 let status = crate::support::biquad_coeffs_f32_to_q15(&scaled_f32[..sos_f32.len()], out_q15, post_shift);
623 if status != Status::Success {
624 return Err(status);
625 }
626
627 Ok(post_shift)
628}
629
630pub fn biquad_quantize_and_scale_q31(
634 sos_f32: &[f32],
635 out_q31: &mut [q31],
636 strategy: ScalingStrategy,
637) -> Result<u8, Status> {
638 if sos_f32.len() != out_q31.len() || sos_f32.is_empty() || sos_f32.len() % 5 != 0 {
639 return Err(Status::LengthError);
640 }
641
642 let num_stages = sos_f32.len() / 5;
643 let mut max_coeff_mag = 0.0f32;
644
645 let mut scaled_f32 = [0.0f32; 128];
646 if sos_f32.len() > scaled_f32.len() {
647 return Err(Status::ArgumentError);
648 }
649
650 for stage in 0..num_stages {
651 let idx = stage * 5;
652 let mut b0 = sos_f32[idx];
653 let mut b1 = sos_f32[idx + 1];
654 let mut b2 = sos_f32[idx + 2];
655 let a1 = sos_f32[idx + 3];
656 let a2 = sos_f32[idx + 4];
657
658 let scale_factor = match strategy {
659 ScalingStrategy::LInfNorm => {
660 let peak = crate::filter_analysis::biquad_peak_gain(&[b0, b1, b2, a1, a2], 64);
661 if peak > 1.0 { 1.0 / peak } else { 1.0 }
662 }
663 ScalingStrategy::L2Norm => {
664 let l2 = crate::filter_analysis::biquad_l2_norm(&[b0, b1, b2, a1, a2], 64);
665 if l2 > 1.0 { 1.0 / l2 } else { 1.0 }
666 }
667 ScalingStrategy::Direct => 1.0,
668 };
669
670 b0 *= scale_factor;
671 b1 *= scale_factor;
672 b2 *= scale_factor;
673
674 scaled_f32[idx] = b0;
675 scaled_f32[idx + 1] = b1;
676 scaled_f32[idx + 2] = b2;
677 scaled_f32[idx + 3] = a1;
678 scaled_f32[idx + 4] = a2;
679
680 for k in 0..5 {
681 let mag = scaled_f32[idx + k].abs();
682 if mag > max_coeff_mag {
683 max_coeff_mag = mag;
684 }
685 }
686 }
687
688 let mut post_shift = 0u8;
689 let mut limit = 0.9999f32;
690 while limit < max_coeff_mag && post_shift < 14 {
691 post_shift += 1;
692 limit *= 2.0;
693 }
694
695 let status = crate::support::biquad_coeffs_f32_to_q31(&scaled_f32[..sos_f32.len()], out_q31, post_shift);
696 if status != Status::Success {
697 return Err(status);
698 }
699
700 Ok(post_shift)
701}
702
703pub fn fir_quantize_q15(taps_f32: &[f32], out_q15: &mut [q15]) -> Result<(), Status> {
705 if taps_f32.len() != out_q15.len() || taps_f32.is_empty() {
706 return Err(Status::LengthError);
707 }
708 for i in 0..taps_f32.len() {
709 out_q15[i] = q15::saturating_from_num(taps_f32[i]);
710 }
711 Ok(())
712}