Skip to main content

zk_audio/
core.rs

1//! Core types for audio processing configuration.
2//!
3//! This module defines the fundamental types used throughout the library,
4//! including audio specifications, profiles, and capture configurations.
5
6use crate::mic_sim::MicrophoneSimConfig;
7use serde::Serialize;
8use serde::{Deserialize, Serialize as SerdeSerialize};
9use std::collections::BTreeMap;
10use std::fmt;
11use std::path::PathBuf;
12
13/// Audio specification defining sample rate and channel count.
14#[derive(Debug, Clone, Copy)]
15pub struct AudioSpec {
16    /// Sample rate in Hz (e.g., 44100, 48000)
17    pub sample_rate: u32,
18    /// Number of audio channels (1 = mono, 2 = stereo)
19    pub channels: u16,
20}
21
22/// A single frame of audio data.
23#[derive(Debug, Clone)]
24pub struct AudioFrame {
25    /// Audio samples as f32 values (typically -1.0 to 1.0)
26    pub samples: Vec<f32>,
27    #[allow(dead_code)]
28    pub spec: AudioSpec,
29}
30
31/// Pre-configured audio processing profiles.
32///
33/// Each profile enables a specific set of processor stages optimized for
34/// different recording environments.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
36pub enum AudioProfile {
37    /// Light processing for clean, quiet environments (studio, home office)
38    #[default]
39    VoiceClean,
40    /// No processing - raw audio capture
41    Raw,
42    /// Moderate noise reduction for noisy environments (cafes, open offices)
43    VoiceNoisyRoom,
44    /// Aggressive HVAC/air conditioning noise suppression
45    VoiceHvac,
46}
47
48pub const SUPPORTED_AUDIO_PROFILES: [AudioProfile; 4] = [
49    AudioProfile::Raw,
50    AudioProfile::VoiceClean,
51    AudioProfile::VoiceNoisyRoom,
52    AudioProfile::VoiceHvac,
53];
54
55impl AudioProfile {
56    pub fn from_str(value: &str) -> Self {
57        match value {
58            "raw" => Self::Raw,
59            "voice_hvac" => Self::VoiceHvac,
60            "voice_noisy_room" => Self::VoiceNoisyRoom,
61            _ => Self::VoiceClean,
62        }
63    }
64
65    pub fn as_str(&self) -> &'static str {
66        match self {
67            Self::Raw => "raw",
68            Self::VoiceClean => "voice_clean",
69            Self::VoiceNoisyRoom => "voice_noisy_room",
70            Self::VoiceHvac => "voice_hvac",
71        }
72    }
73
74    pub fn label(&self) -> &'static str {
75        match self {
76            Self::Raw => "Raw",
77            Self::VoiceClean => "Voice Clean",
78            Self::VoiceNoisyRoom => "Voice Noisy Room",
79            Self::VoiceHvac => "Voice HVAC",
80        }
81    }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum DelayEffectPreset {
86    VoiceDoubler,
87    Thickener,
88    Slapback,
89    Bloom,
90}
91
92pub const SUPPORTED_DELAY_EFFECT_PRESETS: [DelayEffectPreset; 4] = [
93    DelayEffectPreset::VoiceDoubler,
94    DelayEffectPreset::Thickener,
95    DelayEffectPreset::Slapback,
96    DelayEffectPreset::Bloom,
97];
98
99impl DelayEffectPreset {
100    pub fn from_str(value: &str) -> Self {
101        match value {
102            "thickener" => Self::Thickener,
103            "slapback" => Self::Slapback,
104            "bloom" => Self::Bloom,
105            _ => Self::VoiceDoubler,
106        }
107    }
108
109    pub fn as_str(&self) -> &'static str {
110        match self {
111            Self::VoiceDoubler => "voice_doubler",
112            Self::Thickener => "thickener",
113            Self::Slapback => "slapback",
114            Self::Bloom => "bloom",
115        }
116    }
117
118    pub fn label(&self) -> &'static str {
119        match self {
120            Self::VoiceDoubler => "Voice Doubler",
121            Self::Thickener => "Thickener",
122            Self::Slapback => "Slapback",
123            Self::Bloom => "Bloom",
124        }
125    }
126}
127
128#[derive(Debug, Clone, Copy)]
129pub struct DelayEffectConfig {
130    pub preset: DelayEffectPreset,
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, SerdeSerialize, Default)]
134#[serde(rename_all = "snake_case")]
135pub enum ProcessorOverrideMode {
136    #[default]
137    Inherit,
138    Enabled,
139    Disabled,
140}
141
142impl ProcessorOverrideMode {
143    pub fn as_str(&self) -> &'static str {
144        match self {
145            Self::Inherit => "inherit",
146            Self::Enabled => "enabled",
147            Self::Disabled => "disabled",
148        }
149    }
150
151    pub fn label(&self) -> &'static str {
152        match self {
153            Self::Inherit => "Inherit",
154            Self::Enabled => "Force On",
155            Self::Disabled => "Force Off",
156        }
157    }
158}
159
160#[derive(Debug, Clone, Default)]
161pub struct NativeCaptureConfig {
162    pub output_path: PathBuf,
163    pub target_sample_rate: u32,
164    pub target_channels: u16,
165    pub preferred_input_device: Option<String>,
166    pub profile: AudioProfile,
167    pub input_gain_db: f32,
168    pub limiter_threshold: f32,
169    pub high_pass_hz: f32,
170    pub noise_suppression_amount: f32,
171    pub noise_calibration_ms: u32,
172    pub delay_effect: Option<DelayEffectConfig>,
173    pub stage_overrides: BTreeMap<String, ProcessorOverrideMode>,
174    pub microphone_sim: MicrophoneSimConfig,
175}
176
177impl NativeCaptureConfig {
178    pub fn new(output_path: PathBuf, profile: AudioProfile) -> Self {
179        Self {
180            output_path,
181            profile,
182            target_sample_rate: 44_100,
183            target_channels: 1,
184            preferred_input_device: None,
185            input_gain_db: 0.0,
186            limiter_threshold: 0.95,
187            high_pass_hz: 80.0,
188            noise_suppression_amount: 0.5,
189            noise_calibration_ms: 300,
190            delay_effect: None,
191            stage_overrides: BTreeMap::new(),
192            microphone_sim: MicrophoneSimConfig::default(),
193        }
194    }
195}
196
197#[derive(Debug, Clone)]
198pub struct InputDeviceInfo {
199    pub id: String,
200    pub name: String,
201    pub is_default: bool,
202}
203
204#[derive(Debug, Clone, Serialize, Default)]
205pub struct CaptureDiagnostics {
206    pub backend: String,
207    pub profile: String,
208    pub profile_base: Option<String>,
209    pub device_name: Option<String>,
210    pub sample_rate: Option<u32>,
211    pub channels: Option<u16>,
212    pub duration_ms: Option<i64>,
213    pub frames_processed: u64,
214    pub analyzed_frames: u64,
215    pub noise_only_frames: u64,
216    pub transitional_frames: u64,
217    pub speech_like_frames: u64,
218    pub rms_level: f32,
219    pub peak_level: f32,
220    pub clipping_events: u64,
221    pub processor_names: Vec<String>,
222    pub processor_stage_overrides: Vec<String>,
223    pub resolved_delay_preset: Option<String>,
224    pub microphone_sim_model: Option<String>,
225    pub microphone_sim_processor_names: Vec<String>,
226    pub notes: Vec<String>,
227}
228
229#[derive(Debug, Clone)]
230pub struct AudioError {
231    message: String,
232}
233
234impl AudioError {
235    pub fn new(message: impl Into<String>) -> Self {
236        Self {
237            message: message.into(),
238        }
239    }
240}
241
242impl fmt::Display for AudioError {
243    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244        f.write_str(&self.message)
245    }
246}
247
248impl std::error::Error for AudioError {}
249
250pub type AudioResult<T> = Result<T, AudioError>;
251
252pub trait ActiveRecording: Send {
253    fn stop(&mut self) -> AudioResult<CaptureDiagnostics>;
254}
255
256pub trait ActiveListening: Send {
257    fn stop(&mut self) -> AudioResult<CaptureDiagnostics>;
258    fn update_config(&mut self, config: NativeCaptureConfig) -> AudioResult<()>;
259}