use crate::{frequency::Hertz, Errors};
use num_traits::Float;
pub const Q_BUTTERWORTH_F32: f32 = core::f32::consts::FRAC_1_SQRT_2;
pub const Q_BUTTERWORTH_F64: f64 = core::f64::consts::FRAC_1_SQRT_2;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Type<DBGain> {
SinglePoleLowPassApprox,
SinglePoleLowPass,
LowPass,
HighPass,
BandPass,
Notch,
AllPass,
LowShelf(DBGain),
HighShelf(DBGain),
PeakingEQ(DBGain),
}
#[derive(Clone, Copy, Debug)]
pub struct Coefficients<C> {
pub a1: C,
pub a2: C,
pub b0: C,
pub b1: C,
pub b2: C,
}
impl<C> Coefficients<C> where C: Float {
pub fn from_normalized_params(
filter_type: Type<C>,
normalized_f0: C,
q_value: C,
) -> Result<Coefficients<C>, Errors> {
#[allow(non_snake_case)]
let TWO: C = C::from(2).unwrap();
#[allow(non_snake_case)]
let PI: C = C::from(core::f64::consts::PI).unwrap();
#[allow(non_snake_case)]
let FORTY: C = C::from(40).unwrap();
if normalized_f0 >= C::one() || normalized_f0 < C::zero() {
return Err(Errors::OutsideNyquist);
}
if q_value <= C::zero() {
return Err(Errors::NegativeQ);
}
let omega = PI * normalized_f0;
let (omega_s, omega_c, alpha) = match filter_type {
Type::SinglePoleLowPassApprox | Type::SinglePoleLowPass => {
(C::nan(), C::nan(), C::nan())
}
_ => {
let omega_s = omega.sin();
(omega_s, omega.cos(), omega_s / (TWO * q_value))
}
};
match filter_type {
Type::SinglePoleLowPassApprox => {
let alpha = omega / (omega + C::one());
Ok(Coefficients {
a1: alpha - C::one(),
a2: C::zero(),
b0: alpha,
b1: C::zero(),
b2: C::zero(),
})
}
Type::SinglePoleLowPass => {
let omega_t = (omega / TWO).tan();
let a0 = C::one() + omega_t;
Ok(Coefficients {
a1: (omega_t - C::one()) / a0,
a2: C::zero(),
b0: omega_t / a0,
b1: omega_t / a0,
b2: C::zero(),
})
}
Type::LowPass => {
let b0 = (C::one() - omega_c) / TWO;
let b1 = C::one() - omega_c;
let b2 = (C::one() - omega_c) / TWO;
let a0 = C::one() + alpha;
let a1 = -TWO * omega_c;
let a2 = C::one() - alpha;
let div = C::one() / a0;
Ok(Coefficients {
a1: a1 * div,
a2: a2 * div,
b0: b0 * div,
b1: b1 * div,
b2: b2 * div,
})
}
Type::HighPass => {
let b0 = (C::one() + omega_c) / TWO;
let b1 = -(C::one() + omega_c);
let b2 = (C::one() + omega_c) / TWO;
let a0 = C::one() + alpha;
let a1 = -TWO * omega_c;
let a2 = C::one() - alpha;
let div = C::one() / a0;
Ok(Coefficients {
a1: a1 * div,
a2: a2 * div,
b0: b0 * div,
b1: b1 * div,
b2: b2 * div,
})
}
Type::BandPass => {
let b0 = omega_s / TWO;
let b1 = C::zero();
let b2 = -(omega_s / TWO);
let a0 = C::one() + alpha;
let a1 = -TWO * omega_c;
let a2 = C::one() - alpha;
let div = C::one() / a0;
Ok(Coefficients {
a1: a1 * div,
a2: a2 * div,
b0: b0 * div,
b1: b1 * div,
b2: b2 * div,
})
}
Type::Notch => {
let b0 = C::one();
let b1 = -TWO * omega_c;
let b2 = C::one();
let a0 = C::one() + alpha;
let a1 = -TWO * omega_c;
let a2 = C::one() - alpha;
let div = C::one() / a0;
Ok(Coefficients {
a1: a1 * div,
a2: a2 * div,
b0: b0 * div,
b1: b1 * div,
b2: b2 * div,
})
}
Type::AllPass => {
let b0 = C::one() - alpha;
let b1 = -TWO * omega_c;
let b2 = C::one() + alpha;
let a0 = C::one() + alpha;
let a1 = -TWO * omega_c;
let a2 = C::one() - alpha;
let div = C::one() / a0;
Ok(Coefficients {
a1: a1 * div,
a2: a2 * div,
b0: b0 * div,
b1: b1 * div,
b2: b2 * div,
})
}
Type::LowShelf(db_gain) => {
let a = C::from(C::from(10).unwrap().powf(db_gain / FORTY)).unwrap();
let b0 = a * ((a + C::one()) - (a - C::one()) * omega_c + TWO * alpha * a.sqrt());
let b1 = TWO * a * ((a - C::one()) - (a + C::one()) * omega_c);
let b2 = a * ((a + C::one()) - (a - C::one()) * omega_c - TWO * alpha * a.sqrt());
let a0 = (a + C::one()) + (a - C::one()) * omega_c + TWO * alpha * a.sqrt();
let a1 = -TWO * ((a - C::one()) + (a + C::one()) * omega_c);
let a2 = (a + C::one()) + (a - C::one()) * omega_c - TWO * alpha * a.sqrt();
let div = C::one() / a0;
Ok(Coefficients {
a1: a1 * div,
a2: a2 * div,
b0: b0 * div,
b1: b1 * div,
b2: b2 * div,
})
}
Type::HighShelf(db_gain) => {
let a = C::from(C::from(10).unwrap().powf(db_gain / FORTY)).unwrap();
let b0 = a * ((a + C::one()) + (a - C::one()) * omega_c + TWO * alpha * a.sqrt());
let b1 = -TWO * a * ((a - C::one()) + (a + C::one()) * omega_c);
let b2 = a * ((a + C::one()) + (a - C::one()) * omega_c - TWO * alpha * a.sqrt());
let a0 = (a + C::one()) - (a - C::one()) * omega_c + TWO * alpha * a.sqrt();
let a1 = TWO * ((a - C::one()) - (a + C::one()) * omega_c);
let a2 = (a + C::one()) - (a - C::one()) * omega_c - TWO * alpha * a.sqrt();
let div = C::one() / a0;
Ok(Coefficients {
a1: a1 * div,
a2: a2 * div,
b0: b0 * div,
b1: b1 * div,
b2: b2 * div,
})
}
Type::PeakingEQ(db_gain) => {
let a = C::from(C::from(10).unwrap().powf(db_gain / FORTY)).unwrap();
let b0 = C::one() + alpha * a;
let b1 = -TWO * omega_c;
let b2 = C::one() - alpha * a;
let a0 = C::one() + alpha / a;
let a1 = -TWO * omega_c;
let a2 = C::one() - alpha / a;
let div = C::one() / a0;
Ok(Coefficients {
a1: a1 * div,
a2: a2 * div,
b0: b0 * div,
b1: b1 * div,
b2: b2 * div,
})
}
}
}
pub fn from_params(
filter: Type<C>,
fs: Hertz<C>,
f0: Hertz<C>,
q_value: C,
) -> Result<Coefficients<C>, Errors> {
let normalized_f0 = f0.hz() / (fs.hz() / C::from(2).unwrap());
Self::from_normalized_params(filter, normalized_f0, q_value)
}
pub fn band_0db_from_cutting_frequencies(
filter_type: Type<C>,
normalized_f01: C,
normalized_f02: C,
) -> Result<Coefficients<C>, Errors> {
if normalized_f01 < C::zero()
|| normalized_f02 < C::zero()
|| normalized_f01 > C::one()
|| normalized_f02 > C::one()
|| normalized_f01 > normalized_f02
{
return Err(Errors::OutsideNyquist);
}
#[allow(non_snake_case)]
let TWO: C = C::from(2).unwrap();
assert!(filter_type == Type::<_>::BandPass || filter_type == Type::<_>::Notch);
let frac_freq: C = normalized_f02 / normalized_f01;
let bw_in_octaves = frac_freq.log2();
let normalized_f0 = TWO.powf(normalized_f01.log2() + bw_in_octaves / TWO);
Self::from_normalized_params(filter_type, normalized_f0, C::one())
}
}