use crate::traits::FloatScalar;
use super::biquad::{
assemble_cascade, bilinear_hp_pair, bilinear_hp_real, bilinear_lp_pair, bilinear_lp_real,
cascade_gain_at, prewarp, scale_first_section_gain, BiquadCascade,
};
use super::{validate_design_params, ControlError};
fn chebyshev_params<T: FloatScalar>(order: usize, ripple_db: T) -> (T, T, T) {
let ten = T::from(10.0).unwrap();
let nf = T::from(order).unwrap();
let epsilon = (ten.powf(ripple_db / ten) - T::one()).sqrt();
let a = (T::one() / epsilon).asinh() / nf;
(epsilon, a.sinh(), a.cosh())
}
fn chebyshev_lp_pole<T: FloatScalar>(
order: usize,
k: usize,
wa: T,
sinh_a: T,
cosh_a: T,
) -> (T, T) {
let two = T::one() + T::one();
let pi = T::from(core::f64::consts::PI).unwrap();
let nf = T::from(order).unwrap();
let kf = T::from(k).unwrap();
let phi = pi * (two * kf + T::one()) / (two * nf);
(-wa * sinh_a * phi.sin(), wa * cosh_a * phi.cos())
}
fn passband_target<T: FloatScalar>(order: usize, epsilon: T) -> T {
if order % 2 == 0 {
T::one() / (T::one() + epsilon * epsilon).sqrt()
} else {
T::one()
}
}
pub fn chebyshev1_lowpass<T: FloatScalar, const N: usize>(
order: usize,
ripple_db: T,
cutoff: T,
sample_rate: T,
) -> Result<BiquadCascade<T, N>, ControlError> {
validate_design_params::<T, N>(order, cutoff, sample_rate)?;
if ripple_db <= T::zero() || !ripple_db.is_finite() {
return Err(ControlError::InvalidRipple);
}
let (wa, c) = prewarp(cutoff, sample_rate);
let (epsilon, sinh_a, cosh_a) = chebyshev_params(order, ripple_db);
let mut cascade = assemble_cascade::<T, N>(
order,
|k| {
let (sigma, omega) = chebyshev_lp_pole(order, k, wa, sinh_a, cosh_a);
bilinear_lp_pair(sigma, omega, wa, c)
},
|| {
bilinear_lp_real(-wa * sinh_a, wa * sinh_a, c)
},
);
let current_dc = cascade_gain_at(&cascade.sections, T::one());
if current_dc.abs() > T::epsilon() {
let scale = passband_target(order, epsilon) / current_dc;
scale_first_section_gain(&mut cascade.sections, scale);
}
Ok(cascade)
}
pub fn chebyshev1_highpass<T: FloatScalar, const N: usize>(
order: usize,
ripple_db: T,
cutoff: T,
sample_rate: T,
) -> Result<BiquadCascade<T, N>, ControlError> {
validate_design_params::<T, N>(order, cutoff, sample_rate)?;
if ripple_db <= T::zero() || !ripple_db.is_finite() {
return Err(ControlError::InvalidRipple);
}
let (wa, c) = prewarp(cutoff, sample_rate);
let (epsilon, sinh_a, cosh_a) = chebyshev_params(order, ripple_db);
let wa2 = wa * wa;
let mut cascade = assemble_cascade::<T, N>(
order,
|k| {
let (sigma_lp, omega_lp) = chebyshev_lp_pole(order, k, wa, sinh_a, cosh_a);
let mag2 = sigma_lp * sigma_lp + omega_lp * omega_lp;
bilinear_hp_pair(wa2 * sigma_lp / mag2, -wa2 * omega_lp / mag2, wa, c)
},
|| {
bilinear_hp_real(wa2 / (-wa * sinh_a), wa, c)
},
);
let current_nyq = cascade_gain_at(&cascade.sections, -T::one());
if current_nyq.abs() > T::epsilon() {
let scale = passband_target(order, epsilon) / current_nyq.abs();
scale_first_section_gain(&mut cascade.sections, scale);
}
Ok(cascade)
}