#[allow(unused_imports)]
use crate::math::FloatMath;
pub fn biquad_lowpass_coeffs(cutoff_freq: f32, sample_rate: f32, q: f32) -> [f32; 5] {
let w0 = 2.0 * core::f32::consts::PI * cutoff_freq / sample_rate;
let cos_w0 = w0.cos();
let sin_w0 = w0.sin();
let alpha = sin_w0 / (2.0 * q);
let a0 = 1.0 + alpha;
let b0 = (1.0 - cos_w0) / 2.0 / a0;
let b1 = (1.0 - cos_w0) / a0;
let b2 = (1.0 - cos_w0) / 2.0 / a0;
let a1 = (2.0 * cos_w0) / a0;
let a2 = -(1.0 - alpha) / a0;
[b0, b1, b2, a1, a2]
}
pub fn biquad_highpass_coeffs(cutoff_freq: f32, sample_rate: f32, q: f32) -> [f32; 5] {
let w0 = 2.0 * core::f32::consts::PI * cutoff_freq / sample_rate;
let cos_w0 = w0.cos();
let sin_w0 = w0.sin();
let alpha = sin_w0 / (2.0 * q);
let a0 = 1.0 + alpha;
let b0 = (1.0 + cos_w0) / 2.0 / a0;
let b1 = -(1.0 + cos_w0) / a0;
let b2 = (1.0 + cos_w0) / 2.0 / a0;
let a1 = (2.0 * cos_w0) / a0;
let a2 = -(1.0 - alpha) / a0;
[b0, b1, b2, a1, a2]
}
pub fn biquad_bandpass_coeffs(center_freq: f32, sample_rate: f32, q: f32) -> [f32; 5] {
let w0 = 2.0 * core::f32::consts::PI * center_freq / sample_rate;
let cos_w0 = w0.cos();
let sin_w0 = w0.sin();
let alpha = sin_w0 / (2.0 * q);
let a0 = 1.0 + alpha;
let b0 = alpha / a0;
let b1 = 0.0;
let b2 = -alpha / a0;
let a1 = (2.0 * cos_w0) / a0;
let a2 = -(1.0 - alpha) / a0;
[b0, b1, b2, a1, a2]
}
pub fn biquad_notch_coeffs(center_freq: f32, sample_rate: f32, q: f32) -> [f32; 5] {
let w0 = 2.0 * core::f32::consts::PI * center_freq / sample_rate;
let cos_w0 = w0.cos();
let sin_w0 = w0.sin();
let alpha = sin_w0 / (2.0 * q);
let a0 = 1.0 + alpha;
let b0 = 1.0 / a0;
let b1 = (-2.0 * cos_w0) / a0;
let b2 = 1.0 / a0;
let a1 = (2.0 * cos_w0) / a0;
let a2 = -(1.0 - alpha) / a0;
[b0, b1, b2, a1, a2]
}
pub fn biquad_peaking_coeffs(center_freq: f32, sample_rate: f32, q: f32, gain_db: f32) -> [f32; 5] {
let w0 = 2.0 * core::f32::consts::PI * center_freq / sample_rate;
let cos_w0 = w0.cos();
let sin_w0 = w0.sin();
let a = (10.0f32).powf(gain_db / 40.0);
let alpha = sin_w0 / (2.0 * q);
let a0 = 1.0 + alpha / a;
let b0 = (1.0 + alpha * a) / a0;
let b1 = (-2.0 * cos_w0) / a0;
let b2 = (1.0 - alpha * a) / a0;
let a1 = (2.0 * cos_w0) / a0;
let a2 = -(1.0 - alpha / a) / a0;
[b0, b1, b2, a1, a2]
}
pub fn biquad_allpass_coeffs(center_freq: f32, sample_rate: f32, q: f32) -> [f32; 5] {
let w0 = 2.0 * core::f32::consts::PI * center_freq / sample_rate;
let cos_w0 = w0.cos();
let sin_w0 = w0.sin();
let alpha = sin_w0 / (2.0 * q);
let a0 = 1.0 + alpha;
let b0 = (1.0 - alpha) / a0;
let b1 = (-2.0 * cos_w0) / a0;
let b2 = (1.0 + alpha) / a0;
let a1 = (2.0 * cos_w0) / a0;
let a2 = -(1.0 - alpha) / a0;
[b0, b1, b2, a1, a2]
}
pub fn butterworth_lowpass_biquads(
cutoff_freq: f32,
sample_rate: f32,
order: usize,
out_coeffs: &mut [f32],
) {
let num_stages = order / 2;
assert!(
out_coeffs.len() >= num_stages * 5,
"out_coeffs buffer too small"
);
for k in 0..num_stages {
let angle = core::f32::consts::PI * (2 * k + 1) as f32 / (2 * order) as f32;
let q = 1.0 / (2.0 * angle.sin());
let coeffs = biquad_lowpass_coeffs(cutoff_freq, sample_rate, q);
out_coeffs[k * 5..(k + 1) * 5].copy_from_slice(&coeffs);
}
}
pub fn chebyshev_biquad_stage(
cutoff_norm: f32,
high_pass: bool,
ripple_percent: f32,
num_poles: u32,
pole_pair: u32,
) -> [f32; 5] {
let pi = core::f32::consts::PI;
let np = num_poles as f32;
let p = pole_pair as f32;
let angle = pi / (2.0 * np) + (p - 1.0) * pi / np;
let mut rp = -angle.cos();
let mut ip = angle.sin();
if ripple_percent != 0.0 {
let es = ((100.0 / (100.0 - ripple_percent)).powf(2.0) - 1.0).sqrt();
let vx = (1.0 / np) * ((1.0 / es) + ((1.0 / (es * es)) + 1.0).sqrt()).ln();
let kx_raw = (1.0 / np) * ((1.0 / es) + ((1.0 / (es * es)) - 1.0).sqrt()).ln();
let kx = (kx_raw.exp() + (-kx_raw).exp()) / 2.0;
rp *= ((vx.exp() - (-vx).exp()) / 2.0) / kx;
ip *= ((vx.exp() + (-vx).exp()) / 2.0) / kx;
}
let t = 2.0 * (0.5f32).tan();
let w = 2.0 * pi * cutoff_norm;
let m = rp * rp + ip * ip;
let d = 4.0 - 4.0 * rp * t + m * t * t;
let x0 = t * t / d;
let x1 = 2.0 * t * t / d;
let x2 = t * t / d;
let y1 = (8.0 - 2.0 * m * t * t) / d;
let y2 = (-4.0 - 4.0 * rp * t - m * t * t) / d;
let k = if high_pass {
-(w / 2.0 + 0.5).cos() / (w / 2.0 - 0.5).cos()
} else {
(0.5 - w / 2.0).sin() / (0.5 + w / 2.0).sin()
};
let d2 = 1.0 + y1 * k - y2 * k * k;
let b0 = (x0 - x1 * k + x2 * k * k) / d2;
let mut b1 = (-2.0 * x0 * k + x1 + x1 * k * k - 2.0 * x2 * k) / d2;
let b2 = (x0 * k * k - x1 * k + x2) / d2;
let mut a1 = (2.0 * k + y1 + y1 * k * k - 2.0 * y2 * k) / d2;
let a2 = (-(k * k) - y1 * k + y2) / d2;
if high_pass {
b1 = -b1;
a1 = -a1;
}
[b0, b1, b2, a1, a2]
}
pub fn chebyshev_lowpass_biquads(
cutoff_norm: f32,
ripple_percent: f32,
num_poles: u32,
out_coeffs: &mut [f32],
) {
chebyshev_biquads(cutoff_norm, false, ripple_percent, num_poles, out_coeffs);
}
pub fn chebyshev_highpass_biquads(
cutoff_norm: f32,
ripple_percent: f32,
num_poles: u32,
out_coeffs: &mut [f32],
) {
chebyshev_biquads(cutoff_norm, true, ripple_percent, num_poles, out_coeffs);
}
fn chebyshev_biquads(
cutoff_norm: f32,
high_pass: bool,
ripple_percent: f32,
num_poles: u32,
out_coeffs: &mut [f32],
) {
let num_stages = (num_poles / 2) as usize;
assert!(
out_coeffs.len() >= num_stages * 5,
"out_coeffs buffer too small"
);
let mut total_gain = 1.0f32;
for k in 0..num_stages {
let stage = chebyshev_biquad_stage(
cutoff_norm,
high_pass,
ripple_percent,
num_poles,
(k + 1) as u32,
);
let [b0, b1, b2, a1, a2] = stage;
total_gain *= if high_pass {
(b0 - b1 + b2) / (1.0 + a1 - a2)
} else {
(b0 + b1 + b2) / (1.0 - a1 - a2)
};
out_coeffs[k * 5..(k + 1) * 5].copy_from_slice(&stage);
}
if total_gain != 0.0 {
let inv_gain = 1.0 / total_gain;
out_coeffs[0] *= inv_gain;
out_coeffs[1] *= inv_gain;
out_coeffs[2] *= inv_gain;
}
}
pub fn single_pole_decay_from_cutoff(cutoff_norm: f32) -> f32 {
(-2.0 * core::f32::consts::PI * cutoff_norm).exp()
}
pub fn single_pole_decay_from_time_constant(time_constant_samples: f32) -> f32 {
(-1.0 / time_constant_samples).exp()
}
pub fn prewarp_cutoff_f32(fc: f32, fs: f32) -> f32 {
let pi_fc_over_fs = core::f32::consts::PI * fc / fs;
2.0 * fs * pi_fc_over_fs.tan()
}
pub fn bilinear_transform_biquad(
a0: f32,
a1: f32,
a2: f32,
b0: f32,
b1: f32,
b2: f32,
sample_rate: f32,
) -> [f32; 5] {
let fs = sample_rate;
let fs2 = fs * fs;
let ad0 = 4.0 * a2 * fs2 + 2.0 * a1 * fs + a0;
let ad1 = 2.0 * a0 - 8.0 * a2 * fs2;
let ad2 = 4.0 * a2 * fs2 - 2.0 * a1 * fs + a0;
let bd0 = 4.0 * b2 * fs2 + 2.0 * b1 * fs + b0;
let bd1 = 2.0 * b0 - 8.0 * b2 * fs2;
let bd2 = 4.0 * b2 * fs2 - 2.0 * b1 * fs + b0;
let inv_bd0 = 1.0 / bd0;
let b_0 = ad0 * inv_bd0;
let b_1 = ad1 * inv_bd0;
let b_2 = ad2 * inv_bd0;
let a_1 = -bd1 * inv_bd0;
let a_2 = -bd2 * inv_bd0;
[b_0, b_1, b_2, a_1, a_2]
}
use crate::types::Status;
pub fn fir_windowed_sinc_lowpass(fc_norm: f32, out_taps: &mut [f32]) -> Status {
let m = out_taps.len();
if m < 3 || m % 2 == 0 || fc_norm <= 0.0 || fc_norm >= 0.5 {
return Status::ArgumentError;
}
let half = (m - 1) as f32 / 2.0;
let two_pi_fc = 2.0 * core::f32::consts::PI * fc_norm;
let two_pi_over_m = 2.0 * core::f32::consts::PI / (m - 1) as f32;
let mut sum = 0.0f32;
for i in 0..m {
let d = (i as f32) - half;
let sinc = if d == 0.0 {
two_pi_fc
} else {
(two_pi_fc * d).sin() / d
};
let w = 0.42 - 0.5 * (two_pi_over_m * i as f32).cos()
+ 0.08 * (2.0 * two_pi_over_m * i as f32).cos();
let tap = sinc * w;
out_taps[i] = tap;
sum += tap;
}
if sum != 0.0 {
let inv_sum = 1.0 / sum;
for i in 0..m {
out_taps[i] *= inv_sum;
}
}
Status::Success
}
pub fn fir_windowed_sinc_highpass(fc_norm: f32, out_taps: &mut [f32]) -> Status {
let status = fir_windowed_sinc_lowpass(fc_norm, out_taps);
if status != Status::Success {
return status;
}
let m = out_taps.len();
let center = (m - 1) / 2;
for i in 0..m {
out_taps[i] = -out_taps[i];
}
out_taps[center] += 1.0;
Status::Success
}
pub fn fir_windowed_sinc_bandpass(
f_low_norm: f32,
f_high_norm: f32,
out_taps: &mut [f32],
) -> Status {
let m = out_taps.len();
if m < 3 || m % 2 == 0 || f_low_norm <= 0.0 || f_high_norm >= 0.5 || f_low_norm >= f_high_norm {
return Status::ArgumentError;
}
let half = (m - 1) as f32 / 2.0;
let two_pi_flow = 2.0 * core::f32::consts::PI * f_low_norm;
let two_pi_fhigh = 2.0 * core::f32::consts::PI * f_high_norm;
let two_pi_over_m = 2.0 * core::f32::consts::PI / (m - 1) as f32;
for i in 0..m {
let d = (i as f32) - half;
let sinc_low = if d == 0.0 {
two_pi_flow
} else {
(two_pi_flow * d).sin() / d
};
let sinc_high = if d == 0.0 {
two_pi_fhigh
} else {
(two_pi_fhigh * d).sin() / d
};
let w = 0.42 - 0.5 * (two_pi_over_m * i as f32).cos()
+ 0.08 * (2.0 * two_pi_over_m * i as f32).cos();
out_taps[i] = (sinc_high - sinc_low) * w;
}
let f_center = (f_low_norm + f_high_norm) / 2.0;
let mut real_gain = 0.0f32;
let mut imag_gain = 0.0f32;
for i in 0..m {
let angle = 2.0 * core::f32::consts::PI * f_center * (i as f32);
real_gain += out_taps[i] * angle.cos();
imag_gain -= out_taps[i] * angle.sin();
}
let mag = (real_gain * real_gain + imag_gain * imag_gain).sqrt();
if mag > 1e-12 {
let inv_mag = 1.0 / mag;
for i in 0..m {
out_taps[i] *= inv_mag;
}
}
Status::Success
}
pub fn fir_windowed_sinc_bandstop(
f_low_norm: f32,
f_high_norm: f32,
out_taps: &mut [f32],
) -> Status {
let m = out_taps.len();
if m < 3 || m % 2 == 0 || f_low_norm <= 0.0 || f_high_norm >= 0.5 || f_low_norm >= f_high_norm {
return Status::ArgumentError;
}
let status = fir_windowed_sinc_bandpass(f_low_norm, f_high_norm, out_taps);
if status != Status::Success {
return status;
}
let center = (m - 1) / 2;
for i in 0..m {
out_taps[i] = -out_taps[i];
}
out_taps[center] += 1.0;
Status::Success
}
#[cfg(feature = "transform")]
pub fn fir_custom_frequency_sampling(
desired_real: &[f32],
desired_imag: &[f32],
fft_len: usize,
out_taps: &mut [f32],
) -> Status {
let m = out_taps.len();
if m < 3 || m % 2 == 0 {
return Status::ArgumentError;
}
if fft_len < 2 || (fft_len & (fft_len - 1)) != 0 || fft_len > 512 || fft_len < m {
return Status::ArgumentError;
}
let half_spec = fft_len / 2 + 1;
if desired_real.len() < half_spec || desired_imag.len() < half_spec {
return Status::LengthError;
}
let mut c_data = [0.0f32; 1024];
for k in 0..half_spec {
c_data[2 * k] = desired_real[k];
c_data[2 * k + 1] = desired_imag[k];
}
for k in half_spec..fft_len {
let mirror = fft_len - k;
c_data[2 * k] = desired_real[mirror];
c_data[2 * k + 1] = -desired_imag[mirror];
}
crate::transform::cfft_f32(&mut c_data[..2 * fft_len], fft_len, 1, 1);
let half = m / 2;
let two_pi_over_m = 2.0 * core::f32::consts::PI / (m - 1) as f32;
for i in 0..m {
let src_idx = (i + fft_len - half) % fft_len;
let w = 0.54 - 0.46 * (two_pi_over_m * i as f32).cos();
out_taps[i] = c_data[2 * src_idx] * w;
}
Status::Success
}
use crate::types::{q15, q31};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScalingStrategy {
LInfNorm,
L2Norm,
Direct,
}
pub fn biquad_quantize_and_scale_q15(
sos_f32: &[f32],
out_q15: &mut [q15],
strategy: ScalingStrategy,
) -> Result<u8, Status> {
if sos_f32.len() != out_q15.len() || sos_f32.is_empty() || sos_f32.len() % 5 != 0 {
return Err(Status::LengthError);
}
let num_stages = sos_f32.len() / 5;
let mut max_coeff_mag = 0.0f32;
let mut scaled_f32 = [0.0f32; 128];
if sos_f32.len() > scaled_f32.len() {
return Err(Status::ArgumentError);
}
for stage in 0..num_stages {
let idx = stage * 5;
let mut b0 = sos_f32[idx];
let mut b1 = sos_f32[idx + 1];
let mut b2 = sos_f32[idx + 2];
let a1 = sos_f32[idx + 3];
let a2 = sos_f32[idx + 4];
let scale_factor = match strategy {
ScalingStrategy::LInfNorm => {
let peak = crate::filter_analysis::biquad_peak_gain(&[b0, b1, b2, a1, a2], 64);
if peak > 1.0 { 1.0 / peak } else { 1.0 }
}
ScalingStrategy::L2Norm => {
let l2 = crate::filter_analysis::biquad_l2_norm(&[b0, b1, b2, a1, a2], 64);
if l2 > 1.0 { 1.0 / l2 } else { 1.0 }
}
ScalingStrategy::Direct => 1.0,
};
b0 *= scale_factor;
b1 *= scale_factor;
b2 *= scale_factor;
scaled_f32[idx] = b0;
scaled_f32[idx + 1] = b1;
scaled_f32[idx + 2] = b2;
scaled_f32[idx + 3] = a1;
scaled_f32[idx + 4] = a2;
for k in 0..5 {
let mag = scaled_f32[idx + k].abs();
if mag > max_coeff_mag {
max_coeff_mag = mag;
}
}
}
let mut post_shift = 0u8;
let mut limit = 0.9999f32;
while limit < max_coeff_mag && post_shift < 14 {
post_shift += 1;
limit *= 2.0;
}
let status = crate::support::biquad_coeffs_f32_to_q15(&scaled_f32[..sos_f32.len()], out_q15, post_shift);
if status != Status::Success {
return Err(status);
}
Ok(post_shift)
}
pub fn biquad_quantize_and_scale_q31(
sos_f32: &[f32],
out_q31: &mut [q31],
strategy: ScalingStrategy,
) -> Result<u8, Status> {
if sos_f32.len() != out_q31.len() || sos_f32.is_empty() || sos_f32.len() % 5 != 0 {
return Err(Status::LengthError);
}
let num_stages = sos_f32.len() / 5;
let mut max_coeff_mag = 0.0f32;
let mut scaled_f32 = [0.0f32; 128];
if sos_f32.len() > scaled_f32.len() {
return Err(Status::ArgumentError);
}
for stage in 0..num_stages {
let idx = stage * 5;
let mut b0 = sos_f32[idx];
let mut b1 = sos_f32[idx + 1];
let mut b2 = sos_f32[idx + 2];
let a1 = sos_f32[idx + 3];
let a2 = sos_f32[idx + 4];
let scale_factor = match strategy {
ScalingStrategy::LInfNorm => {
let peak = crate::filter_analysis::biquad_peak_gain(&[b0, b1, b2, a1, a2], 64);
if peak > 1.0 { 1.0 / peak } else { 1.0 }
}
ScalingStrategy::L2Norm => {
let l2 = crate::filter_analysis::biquad_l2_norm(&[b0, b1, b2, a1, a2], 64);
if l2 > 1.0 { 1.0 / l2 } else { 1.0 }
}
ScalingStrategy::Direct => 1.0,
};
b0 *= scale_factor;
b1 *= scale_factor;
b2 *= scale_factor;
scaled_f32[idx] = b0;
scaled_f32[idx + 1] = b1;
scaled_f32[idx + 2] = b2;
scaled_f32[idx + 3] = a1;
scaled_f32[idx + 4] = a2;
for k in 0..5 {
let mag = scaled_f32[idx + k].abs();
if mag > max_coeff_mag {
max_coeff_mag = mag;
}
}
}
let mut post_shift = 0u8;
let mut limit = 0.9999f32;
while limit < max_coeff_mag && post_shift < 14 {
post_shift += 1;
limit *= 2.0;
}
let status = crate::support::biquad_coeffs_f32_to_q31(&scaled_f32[..sos_f32.len()], out_q31, post_shift);
if status != Status::Success {
return Err(status);
}
Ok(post_shift)
}
pub fn fir_quantize_q15(taps_f32: &[f32], out_q15: &mut [q15]) -> Result<(), Status> {
if taps_f32.len() != out_q15.len() || taps_f32.is_empty() {
return Err(Status::LengthError);
}
for i in 0..taps_f32.len() {
out_q15[i] = q15::saturating_from_num(taps_f32[i]);
}
Ok(())
}