zk-audio 0.1.0

Audio processing library for voice recording and enhancement
Documentation
use crate::contracts::{AudioProcessor, ProcessorBuildRequest, ProcessorFactory};
use crate::core::AudioResult;
use crate::profiles::build_processors;

pub struct ProfileProcessorFactory;

impl ProfileProcessorFactory {
    pub fn new() -> Self {
        Self
    }
}

impl Default for ProfileProcessorFactory {
    fn default() -> Self {
        Self::new()
    }
}

impl ProcessorFactory for ProfileProcessorFactory {
    fn build_processors(
        &self,
        request: ProcessorBuildRequest,
    ) -> AudioResult<Vec<Box<dyn AudioProcessor>>> {
        build_processors(
            request.profile,
            request.gain_db,
            request.limiter_threshold,
            request.high_pass_hz,
            request.noise_suppression_amount,
            request.noise_calibration_ms,
            request.delay_effect,
            &request.stage_overrides,
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::core::{AudioProfile, DelayEffectConfig, DelayEffectPreset};
    use std::collections::BTreeMap;

    #[test]
    fn factory_builds_expected_hvac_chain() {
        let factory = ProfileProcessorFactory::new();
        let processors = factory
            .build_processors(ProcessorBuildRequest {
                profile: AudioProfile::VoiceHvac,
                gain_db: 2.0,
                limiter_threshold: 0.92,
                high_pass_hz: 100.0,
                noise_suppression_amount: 0.5,
                noise_calibration_ms: 350,
                delay_effect: None,
                stage_overrides: BTreeMap::new(),
            })
            .unwrap();

        let names = processors.iter().map(|p| p.name()).collect::<Vec<_>>();
        assert_eq!(
            names,
            vec![
                "high_pass",
                "low_shelf_cut",
                "low_pass",
                "expander",
                "noise_suppression",
                "adaptive_gain",
                "compressor",
                "agc",
                "adaptive_tone_shaper",
                "mid_resonance_suppressor",
                "high_band_cleanup",
                "presence_boost",
                "deesser",
                "tail_suppression",
                "air_band_reducer",
                "voice_finisher",
                "limiter"
            ]
        );
    }

    #[test]
    fn factory_builds_raw_profile_without_processors() {
        let factory = ProfileProcessorFactory::new();
        let processors = factory
            .build_processors(ProcessorBuildRequest {
                profile: AudioProfile::Raw,
                gain_db: 0.0,
                limiter_threshold: 0.92,
                high_pass_hz: 0.0,
                noise_suppression_amount: 0.0,
                noise_calibration_ms: 0,
                delay_effect: None,
                stage_overrides: BTreeMap::new(),
            })
            .unwrap();

        assert!(processors.is_empty());
    }

    #[test]
    fn factory_builds_delay_stage_before_limiter_when_enabled() {
        let factory = ProfileProcessorFactory::new();
        let processors = factory
            .build_processors(ProcessorBuildRequest {
                profile: AudioProfile::VoiceClean,
                gain_db: 2.0,
                limiter_threshold: 0.92,
                high_pass_hz: 100.0,
                noise_suppression_amount: 0.5,
                noise_calibration_ms: 350,
                delay_effect: Some(DelayEffectConfig {
                    preset: DelayEffectPreset::Slapback,
                }),
                stage_overrides: BTreeMap::new(),
            })
            .unwrap();

        let names = processors.iter().map(|p| p.name()).collect::<Vec<_>>();
        assert_eq!(names[names.len() - 3], "doubling_delay");
        assert_eq!(names[names.len() - 2], "voice_finisher");
        assert_eq!(names[names.len() - 1], "limiter");
    }
}