Skip to main content

max / audiofiles

2.7 KB · 92 lines History Blame Raw
1 //! Channel conversion: mono-to-stereo and stereo-to-mono destructive edits.
2
3 use crate::error::CoreError;
4
5 /// Convert mono interleaved samples to stereo by duplicating each sample.
6 ///
7 /// Returns the new channel count (2). Caller must update the stored channel count.
8 /// Rejects input with more than 1 channel to prevent garbled output.
9 pub fn apply_mono_to_stereo(samples: &mut Vec<f32>, channels: u16) -> Result<u16, CoreError> {
10 if channels != 1 {
11 return Err(CoreError::Internal(format!(
12 "mono_to_stereo: expected 1 channel, got {channels}"
13 )));
14 }
15 let n = samples.len();
16 let mut stereo = Vec::with_capacity(n * 2);
17 for &s in samples.iter() {
18 stereo.push(s);
19 stereo.push(s);
20 }
21 *samples = stereo;
22 Ok(2)
23 }
24
25 /// Convert stereo interleaved samples to mono by averaging L+R per frame.
26 ///
27 /// Returns the new channel count (1). Caller must update the stored channel count.
28 pub fn apply_stereo_to_mono(samples: &mut Vec<f32>, channels: u16) -> Result<u16, CoreError> {
29 if channels < 2 {
30 return Ok(channels); // already mono, no-op
31 }
32 let ch = channels as usize;
33 let num_frames = samples.len() / ch;
34 let mut mono = Vec::with_capacity(num_frames);
35 for frame in 0..num_frames {
36 let mut sum = 0.0f32;
37 for c in 0..ch {
38 sum += samples[frame * ch + c];
39 }
40 mono.push(sum / ch as f32);
41 }
42 *samples = mono;
43 Ok(1)
44 }
45
46 #[cfg(test)]
47 mod tests {
48 use super::*;
49
50 #[test]
51 fn mono_to_stereo() {
52 let mut samples = vec![0.1, 0.2, 0.3];
53 let ch = apply_mono_to_stereo(&mut samples, 1).unwrap();
54 assert_eq!(ch, 2);
55 assert_eq!(samples, vec![0.1, 0.1, 0.2, 0.2, 0.3, 0.3]);
56 }
57
58 #[test]
59 fn stereo_to_mono() {
60 let mut samples = vec![0.4, 0.6, 0.2, 0.8];
61 let ch = apply_stereo_to_mono(&mut samples, 2).unwrap();
62 assert_eq!(ch, 1);
63 assert_eq!(samples.len(), 2);
64 assert!((samples[0] - 0.5).abs() < 1e-6);
65 assert!((samples[1] - 0.5).abs() < 1e-6);
66 }
67
68 #[test]
69 fn mono_to_mono_noop() {
70 let mut samples = vec![0.1, 0.2];
71 let ch = apply_stereo_to_mono(&mut samples, 1).unwrap();
72 assert_eq!(ch, 1);
73 assert_eq!(samples, vec![0.1, 0.2]);
74 }
75
76 #[test]
77 fn roundtrip_preserves_energy() {
78 let mut samples = vec![0.5, -0.5, 0.3, -0.3];
79 let _ = apply_stereo_to_mono(&mut samples, 2).unwrap();
80 // Mono: [0.0, 0.0] — opposing channels cancel
81 assert!((samples[0]).abs() < 1e-6);
82 }
83
84 #[test]
85 fn empty_input() {
86 let mut samples: Vec<f32> = vec![];
87 let ch = apply_mono_to_stereo(&mut samples, 1).unwrap();
88 assert_eq!(ch, 2);
89 assert!(samples.is_empty());
90 }
91 }
92