zk-audio 0.1.0

Audio processing library for voice recording and enhancement
Documentation
//! Core types for audio processing configuration.
//!
//! This module defines the fundamental types used throughout the library,
//! including audio specifications, profiles, and capture configurations.

use crate::mic_sim::MicrophoneSimConfig;
use serde::Serialize;
use serde::{Deserialize, Serialize as SerdeSerialize};
use std::collections::BTreeMap;
use std::fmt;
use std::path::PathBuf;

/// Audio specification defining sample rate and channel count.
#[derive(Debug, Clone, Copy)]
pub struct AudioSpec {
    /// Sample rate in Hz (e.g., 44100, 48000)
    pub sample_rate: u32,
    /// Number of audio channels (1 = mono, 2 = stereo)
    pub channels: u16,
}

/// A single frame of audio data.
#[derive(Debug, Clone)]
pub struct AudioFrame {
    /// Audio samples as f32 values (typically -1.0 to 1.0)
    pub samples: Vec<f32>,
    #[allow(dead_code)]
    pub spec: AudioSpec,
}

/// Pre-configured audio processing profiles.
///
/// Each profile enables a specific set of processor stages optimized for
/// different recording environments.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AudioProfile {
    /// Light processing for clean, quiet environments (studio, home office)
    #[default]
    VoiceClean,
    /// No processing - raw audio capture
    Raw,
    /// Moderate noise reduction for noisy environments (cafes, open offices)
    VoiceNoisyRoom,
    /// Aggressive HVAC/air conditioning noise suppression
    VoiceHvac,
}

pub const SUPPORTED_AUDIO_PROFILES: [AudioProfile; 4] = [
    AudioProfile::Raw,
    AudioProfile::VoiceClean,
    AudioProfile::VoiceNoisyRoom,
    AudioProfile::VoiceHvac,
];

impl AudioProfile {
    pub fn from_str(value: &str) -> Self {
        match value {
            "raw" => Self::Raw,
            "voice_hvac" => Self::VoiceHvac,
            "voice_noisy_room" => Self::VoiceNoisyRoom,
            _ => Self::VoiceClean,
        }
    }

    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Raw => "raw",
            Self::VoiceClean => "voice_clean",
            Self::VoiceNoisyRoom => "voice_noisy_room",
            Self::VoiceHvac => "voice_hvac",
        }
    }

    pub fn label(&self) -> &'static str {
        match self {
            Self::Raw => "Raw",
            Self::VoiceClean => "Voice Clean",
            Self::VoiceNoisyRoom => "Voice Noisy Room",
            Self::VoiceHvac => "Voice HVAC",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DelayEffectPreset {
    VoiceDoubler,
    Thickener,
    Slapback,
    Bloom,
}

pub const SUPPORTED_DELAY_EFFECT_PRESETS: [DelayEffectPreset; 4] = [
    DelayEffectPreset::VoiceDoubler,
    DelayEffectPreset::Thickener,
    DelayEffectPreset::Slapback,
    DelayEffectPreset::Bloom,
];

impl DelayEffectPreset {
    pub fn from_str(value: &str) -> Self {
        match value {
            "thickener" => Self::Thickener,
            "slapback" => Self::Slapback,
            "bloom" => Self::Bloom,
            _ => Self::VoiceDoubler,
        }
    }

    pub fn as_str(&self) -> &'static str {
        match self {
            Self::VoiceDoubler => "voice_doubler",
            Self::Thickener => "thickener",
            Self::Slapback => "slapback",
            Self::Bloom => "bloom",
        }
    }

    pub fn label(&self) -> &'static str {
        match self {
            Self::VoiceDoubler => "Voice Doubler",
            Self::Thickener => "Thickener",
            Self::Slapback => "Slapback",
            Self::Bloom => "Bloom",
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub struct DelayEffectConfig {
    pub preset: DelayEffectPreset,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, SerdeSerialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum ProcessorOverrideMode {
    #[default]
    Inherit,
    Enabled,
    Disabled,
}

impl ProcessorOverrideMode {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Inherit => "inherit",
            Self::Enabled => "enabled",
            Self::Disabled => "disabled",
        }
    }

    pub fn label(&self) -> &'static str {
        match self {
            Self::Inherit => "Inherit",
            Self::Enabled => "Force On",
            Self::Disabled => "Force Off",
        }
    }
}

#[derive(Debug, Clone, Default)]
pub struct NativeCaptureConfig {
    pub output_path: PathBuf,
    pub target_sample_rate: u32,
    pub target_channels: u16,
    pub preferred_input_device: Option<String>,
    pub profile: AudioProfile,
    pub input_gain_db: f32,
    pub limiter_threshold: f32,
    pub high_pass_hz: f32,
    pub noise_suppression_amount: f32,
    pub noise_calibration_ms: u32,
    pub delay_effect: Option<DelayEffectConfig>,
    pub stage_overrides: BTreeMap<String, ProcessorOverrideMode>,
    pub microphone_sim: MicrophoneSimConfig,
}

impl NativeCaptureConfig {
    pub fn new(output_path: PathBuf, profile: AudioProfile) -> Self {
        Self {
            output_path,
            profile,
            target_sample_rate: 44_100,
            target_channels: 1,
            preferred_input_device: None,
            input_gain_db: 0.0,
            limiter_threshold: 0.95,
            high_pass_hz: 80.0,
            noise_suppression_amount: 0.5,
            noise_calibration_ms: 300,
            delay_effect: None,
            stage_overrides: BTreeMap::new(),
            microphone_sim: MicrophoneSimConfig::default(),
        }
    }
}

#[derive(Debug, Clone)]
pub struct InputDeviceInfo {
    pub id: String,
    pub name: String,
    pub is_default: bool,
}

#[derive(Debug, Clone, Serialize, Default)]
pub struct CaptureDiagnostics {
    pub backend: String,
    pub profile: String,
    pub profile_base: Option<String>,
    pub device_name: Option<String>,
    pub sample_rate: Option<u32>,
    pub channels: Option<u16>,
    pub duration_ms: Option<i64>,
    pub frames_processed: u64,
    pub analyzed_frames: u64,
    pub noise_only_frames: u64,
    pub transitional_frames: u64,
    pub speech_like_frames: u64,
    pub rms_level: f32,
    pub peak_level: f32,
    pub clipping_events: u64,
    pub processor_names: Vec<String>,
    pub processor_stage_overrides: Vec<String>,
    pub resolved_delay_preset: Option<String>,
    pub microphone_sim_model: Option<String>,
    pub microphone_sim_processor_names: Vec<String>,
    pub notes: Vec<String>,
}

#[derive(Debug, Clone)]
pub struct AudioError {
    message: String,
}

impl AudioError {
    pub fn new(message: impl Into<String>) -> Self {
        Self {
            message: message.into(),
        }
    }
}

impl fmt::Display for AudioError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&self.message)
    }
}

impl std::error::Error for AudioError {}

pub type AudioResult<T> = Result<T, AudioError>;

pub trait ActiveRecording: Send {
    fn stop(&mut self) -> AudioResult<CaptureDiagnostics>;
}

pub trait ActiveListening: Send {
    fn stop(&mut self) -> AudioResult<CaptureDiagnostics>;
    fn update_config(&mut self, config: NativeCaptureConfig) -> AudioResult<()>;
}