//! Canonical P1 voice frame helpers. //! //! The network contract remains 48 kHz mono, 20 ms Opus frames. P1 //! processing works internally on 10 ms f32 frames so VAD and future //! processors can share a stable frame size without changing the //! transport layer. /// P1 sample rate in Hz. pub const SAMPLE_RATE_HZ: u32 = 48_000; /// Network frame duration in milliseconds. pub const NETWORK_FRAME_MS: u32 = 20; /// Processing frame duration in milliseconds. pub const PROCESSING_FRAME_MS: u32 = 10; /// Samples in one 10 ms mono frame at 48 kHz. pub const FRAME_10MS_SAMPLES: usize = 480; /// Samples in one 20 ms mono frame at 48 kHz. pub const FRAME_20MS_SAMPLES: usize = 960; /// Convert i16 PCM to normalized f32 PCM. pub fn i16_to_f32(sample: i16) -> f32 { sample as f32 / i16::MAX as f32 } /// Convert normalized f32 PCM to saturated i16 PCM. pub fn f32_to_i16(sample: f32) -> i16 { (sample.clamp(-1.0, 1.0) * i16::MAX as f32) as i16 } /// RMS dBFS for a normalized f32 slice. Silence returns `-120.0`. pub fn dbfs(samples: &[f32]) -> f32 { if samples.is_empty() { return -120.0; } let sum = samples.iter().map(|s| s * s).sum::(); let rms = (sum / samples.len() as f32).sqrt(); if rms <= 0.000_001 { -120.0 } else { 20.0 * rms.log10() } }