use serde::{Deserialize, Serialize};
use crate::error::Result;
use crate::filter::{BiquadFilter, FilterType};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EqBand {
filter: BiquadFilter,
pub enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParametricEq {
bands: Vec<EqBand>,
sample_rate: f32,
}
impl ParametricEq {
#[must_use]
pub fn new(sample_rate: f32) -> Self {
Self {
bands: Vec::new(),
sample_rate,
}
}
pub fn add_band(
&mut self,
filter_type: FilterType,
frequency: f32,
q: f32,
gain_db: f32,
) -> Result<()> {
let filter = BiquadFilter::with_gain(filter_type, self.sample_rate, frequency, q, gain_db)?;
self.bands.push(EqBand {
filter,
enabled: true,
});
Ok(())
}
#[must_use]
pub fn num_bands(&self) -> usize {
self.bands.len()
}
pub fn band_mut(&mut self, index: usize) -> Option<&mut EqBand> {
self.bands.get_mut(index)
}
#[inline]
#[must_use]
pub fn process_sample(&mut self, input: f32) -> f32 {
let mut out = input;
for band in &mut self.bands {
if band.enabled {
out = band.filter.process_sample(out);
}
}
out
}
#[inline]
pub fn process_buffer(&mut self, buffer: &mut [f32]) {
for s in buffer.iter_mut() {
*s = self.process_sample(*s);
}
}
pub fn reset(&mut self) {
for band in &mut self.bands {
band.filter.reset();
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphicEq {
eq: ParametricEq,
active_frequencies: Vec<f32>,
}
pub const GRAPHIC_EQ_FREQUENCIES: [f32; 10] = [
31.25, 62.5, 125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0, 8000.0, 16000.0,
];
impl GraphicEq {
pub fn new(sample_rate: f32) -> Result<Self> {
let mut eq = ParametricEq::new(sample_rate);
let mut active_frequencies = Vec::new();
let q = 1.4; for &freq in &GRAPHIC_EQ_FREQUENCIES {
if freq < sample_rate * 0.5 {
eq.add_band(FilterType::Peak, freq, q, 0.0)?;
active_frequencies.push(freq);
}
}
Ok(Self {
eq,
active_frequencies,
})
}
pub fn set_band_gain(&mut self, index: usize, gain_db: f32) -> Result<()> {
if index >= self.active_frequencies.len() {
return Err(crate::NaadError::InvalidParameter {
name: "index".to_string(),
reason: format!(
"band index {index} out of range ({} active bands)",
self.active_frequencies.len()
),
});
}
let freq = self.active_frequencies[index];
if let Some(band) = self.eq.band_mut(index) {
band.filter.set_params(freq, 1.4, gain_db)
} else {
Err(crate::NaadError::InvalidParameter {
name: "index".to_string(),
reason: format!("band index {index} out of range"),
})
}
}
#[inline]
#[must_use]
pub fn process_sample(&mut self, input: f32) -> f32 {
self.eq.process_sample(input)
}
#[inline]
pub fn process_buffer(&mut self, buffer: &mut [f32]) {
self.eq.process_buffer(buffer);
}
#[must_use]
pub fn num_bands(&self) -> usize {
self.eq.num_bands()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeEsser {
detector_filter: BiquadFilter,
envelope: f32,
pub threshold_db: f32,
pub max_reduction_db: f32,
smooth_coeff: f32,
}
impl DeEsser {
pub fn new(center_freq: f32, threshold_db: f32, sample_rate: f32) -> Result<Self> {
let detector_filter =
BiquadFilter::new(FilterType::BandPass, sample_rate, center_freq, 2.0)?;
let smooth_coeff = 1.0 - (-1.0 / (0.002 * sample_rate)).exp();
Ok(Self {
detector_filter,
envelope: 0.0,
threshold_db,
max_reduction_db: -12.0,
smooth_coeff,
})
}
#[inline]
#[must_use]
pub fn process_sample(&mut self, input: f32) -> f32 {
let detected = self.detector_filter.process_sample(input);
let level = detected.abs();
self.envelope += self.smooth_coeff * (level - self.envelope);
self.envelope = crate::flush_denormal(self.envelope);
let env_db = crate::dsp_util::amplitude_to_db(self.envelope);
if env_db > self.threshold_db {
let overshoot = env_db - self.threshold_db;
let reduction_db = (-overshoot).max(self.max_reduction_db);
input * crate::dsp_util::db_to_amplitude(reduction_db)
} else {
input
}
}
#[inline]
pub fn process_buffer(&mut self, buffer: &mut [f32]) {
for s in buffer.iter_mut() {
*s = self.process_sample(*s);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parametric_eq_passthrough() {
let mut eq = ParametricEq::new(44100.0);
let out = eq.process_sample(0.5);
assert!((out - 0.5).abs() < f32::EPSILON);
}
#[test]
fn test_parametric_eq_with_band() {
let mut eq = ParametricEq::new(44100.0);
eq.add_band(FilterType::Peak, 1000.0, 1.0, 6.0).unwrap();
assert_eq!(eq.num_bands(), 1);
let out = eq.process_sample(0.5);
assert!(out.is_finite());
}
#[test]
fn test_graphic_eq() {
let mut geq = GraphicEq::new(44100.0).unwrap();
assert_eq!(geq.num_bands(), 10);
geq.set_band_gain(4, 6.0).unwrap(); let out = geq.process_sample(0.5);
assert!(out.is_finite());
}
#[test]
fn test_graphic_eq_invalid_band() {
let mut geq = GraphicEq::new(44100.0).unwrap();
assert!(geq.set_band_gain(20, 6.0).is_err());
}
#[test]
fn test_deesser() {
let mut de = DeEsser::new(6000.0, -20.0, 44100.0).unwrap();
let out = de.process_sample(0.5);
assert!(out.is_finite());
}
#[test]
fn test_serde_roundtrip_parametric_eq() {
let mut eq = ParametricEq::new(44100.0);
eq.add_band(FilterType::Peak, 1000.0, 1.0, 3.0).unwrap();
let json = serde_json::to_string(&eq).unwrap();
let back: ParametricEq = serde_json::from_str(&json).unwrap();
assert_eq!(eq.num_bands(), back.num_bands());
}
#[test]
fn test_serde_roundtrip_graphic_eq() {
let geq = GraphicEq::new(44100.0).unwrap();
let json = serde_json::to_string(&geq).unwrap();
let _back: GraphicEq = serde_json::from_str(&json).unwrap();
}
#[test]
fn test_serde_roundtrip_deesser() {
let de = DeEsser::new(6000.0, -20.0, 44100.0).unwrap();
let json = serde_json::to_string(&de).unwrap();
let back: DeEsser = serde_json::from_str(&json).unwrap();
assert!((de.threshold_db - back.threshold_db).abs() < f32::EPSILON);
}
}