use no_denormals::*;
use std::f32::consts::PI;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BiquadType {
LowPass,
HighPass,
BandPass,
Notch,
AllPass,
Peak,
LowShelf,
HighShelf,
}
pub struct Biquad {
b0: f32,
b1: f32,
b2: f32,
a1: f32,
a2: f32,
z1: f32,
z2: f32,
}
impl Biquad {
pub fn new(kind: BiquadType, sample_rate: f32, freq: f32, q: f32, gain_db: f32) -> Self {
let q = q.max(1e-4);
let omega = 2.0 * PI * freq.max(1.0).min(sample_rate * 0.499) / sample_rate;
let sin_w = omega.sin();
let cos_w = omega.cos();
let alpha = sin_w / (2.0 * q);
let a = 10.0f32.powf(gain_db / 40.0);
let (b0, b1, b2, a0, a1, a2) = match kind {
BiquadType::LowPass => {
let b1 = 1.0 - cos_w;
let b0 = b1 * 0.5;
let b2 = b0;
let a0 = 1.0 + alpha;
let a1 = -2.0 * cos_w;
let a2 = 1.0 - alpha;
(b0, b1, b2, a0, a1, a2)
}
BiquadType::HighPass => {
let b0 = (1.0 + cos_w) * 0.5;
let b1 = -(1.0 + cos_w);
let b2 = b0;
let a0 = 1.0 + alpha;
let a1 = -2.0 * cos_w;
let a2 = 1.0 - alpha;
(b0, b1, b2, a0, a1, a2)
}
BiquadType::BandPass => {
let b0 = alpha;
let b1 = 0.0;
let b2 = -alpha;
let a0 = 1.0 + alpha;
let a1 = -2.0 * cos_w;
let a2 = 1.0 - alpha;
(b0, b1, b2, a0, a1, a2)
}
BiquadType::Notch => {
let b0 = 1.0;
let b1 = -2.0 * cos_w;
let b2 = 1.0;
let a0 = 1.0 + alpha;
let a1 = -2.0 * cos_w;
let a2 = 1.0 - alpha;
(b0, b1, b2, a0, a1, a2)
}
BiquadType::AllPass => {
let b0 = 1.0 - alpha;
let b1 = -2.0 * cos_w;
let b2 = 1.0 + alpha;
let a0 = 1.0 + alpha;
let a1 = -2.0 * cos_w;
let a2 = 1.0 - alpha;
(b0, b1, b2, a0, a1, a2)
}
BiquadType::Peak => {
let b0 = 1.0 + alpha * a;
let b1 = -2.0 * cos_w;
let b2 = 1.0 - alpha * a;
let a0 = 1.0 + alpha / a;
let a1 = -2.0 * cos_w;
let a2 = 1.0 - alpha / a;
(b0, b1, b2, a0, a1, a2)
}
BiquadType::LowShelf => {
let sqrt_a = a.sqrt();
let two_sqrt_a_alpha = 2.0 * sqrt_a * alpha;
let b0 = a * ((a + 1.0) - (a - 1.0) * cos_w + two_sqrt_a_alpha);
let b1 = 2.0 * a * ((a - 1.0) - (a + 1.0) * cos_w);
let b2 = a * ((a + 1.0) - (a - 1.0) * cos_w - two_sqrt_a_alpha);
let a0 = (a + 1.0) + (a - 1.0) * cos_w + two_sqrt_a_alpha;
let a1 = -2.0 * ((a - 1.0) + (a + 1.0) * cos_w);
let a2 = (a + 1.0) + (a - 1.0) * cos_w - two_sqrt_a_alpha;
(b0, b1, b2, a0, a1, a2)
}
BiquadType::HighShelf => {
let sqrt_a = a.sqrt();
let two_sqrt_a_alpha = 2.0 * sqrt_a * alpha;
let b0 = a * ((a + 1.0) + (a - 1.0) * cos_w + two_sqrt_a_alpha);
let b1 = -2.0 * a * ((a - 1.0) + (a + 1.0) * cos_w);
let b2 = a * ((a + 1.0) + (a - 1.0) * cos_w - two_sqrt_a_alpha);
let a0 = (a + 1.0) - (a - 1.0) * cos_w + two_sqrt_a_alpha;
let a1 = 2.0 * ((a - 1.0) - (a + 1.0) * cos_w);
let a2 = (a + 1.0) - (a - 1.0) * cos_w - two_sqrt_a_alpha;
(b0, b1, b2, a0, a1, a2)
}
};
Self {
b0: b0 / a0,
b1: b1 / a0,
b2: b2 / a0,
a1: a1 / a0,
a2: a2 / a0,
z1: 0.0,
z2: 0.0,
}
}
pub fn from_coefficients(b0: f32, b1: f32, b2: f32, a1: f32, a2: f32) -> Self {
Self {
b0,
b1,
b2,
a1,
a2,
z1: 0.0,
z2: 0.0,
}
}
pub fn reset(&mut self) {
self.z1 = 0.0;
self.z2 = 0.0;
}
#[inline]
pub fn process(&mut self, input: f32) -> f32 {
let output = self.b0 * input + self.z1;
self.z1 = self.b1 * input - self.a1 * output + self.z2;
self.z2 = self.b2 * input - self.a2 * output;
output
}
pub fn run(&mut self, input: &[f32], output: &mut [f32]) {
unsafe {
no_denormals(|| {
for index in 0..input.len().min(output.len()) {
output[index] = self.process(input[index]);
}
});
}
}
}
pub struct IirFilter {
sections: Vec<Biquad>,
}
impl IirFilter {
pub fn new() -> Self {
Self {
sections: Vec::new(),
}
}
pub fn push(&mut self, section: Biquad) {
self.sections.push(section);
}
pub fn len(&self) -> usize {
self.sections.len()
}
pub fn is_empty(&self) -> bool {
self.sections.is_empty()
}
pub fn butterworth(kind: BiquadType, order: usize, sample_rate: f32, cutoff: f32) -> Self {
assert!(
order > 0 && order.is_multiple_of(2),
"Butterworth cascade order must be a positive even number, got {order}"
);
assert!(
matches!(kind, BiquadType::LowPass | BiquadType::HighPass),
"Butterworth cascade only supports LowPass/HighPass"
);
let n_sections = order / 2;
let mut sections = Vec::with_capacity(n_sections);
for k in 0..n_sections {
let q = 1.0 / (2.0 * (((2 * k + 1) as f32) * PI / (2.0 * order as f32)).cos());
sections.push(Biquad::new(kind, sample_rate, cutoff, q, 0.0));
}
Self { sections }
}
pub fn reset(&mut self) {
for s in &mut self.sections {
s.reset();
}
}
#[inline]
pub fn process(&mut self, input: f32) -> f32 {
let mut x = input;
for s in &mut self.sections {
x = s.process(x);
}
x
}
pub fn run(&mut self, input: &[f32], output: &mut [f32]) {
unsafe {
no_denormals(|| {
for index in 0..input.len().min(output.len()) {
output[index] = self.process(input[index]);
}
});
}
}
}
impl Default for IirFilter {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lowpass_attenuates_high_frequency() {
let sr = 48000.0;
let mut lp = Biquad::new(BiquadType::LowPass, sr, 500.0, 0.707, 0.0);
let measure = |filter: &mut Biquad, freq: f32| -> f32 {
filter.reset();
let n = (sr / freq * 20.0) as usize;
let mut sum_sq = 0.0;
let mut count = 0;
for i in 0..n {
let t = i as f32 / sr;
let x = (2.0 * PI * freq * t).sin();
let y = filter.process(x);
if i > n / 2 {
sum_sq += y * y;
count += 1;
}
}
(sum_sq / count as f32).sqrt()
};
let low_rms = measure(&mut lp, 50.0);
let high_rms = measure(&mut lp, 8000.0);
assert!(
high_rms < low_rms * 0.2,
"lowpass should attenuate 8kHz much more than 50Hz: low={low_rms}, high={high_rms}"
);
}
#[test]
fn test_highpass_attenuates_low_frequency() {
let sr = 48000.0;
let mut hp = Biquad::new(BiquadType::HighPass, sr, 2000.0, 0.707, 0.0);
let measure = |filter: &mut Biquad, freq: f32| -> f32 {
filter.reset();
let n = (sr / freq * 20.0).min(sr) as usize;
let mut sum_sq = 0.0;
let mut count = 0;
for i in 0..n {
let t = i as f32 / sr;
let x = (2.0 * PI * freq * t).sin();
let y = filter.process(x);
if i > n / 2 {
sum_sq += y * y;
count += 1;
}
}
(sum_sq / count as f32).sqrt()
};
let low_rms = measure(&mut hp, 50.0);
let high_rms = measure(&mut hp, 8000.0);
assert!(
low_rms < high_rms * 0.2,
"highpass should attenuate 50Hz much more than 8kHz: low={low_rms}, high={high_rms}"
);
}
#[test]
fn test_butterworth_cascade_no_nan() {
let mut filter = IirFilter::butterworth(BiquadType::LowPass, 8, 44100.0, 1000.0);
assert_eq!(filter.len(), 4);
for i in 0..1000 {
let x = ((i as f32) * 0.01).sin();
let y = filter.process(x);
assert!(y.is_finite(), "cascade output not finite at sample {i}");
}
}
#[test]
#[should_panic]
fn test_butterworth_odd_order_panics() {
let _ = IirFilter::butterworth(BiquadType::LowPass, 3, 44100.0, 1000.0);
}
}