1use rubato::audioadapter_buffers::direct::SequentialSliceOfVecs;
9use rubato::{
10 Async, FixedAsync, Resampler as RubatoTrait, SincInterpolationParameters, SincInterpolationType, WindowFunction,
11};
12
13use crate::Error;
14
15pub struct Resampler {
17 resampler: Async<f32>,
18 chunk_frames: usize,
19 channels: usize,
20 input_planar: Vec<Vec<f32>>,
21 output_planar: Vec<Vec<f32>>,
22 output_frames_max: usize,
23 pending: Vec<f32>,
24}
25
26impl Resampler {
27 pub fn new(input_rate: u32, output_rate: u32, channels: u32, chunk_frames: usize) -> Result<Self, Error> {
34 if chunk_frames == 0 {
35 return Err(Error::Unsupported("chunk_frames must be > 0".into()));
36 }
37
38 let params = SincInterpolationParameters {
39 sinc_len: 128,
40 f_cutoff: Some(0.95),
41 interpolation: SincInterpolationType::Linear,
42 oversampling_factor: 128,
43 window: WindowFunction::BlackmanHarris2,
44 };
45 let resampler = Async::<f32>::new_sinc(
46 output_rate as f64 / input_rate as f64,
47 1.0,
48 ¶ms,
49 chunk_frames,
50 channels as usize,
51 FixedAsync::Input,
52 )?;
53
54 let input_planar = (0..channels as usize).map(|_| vec![0.0f32; chunk_frames]).collect();
55 let output_frames_max = resampler.output_frames_max();
56 let output_planar = vec![vec![0.0f32; output_frames_max]; channels as usize];
57
58 Ok(Self {
59 resampler,
60 chunk_frames,
61 channels: channels as usize,
62 input_planar,
63 output_planar,
64 output_frames_max,
65 pending: Vec::new(),
66 })
67 }
68
69 pub fn process(&mut self, samples: &[f32]) -> Result<Vec<f32>, Error> {
74 if !samples.len().is_multiple_of(self.channels) {
75 return Err(Error::Misaligned {
76 got: samples.len(),
77 expected: samples.len().next_multiple_of(self.channels),
78 });
79 }
80
81 self.pending.extend_from_slice(samples);
82
83 let chunk_samples = self.chunk_frames * self.channels;
84 let mut out = Vec::new();
85 while self.pending.len() >= chunk_samples {
86 for (frame_idx, frame) in self.pending[..chunk_samples].chunks_exact(self.channels).enumerate() {
87 for (ch, &sample) in frame.iter().enumerate() {
88 self.input_planar[ch][frame_idx] = sample;
89 }
90 }
91
92 let input = SequentialSliceOfVecs::new(&self.input_planar, self.channels, self.chunk_frames)
93 .expect("resampler input buffer dimensions");
94 let mut output =
95 SequentialSliceOfVecs::new_mut(&mut self.output_planar, self.channels, self.output_frames_max)
96 .expect("resampler output buffer dimensions");
97 let (_, produced) = self.resampler.process_into_buffer(&input, &mut output, None)?;
98
99 let prev_len = out.len();
100 out.resize(prev_len + produced * self.channels, 0.0);
101 for frame_idx in 0..produced {
102 for ch in 0..self.channels {
103 out[prev_len + frame_idx * self.channels + ch] = self.output_planar[ch][frame_idx];
104 }
105 }
106
107 self.pending.drain(..chunk_samples);
108 }
109
110 Ok(out)
111 }
112}
113
114pub(crate) fn remix(samples: &[f32], input_channels: u32, output_channels: u32) -> Result<Vec<f32>, Error> {
116 match (input_channels, output_channels) {
117 (1, 1) | (2, 2) => Ok(samples.to_vec()),
118 (1, 2) => {
119 let mut output = Vec::with_capacity(samples.len() * 2);
120 for &sample in samples {
121 output.extend_from_slice(&[sample, sample]);
122 }
123 Ok(output)
124 }
125 (2, 1) => Ok(samples.chunks_exact(2).map(|pair| (pair[0] + pair[1]) * 0.5).collect()),
126 _ => Err(Error::Unsupported(format!(
127 "channel remix only supports mono and stereo (got {input_channels} to {output_channels})"
128 ))),
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn rejects_zero_chunk_frames() {
138 let r = Resampler::new(48_000, 48_000, 2, 0);
139 assert!(matches!(r, Err(Error::Unsupported(_))));
140 }
141
142 #[test]
143 fn upsample_44100_to_48000_preserves_energy_roughly() {
144 let mut r = Resampler::new(44_100, 48_000, 1, 1024).unwrap();
145 let input: Vec<f32> = (0..44_100)
146 .map(|i| (2.0 * std::f32::consts::PI * 440.0 * i as f32 / 44_100.0).sin() * 0.5)
147 .collect();
148 let mut out = r.process(&input).unwrap();
149 out.extend(r.process(&vec![0.0; 1024]).unwrap());
150 assert!(
151 (47_000..50_000).contains(&out.len()),
152 "expected ~48k samples, got {}",
153 out.len()
154 );
155 }
156
157 #[test]
158 fn remix_mono_to_stereo_duplicates_samples() {
159 assert_eq!(remix(&[1.0, 2.0], 1, 2).unwrap(), [1.0, 1.0, 2.0, 2.0]);
160 }
161
162 #[test]
163 fn remix_stereo_to_mono_averages_channels() {
164 assert_eq!(remix(&[1.0, 3.0, 2.0, 4.0], 2, 1).unwrap(), [2.0, 3.0]);
165 }
166}