use crate::core::{AudioError, AudioFrame, AudioResult, AudioSpec};
use crate::mic_sim::contracts::MicrophoneSimProcessor;
use crate::mic_sim::voicing::{MicrophoneVoicing, ResonanceBand};
use biquad::{Biquad, Coefficients, DirectForm1, Hertz, ToHertz, Type, Q_BUTTERWORTH_F32};
pub struct VoicingContourProcessor {
voicing: MicrophoneVoicing,
filters: Vec<DirectForm1<f32>>,
}
impl VoicingContourProcessor {
pub fn new(voicing: MicrophoneVoicing) -> Self {
Self {
voicing,
filters: Vec::new(),
}
}
fn sample_rate(spec: AudioSpec) -> AudioResult<Hertz<f32>> {
Hertz::<f32>::from_hz(spec.sample_rate as f32)
.map_err(|_| AudioError::new("Invalid sample rate for microphone simulation"))
}
fn capped_frequency(spec: AudioSpec, frequency_hz: f32) -> f32 {
frequency_hz.clamp(20.0, spec.sample_rate as f32 * 0.45)
}
fn push_shelf(
filters: &mut Vec<DirectForm1<f32>>,
spec: AudioSpec,
gain_db: f32,
cutoff_hz: f32,
high: bool,
) -> AudioResult<()> {
if gain_db.abs() < 0.01 {
return Ok(());
}
let sample_rate = Self::sample_rate(spec)?;
let cutoff_hz = Self::capped_frequency(spec, cutoff_hz);
let filter_type = if high {
Type::HighShelf(gain_db)
} else {
Type::LowShelf(gain_db)
};
let coeffs = Coefficients::<f32>::from_params(
filter_type,
sample_rate,
cutoff_hz.hz(),
Q_BUTTERWORTH_F32,
)
.map_err(|_| AudioError::new("Invalid microphone simulation shelf configuration"))?;
filters.push(DirectForm1::<f32>::new(coeffs));
Ok(())
}
fn push_peak(
filters: &mut Vec<DirectForm1<f32>>,
spec: AudioSpec,
gain_db: f32,
center_hz: f32,
q: f32,
) -> AudioResult<()> {
if gain_db.abs() < 0.01 {
return Ok(());
}
let sample_rate = Self::sample_rate(spec)?;
let center_hz = Self::capped_frequency(spec, center_hz);
let coeffs = Coefficients::<f32>::from_params(
Type::PeakingEQ(gain_db),
sample_rate,
center_hz.hz(),
q.max(0.2),
)
.map_err(|_| AudioError::new("Invalid microphone simulation peak configuration"))?;
filters.push(DirectForm1::<f32>::new(coeffs));
Ok(())
}
fn push_resonance(
filters: &mut Vec<DirectForm1<f32>>,
spec: AudioSpec,
band: ResonanceBand,
) -> AudioResult<()> {
Self::push_peak(filters, spec, band.gain_db, band.frequency_hz, band.q)
}
}
impl MicrophoneSimProcessor for VoicingContourProcessor {
fn name(&self) -> &'static str {
"voicing_contour"
}
fn prepare(&mut self, spec: AudioSpec) -> AudioResult<()> {
let mut filters = Vec::new();
Self::push_shelf(
&mut filters,
spec,
self.voicing.proximity_gain_db,
self.voicing.proximity_cutoff_hz,
false,
)?;
Self::push_shelf(
&mut filters,
spec,
self.voicing.body_gain_db,
self.voicing.body_cutoff_hz,
false,
)?;
Self::push_peak(
&mut filters,
spec,
self.voicing.low_mid_gain_db,
self.voicing.low_mid_center_hz,
self.voicing.low_mid_q,
)?;
Self::push_peak(
&mut filters,
spec,
self.voicing.presence_gain_db,
self.voicing.presence_center_hz,
self.voicing.presence_q,
)?;
Self::push_shelf(
&mut filters,
spec,
self.voicing.air_gain_db,
self.voicing.air_cutoff_hz,
true,
)?;
for resonance in self.voicing.resonances {
Self::push_resonance(&mut filters, spec, *resonance)?;
}
self.filters = filters;
Ok(())
}
fn process(&mut self, frame: &mut AudioFrame) -> AudioResult<()> {
for filter in &mut self.filters {
for sample in &mut frame.samples {
*sample = filter.run(*sample);
}
}
Ok(())
}
}