#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BandShape {
BandPass,
LowShelf,
HighShelf,
Peaking,
Notch,
}
impl BandShape {
#[must_use]
pub fn label(self) -> &'static str {
match self {
BandShape::BandPass => "Band-Pass",
BandShape::LowShelf => "Low-Shelf",
BandShape::HighShelf => "High-Shelf",
BandShape::Peaking => "Peaking",
BandShape::Notch => "Notch",
}
}
}
#[derive(Debug, Clone)]
pub struct FilterBand {
pub center_hz: f32,
pub bandwidth_hz_val: f32,
pub gain_db: f32,
pub shape: BandShape,
pub enabled: bool,
s1: f32,
s2: f32,
b0: f32,
b1: f32,
b2: f32,
a1: f32,
a2: f32,
}
impl FilterBand {
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn peaking(center_hz: f32, bandwidth_hz: f32, gain_db: f32, sample_rate: f32) -> Self {
let mut band = Self {
center_hz,
bandwidth_hz_val: bandwidth_hz,
gain_db,
shape: BandShape::Peaking,
enabled: true,
s1: 0.0,
s2: 0.0,
b0: 1.0,
b1: 0.0,
b2: 0.0,
a1: 0.0,
a2: 0.0,
};
band.recalculate(sample_rate);
band
}
#[must_use]
pub fn band_pass(center_hz: f32, bandwidth_hz: f32, sample_rate: f32) -> Self {
let mut band = Self {
center_hz,
bandwidth_hz_val: bandwidth_hz,
gain_db: 0.0,
shape: BandShape::BandPass,
enabled: true,
s1: 0.0,
s2: 0.0,
b0: 1.0,
b1: 0.0,
b2: 0.0,
a1: 0.0,
a2: 0.0,
};
band.recalculate(sample_rate);
band
}
#[must_use]
pub fn bandwidth_hz(&self) -> f32 {
self.bandwidth_hz_val
}
#[must_use]
pub fn q(&self) -> f32 {
if self.bandwidth_hz_val > 0.0 {
self.center_hz / self.bandwidth_hz_val
} else {
1.0
}
}
pub fn recalculate(&mut self, sample_rate: f32) {
use std::f32::consts::PI;
let w0 = 2.0 * PI * self.center_hz / sample_rate;
let cos_w0 = w0.cos();
let sin_w0 = w0.sin();
let q = self.q().max(0.01);
let alpha = sin_w0 / (2.0 * q);
match self.shape {
BandShape::Peaking => {
let a = 10.0_f32.powf(self.gain_db / 40.0);
self.b0 = 1.0 + alpha * a;
self.b1 = -2.0 * cos_w0;
self.b2 = 1.0 - alpha * a;
let a0 = 1.0 + alpha / a;
self.a1 = -2.0 * cos_w0 / a0;
self.a2 = (1.0 - alpha / a) / a0;
self.b0 /= a0;
self.b1 /= a0;
self.b2 /= a0;
}
BandShape::BandPass => {
self.b0 = alpha;
self.b1 = 0.0;
self.b2 = -alpha;
let a0 = 1.0 + alpha;
self.b0 /= a0;
self.b1 /= a0;
self.b2 /= a0;
self.a1 = -2.0 * cos_w0 / a0;
self.a2 = (1.0 - alpha) / a0;
}
BandShape::Notch => {
self.b0 = 1.0;
self.b1 = -2.0 * cos_w0;
self.b2 = 1.0;
let a0 = 1.0 + alpha;
self.b0 /= a0;
self.b1 /= a0;
self.b2 /= a0;
self.a1 = -2.0 * cos_w0 / a0;
self.a2 = (1.0 - alpha) / a0;
}
BandShape::LowShelf | BandShape::HighShelf => {
self.b0 = 1.0;
self.b1 = 0.0;
self.b2 = 0.0;
self.a1 = 0.0;
self.a2 = 0.0;
}
}
}
pub fn process_sample(&mut self, input: f32) -> f32 {
if !self.enabled {
return input;
}
let output = self.b0 * input + self.s1;
self.s1 = self.b1 * input - self.a1 * output + self.s2;
self.s2 = self.b2 * input - self.a2 * output;
output
}
pub fn reset(&mut self) {
self.s1 = 0.0;
self.s2 = 0.0;
}
}
#[derive(Debug, Clone, Default)]
pub struct FilterBankConfig {
pub band_count: usize,
pub sample_rate: f32,
pub bypassed: bool,
}
impl FilterBankConfig {
#[must_use]
pub fn new(band_count: usize, sample_rate: f32) -> Self {
Self {
band_count,
sample_rate,
bypassed: false,
}
}
#[must_use]
pub fn band_count(&self) -> usize {
self.band_count
}
}
pub struct FilterBank {
bands: Vec<FilterBand>,
sample_rate: f32,
}
impl FilterBank {
#[must_use]
pub fn new(sample_rate: f32) -> Self {
Self {
bands: Vec::new(),
sample_rate,
}
}
pub fn add_band(&mut self, band: FilterBand) {
self.bands.push(band);
}
pub fn process_sample(&mut self, mut input: f32) -> f32 {
for band in &mut self.bands {
input = band.process_sample(input);
}
input
}
#[must_use]
pub fn center_frequencies(&self) -> Vec<f32> {
self.bands.iter().map(|b| b.center_hz).collect()
}
#[must_use]
pub fn band_count(&self) -> usize {
self.bands.len()
}
pub fn reset(&mut self) {
for band in &mut self.bands {
band.reset();
}
}
#[must_use]
pub fn sample_rate(&self) -> f32 {
self.sample_rate
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_band_shape_labels() {
assert_eq!(BandShape::BandPass.label(), "Band-Pass");
assert_eq!(BandShape::Peaking.label(), "Peaking");
assert_eq!(BandShape::Notch.label(), "Notch");
}
#[test]
fn test_filter_band_bandwidth_hz() {
let band = FilterBand::peaking(1000.0, 200.0, 0.0, 48000.0);
assert!((band.bandwidth_hz() - 200.0).abs() < 0.01);
}
#[test]
fn test_filter_band_q() {
let band = FilterBand::peaking(1000.0, 200.0, 0.0, 48000.0);
let q = band.q();
assert!((q - 5.0).abs() < 0.01, "expected q=5, got {q}");
}
#[test]
fn test_filter_band_pass_through_when_disabled() {
let mut band = FilterBand::band_pass(1000.0, 200.0, 48000.0);
band.enabled = false;
let out = band.process_sample(0.5);
assert!((out - 0.5).abs() < 1e-6);
}
#[test]
fn test_filter_band_reset() {
let mut band = FilterBand::band_pass(500.0, 100.0, 48000.0);
band.process_sample(1.0);
band.reset();
assert_eq!(band.s1, 0.0);
assert_eq!(band.s2, 0.0);
}
#[test]
fn test_filter_bank_add_and_count() {
let mut bank = FilterBank::new(48000.0);
assert_eq!(bank.band_count(), 0);
bank.add_band(FilterBand::band_pass(500.0, 100.0, 48000.0));
bank.add_band(FilterBand::band_pass(1000.0, 200.0, 48000.0));
assert_eq!(bank.band_count(), 2);
}
#[test]
fn test_filter_bank_center_frequencies() {
let mut bank = FilterBank::new(48000.0);
bank.add_band(FilterBand::band_pass(500.0, 100.0, 48000.0));
bank.add_band(FilterBand::band_pass(2000.0, 400.0, 48000.0));
let freqs = bank.center_frequencies();
assert_eq!(freqs.len(), 2);
assert!((freqs[0] - 500.0).abs() < 0.01);
assert!((freqs[1] - 2000.0).abs() < 0.01);
}
#[test]
fn test_filter_bank_process_silence() {
let mut bank = FilterBank::new(48000.0);
bank.add_band(FilterBand::peaking(1000.0, 200.0, 6.0, 48000.0));
let out = bank.process_sample(0.0);
assert!(out.abs() < 1e-6);
}
#[test]
fn test_filter_bank_reset() {
let mut bank = FilterBank::new(48000.0);
bank.add_band(FilterBand::peaking(500.0, 100.0, 3.0, 48000.0));
bank.process_sample(1.0);
bank.reset();
let out = bank.process_sample(0.0);
assert!(
out.abs() < 1e-5,
"expected near zero after reset, got {out}"
);
}
#[test]
fn test_filter_bank_config_band_count() {
let cfg = FilterBankConfig::new(10, 48000.0);
assert_eq!(cfg.band_count(), 10);
assert!(!cfg.bypassed);
}
#[test]
fn test_filter_bank_sample_rate() {
let bank = FilterBank::new(44100.0);
assert!((bank.sample_rate() - 44100.0).abs() < 0.01);
}
#[test]
fn test_band_pass_attenuates_off_frequency() {
let mut band = FilterBand::band_pass(10000.0, 500.0, 48000.0);
let out = band.process_sample(1.0);
assert!(out.abs() < 0.5, "expected attenuation of DC, got {out}");
}
#[test]
fn test_notch_reduces_signal() {
let mut band = FilterBand {
center_hz: 1000.0,
bandwidth_hz_val: 100.0,
gain_db: 0.0,
shape: BandShape::Notch,
enabled: true,
s1: 0.0,
s2: 0.0,
b0: 1.0,
b1: 0.0,
b2: 0.0,
a1: 0.0,
a2: 0.0,
};
band.recalculate(48000.0);
let _ = band.process_sample(0.5);
}
}