zk-audio 0.1.0

Audio processing library for voice recording and enhancement
Documentation
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(())
    }
}