#![forbid(unsafe_code)]
#![allow(clippy::cast_lossless)]
use super::biquad::{BiquadCoefficients, BiquadState, BiquadType};
pub const MAX_EQ_BANDS: usize = 32;
#[derive(Clone, Debug)]
pub struct EqBand {
pub filter_type: BiquadType,
pub frequency: f64,
pub gain_db: f64,
pub q: f64,
pub enabled: bool,
}
impl EqBand {
#[must_use]
pub fn new(filter_type: BiquadType, frequency: f64, gain_db: f64, q: f64) -> Self {
Self {
filter_type,
frequency,
gain_db,
q,
enabled: true,
}
}
#[must_use]
pub fn low_shelf(frequency: f64, gain_db: f64) -> Self {
Self::new(BiquadType::LowShelf, frequency, gain_db, 0.707)
}
#[must_use]
pub fn high_shelf(frequency: f64, gain_db: f64) -> Self {
Self::new(BiquadType::HighShelf, frequency, gain_db, 0.707)
}
#[must_use]
pub fn peaking(frequency: f64, gain_db: f64, q: f64) -> Self {
Self::new(BiquadType::Peaking, frequency, gain_db, q)
}
#[must_use]
pub fn low_pass(frequency: f64, q: f64) -> Self {
Self::new(BiquadType::LowPass, frequency, 0.0, q)
}
#[must_use]
pub fn high_pass(frequency: f64, q: f64) -> Self {
Self::new(BiquadType::HighPass, frequency, 0.0, q)
}
#[must_use]
pub fn band_pass(frequency: f64, q: f64) -> Self {
Self::new(BiquadType::BandPass, frequency, 0.0, q)
}
#[must_use]
pub fn notch(frequency: f64, q: f64) -> Self {
Self::new(BiquadType::Notch, frequency, 0.0, q)
}
#[must_use]
pub fn with_enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
}
impl Default for EqBand {
fn default() -> Self {
Self {
filter_type: BiquadType::Peaking,
frequency: 1000.0,
gain_db: 0.0,
q: 1.0,
enabled: true,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct EqualizerConfig {
pub bands: Vec<EqBand>,
}
impl EqualizerConfig {
#[must_use]
pub fn new() -> Self {
Self { bands: Vec::new() }
}
#[must_use]
pub fn add_band(mut self, band: EqBand) -> Self {
if self.bands.len() < MAX_EQ_BANDS {
self.bands.push(band);
}
self
}
#[must_use]
pub fn three_band(low_gain_db: f64, mid_gain_db: f64, high_gain_db: f64) -> Self {
Self::new()
.add_band(EqBand::low_shelf(250.0, low_gain_db))
.add_band(EqBand::peaking(1000.0, mid_gain_db, 1.0))
.add_band(EqBand::high_shelf(4000.0, high_gain_db))
}
#[must_use]
pub fn five_band(
low_gain_db: f64,
low_mid_gain_db: f64,
mid_gain_db: f64,
high_mid_gain_db: f64,
high_gain_db: f64,
) -> Self {
Self::new()
.add_band(EqBand::low_shelf(100.0, low_gain_db))
.add_band(EqBand::peaking(250.0, low_mid_gain_db, 1.0))
.add_band(EqBand::peaking(1000.0, mid_gain_db, 1.0))
.add_band(EqBand::peaking(4000.0, high_mid_gain_db, 1.0))
.add_band(EqBand::high_shelf(8000.0, high_gain_db))
}
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn graphic_10_band(
g31: f64,
g62: f64,
g125: f64,
g250: f64,
g500: f64,
g1k: f64,
g2k: f64,
g4k: f64,
g8k: f64,
g16k: f64,
) -> Self {
let q = 1.414; Self::new()
.add_band(EqBand::peaking(31.0, g31, q))
.add_band(EqBand::peaking(62.0, g62, q))
.add_band(EqBand::peaking(125.0, g125, q))
.add_band(EqBand::peaking(250.0, g250, q))
.add_band(EqBand::peaking(500.0, g500, q))
.add_band(EqBand::peaking(1000.0, g1k, q))
.add_band(EqBand::peaking(2000.0, g2k, q))
.add_band(EqBand::peaking(4000.0, g4k, q))
.add_band(EqBand::peaking(8000.0, g8k, q))
.add_band(EqBand::peaking(16000.0, g16k, q))
}
}
pub struct Equalizer {
coefficients: Vec<BiquadCoefficients>,
states: Vec<Vec<BiquadState>>,
config: EqualizerConfig,
sample_rate: f64,
channels: usize,
}
impl Equalizer {
#[must_use]
pub fn new(config: EqualizerConfig, sample_rate: f64, channels: usize) -> Self {
let mut eq = Self {
coefficients: Vec::new(),
states: vec![Vec::new(); channels],
config: config.clone(),
sample_rate,
channels,
};
eq.update_coefficients(&config);
eq
}
fn update_coefficients(&mut self, config: &EqualizerConfig) {
self.coefficients.clear();
for band in &config.bands {
let coeffs = BiquadCoefficients::calculate(
band.filter_type,
self.sample_rate,
band.frequency,
band.q,
band.gain_db,
);
self.coefficients.push(coeffs);
}
for channel_states in &mut self.states {
channel_states.resize(self.coefficients.len(), BiquadState::new());
}
}
pub fn set_config(&mut self, config: EqualizerConfig) {
self.config = config.clone();
self.update_coefficients(&config);
}
#[must_use]
pub fn config(&self) -> &EqualizerConfig {
&self.config
}
pub fn process_channel(&mut self, channel: usize, samples: &mut [f64]) {
if channel >= self.channels {
return;
}
for sample in samples.iter_mut() {
let mut value = *sample;
for (band_idx, band) in self.config.bands.iter().enumerate() {
if !band.enabled {
continue;
}
if band_idx < self.coefficients.len() && band_idx < self.states[channel].len() {
value =
self.states[channel][band_idx].process(value, &self.coefficients[band_idx]);
}
}
*sample = value;
}
}
pub fn process_interleaved(&mut self, samples: &mut [f64], num_samples: usize) {
for i in 0..num_samples {
for ch in 0..self.channels {
let idx = i * self.channels + ch;
if idx >= samples.len() {
break;
}
let mut value = samples[idx];
for (band_idx, band) in self.config.bands.iter().enumerate() {
if !band.enabled {
continue;
}
if band_idx < self.coefficients.len()
&& ch < self.states.len()
&& band_idx < self.states[ch].len()
{
value =
self.states[ch][band_idx].process(value, &self.coefficients[band_idx]);
}
}
samples[idx] = value;
}
}
}
pub fn process_planar(&mut self, channels: &mut [Vec<f64>]) {
for (ch, channel_samples) in channels.iter_mut().enumerate() {
self.process_channel(ch, channel_samples);
}
}
pub fn reset(&mut self) {
for channel_states in &mut self.states {
for state in channel_states {
state.reset();
}
}
}
pub fn update_band(&mut self, band_index: usize, band: EqBand) {
if band_index < self.config.bands.len() {
self.config.bands[band_index] = band;
if band_index < self.coefficients.len() {
let coeffs = BiquadCoefficients::calculate(
self.config.bands[band_index].filter_type,
self.sample_rate,
self.config.bands[band_index].frequency,
self.config.bands[band_index].q,
self.config.bands[band_index].gain_db,
);
self.coefficients[band_index] = coeffs;
}
}
}
pub fn set_band_enabled(&mut self, band_index: usize, enabled: bool) {
if band_index < self.config.bands.len() {
self.config.bands[band_index].enabled = enabled;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dsp::biquad::BiquadType;
const SAMPLE_RATE: f64 = 48000.0;
#[test]
fn test_eq_band_default() {
let band = EqBand::default();
assert_eq!(band.filter_type, BiquadType::Peaking);
assert_eq!(band.frequency, 1000.0);
assert_eq!(band.gain_db, 0.0);
assert!(band.enabled);
}
#[test]
fn test_eq_band_low_shelf() {
let band = EqBand::low_shelf(200.0, 3.0);
assert_eq!(band.filter_type, BiquadType::LowShelf);
assert_eq!(band.frequency, 200.0);
assert_eq!(band.gain_db, 3.0);
}
#[test]
fn test_eq_band_high_shelf() {
let band = EqBand::high_shelf(8000.0, -3.0);
assert_eq!(band.filter_type, BiquadType::HighShelf);
assert_eq!(band.frequency, 8000.0);
assert_eq!(band.gain_db, -3.0);
}
#[test]
fn test_eq_band_peaking() {
let band = EqBand::peaking(1000.0, 6.0, 1.5);
assert_eq!(band.filter_type, BiquadType::Peaking);
assert_eq!(band.q, 1.5);
assert_eq!(band.gain_db, 6.0);
}
#[test]
fn test_eq_band_notch() {
let band = EqBand::notch(500.0, 2.0);
assert_eq!(band.filter_type, BiquadType::Notch);
assert_eq!(band.frequency, 500.0);
}
#[test]
fn test_eq_band_enabled_toggle() {
let band = EqBand::peaking(1000.0, 3.0, 1.0).with_enabled(false);
assert!(!band.enabled);
let band_on = band.with_enabled(true);
assert!(band_on.enabled);
}
#[test]
fn test_equalizer_config_new() {
let config = EqualizerConfig::new();
assert!(config.bands.is_empty());
}
#[test]
fn test_equalizer_config_add_band() {
let config = EqualizerConfig::new()
.add_band(EqBand::peaking(1000.0, 3.0, 1.0))
.add_band(EqBand::high_shelf(8000.0, -2.0));
assert_eq!(config.bands.len(), 2);
}
#[test]
fn test_equalizer_config_three_band() {
let config = EqualizerConfig::three_band(3.0, 0.0, -2.0);
assert_eq!(config.bands.len(), 3);
}
#[test]
fn test_equalizer_config_five_band() {
let config = EqualizerConfig::five_band(1.0, -1.0, 2.0, -2.0, 3.0);
assert_eq!(config.bands.len(), 5);
}
#[test]
fn test_equalizer_config_ten_band() {
let config =
EqualizerConfig::graphic_10_band(0.0, 1.0, 2.0, 3.0, -1.0, -2.0, -3.0, 0.0, 1.0, 2.0);
assert_eq!(config.bands.len(), 10);
}
#[test]
fn test_equalizer_new_and_passthrough() {
let config = EqualizerConfig::new().add_band(EqBand::peaking(1000.0, 0.0, 1.0));
let mut eq = Equalizer::new(config, SAMPLE_RATE, 1);
let mut samples = vec![1.0_f64; 200];
eq.process_channel(0, &mut samples);
assert!((samples[199] - 1.0).abs() < 0.05);
}
#[test]
fn test_equalizer_process_interleaved() {
let config = EqualizerConfig::three_band(0.0, 0.0, 0.0);
let mut eq = Equalizer::new(config, SAMPLE_RATE, 2);
let mut samples = vec![0.5_f64; 40]; eq.process_interleaved(&mut samples, 20);
for s in &samples {
assert!(s.is_finite());
}
}
#[test]
fn test_equalizer_process_planar() {
let config = EqualizerConfig::five_band(1.0, -1.0, 0.0, 1.0, -1.0);
let mut eq = Equalizer::new(config, SAMPLE_RATE, 2);
let mut channels = vec![vec![0.3_f64; 256]; 2];
eq.process_planar(&mut channels);
for ch in &channels {
for s in ch {
assert!(s.is_finite());
}
}
}
#[test]
fn test_equalizer_reset() {
let config = EqualizerConfig::three_band(3.0, 0.0, -3.0);
let mut eq = Equalizer::new(config, SAMPLE_RATE, 1);
let mut samples = vec![1.0_f64; 100];
eq.process_channel(0, &mut samples);
eq.reset();
let mut samples2 = vec![1.0_f64; 100];
let config2 = EqualizerConfig::three_band(3.0, 0.0, -3.0);
let mut eq2 = Equalizer::new(config2, SAMPLE_RATE, 1);
eq2.process_channel(0, &mut samples2);
let mut samples3 = vec![1.0_f64; 100];
eq.process_channel(0, &mut samples3);
assert!((samples2[99] - samples3[99]).abs() < 1e-10);
}
#[test]
fn test_equalizer_update_band() {
let config = EqualizerConfig::new().add_band(EqBand::peaking(1000.0, 0.0, 1.0));
let mut eq = Equalizer::new(config, SAMPLE_RATE, 1);
eq.update_band(0, EqBand::peaking(2000.0, 6.0, 1.0));
assert_eq!(eq.config().bands[0].frequency, 2000.0);
assert_eq!(eq.config().bands[0].gain_db, 6.0);
}
#[test]
fn test_equalizer_set_band_enabled() {
let config = EqualizerConfig::new().add_band(EqBand::peaking(1000.0, 12.0, 1.0));
let mut eq = Equalizer::new(config, SAMPLE_RATE, 1);
eq.set_band_enabled(0, false);
assert!(!eq.config().bands[0].enabled);
let mut s = vec![1.0_f64; 100];
eq.process_channel(0, &mut s);
assert!((s[99] - 1.0).abs() < 1e-10);
}
#[test]
fn test_equalizer_max_bands_limit() {
let mut config = EqualizerConfig::new();
for _ in 0..MAX_EQ_BANDS + 5 {
config = config.add_band(EqBand::peaking(1000.0, 0.0, 1.0));
}
assert_eq!(config.bands.len(), MAX_EQ_BANDS);
}
#[test]
fn test_equalizer_set_config() {
let config1 = EqualizerConfig::three_band(3.0, 0.0, -3.0);
let mut eq = Equalizer::new(config1, SAMPLE_RATE, 1);
let config2 = EqualizerConfig::five_band(0.0, 0.0, 0.0, 0.0, 0.0);
eq.set_config(config2);
assert_eq!(eq.config().bands.len(), 5);
}
}