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