//! Channel conversion: mono-to-stereo and stereo-to-mono destructive edits. use crate::error::CoreError; /// Convert mono interleaved samples to stereo by duplicating each sample. /// /// Returns the new channel count (2). Caller must update the stored channel count. /// Rejects input with more than 1 channel to prevent garbled output. pub fn apply_mono_to_stereo(samples: &mut Vec, channels: u16) -> Result { if channels != 1 { return Err(CoreError::Internal(format!( "mono_to_stereo: expected 1 channel, got {channels}" ))); } let n = samples.len(); let mut stereo = Vec::with_capacity(n * 2); for &s in samples.iter() { stereo.push(s); stereo.push(s); } *samples = stereo; Ok(2) } /// Convert stereo interleaved samples to mono by averaging L+R per frame. /// /// Returns the new channel count (1). Caller must update the stored channel count. pub fn apply_stereo_to_mono(samples: &mut Vec, channels: u16) -> Result { if channels < 2 { return Ok(channels); // already mono, no-op } let ch = channels as usize; let num_frames = samples.len() / ch; let mut mono = Vec::with_capacity(num_frames); for frame in 0..num_frames { let mut sum = 0.0f32; for c in 0..ch { sum += samples[frame * ch + c]; } mono.push(sum / ch as f32); } *samples = mono; Ok(1) } #[cfg(test)] mod tests { use super::*; #[test] fn mono_to_stereo() { let mut samples = vec![0.1, 0.2, 0.3]; let ch = apply_mono_to_stereo(&mut samples, 1).unwrap(); assert_eq!(ch, 2); assert_eq!(samples, vec![0.1, 0.1, 0.2, 0.2, 0.3, 0.3]); } #[test] fn stereo_to_mono() { let mut samples = vec![0.4, 0.6, 0.2, 0.8]; let ch = apply_stereo_to_mono(&mut samples, 2).unwrap(); assert_eq!(ch, 1); assert_eq!(samples.len(), 2); assert!((samples[0] - 0.5).abs() < 1e-6); assert!((samples[1] - 0.5).abs() < 1e-6); } #[test] fn mono_to_mono_noop() { let mut samples = vec![0.1, 0.2]; let ch = apply_stereo_to_mono(&mut samples, 1).unwrap(); assert_eq!(ch, 1); assert_eq!(samples, vec![0.1, 0.2]); } #[test] fn roundtrip_preserves_energy() { let mut samples = vec![0.5, -0.5, 0.3, -0.3]; let _ = apply_stereo_to_mono(&mut samples, 2).unwrap(); // Mono: [0.0, 0.0] — opposing channels cancel assert!((samples[0]).abs() < 1e-6); } #[test] fn empty_input() { let mut samples: Vec = vec![]; let ch = apply_mono_to_stereo(&mut samples, 1).unwrap(); assert_eq!(ch, 2); assert!(samples.is_empty()); } }