1use rubato::{
25 audioadapter_buffers::direct::SequentialSlice, Fft, FixedSync,
26 Resampler as RubatoResamplerTrait,
27};
28
29use crate::{ResampleError, Resampler};
30
31const MAX_RATE: f64 = 4_000_000.0;
34
35#[derive(Debug)]
47pub struct RubatoResampler {
48 inner: Fft<f32>,
49 channels: usize,
50 chunk_size: usize,
51 input_rate: f64,
52 output_rate: f64,
53}
54
55impl RubatoResampler {
56 pub fn new(
71 input_rate: f64,
72 output_rate: f64,
73 channels: usize,
74 chunk_size: usize,
75 ) -> Result<Self, ResampleError> {
76 if !is_valid_rate(input_rate) {
77 return Err(ResampleError::InvalidSampleRate { rate: input_rate });
78 }
79 if !is_valid_rate(output_rate) {
80 return Err(ResampleError::InvalidSampleRate { rate: output_rate });
81 }
82 if channels == 0 {
83 return Err(ResampleError::InvalidChannelCount(channels));
84 }
85 if chunk_size == 0 {
86 return Err(ResampleError::InvalidChunkSize(chunk_size));
87 }
88 let rate_in = rate_to_usize(input_rate)
89 .ok_or(ResampleError::InvalidSampleRate { rate: input_rate })?;
90 let rate_out = rate_to_usize(output_rate)
91 .ok_or(ResampleError::InvalidSampleRate { rate: output_rate })?;
92 let inner = Fft::<f32>::new(rate_in, rate_out, chunk_size, channels, FixedSync::Input)
93 .map_err(|e| ResampleError::ResampleFailed(e.to_string()))?;
94 Ok(Self {
95 inner,
96 channels,
97 chunk_size,
98 input_rate,
99 output_rate,
100 })
101 }
102
103 #[must_use]
105 pub fn input_chunk_size(&self) -> usize {
106 self.chunk_size
107 }
108
109 #[must_use]
112 pub fn output_frames_max(&self) -> usize {
113 self.inner.output_frames_max()
114 }
115
116 #[must_use]
118 pub fn channels(&self) -> usize {
119 self.channels
120 }
121
122 #[must_use]
124 pub fn input_rate(&self) -> f64 {
125 self.input_rate
126 }
127
128 #[must_use]
130 pub fn output_rate(&self) -> f64 {
131 self.output_rate
132 }
133
134 #[must_use]
145 pub fn output_delay(&self) -> usize {
146 self.inner.output_delay()
147 }
148}
149
150impl Resampler for RubatoResampler {
151 fn process_into_buffer(
152 &mut self,
153 input: &[f32],
154 output: &mut [f32],
155 ) -> Result<usize, ResampleError> {
156 let expected = self.channels * self.chunk_size;
158 if input.len() != expected {
159 return Err(ResampleError::InputLengthMismatch {
160 expected,
161 actual: input.len(),
162 });
163 }
164 let out_n = self.inner.output_frames_next();
166 let needed = self.channels * out_n;
167 if output.len() < needed {
168 return Err(ResampleError::BufferTooSmall {
169 needed,
170 have: output.len(),
171 });
172 }
173 let input_adapter = SequentialSlice::new(input, self.channels, self.chunk_size)
178 .map_err(|e| ResampleError::ResampleFailed(e.to_string()))?;
179 let mut output_adapter = SequentialSlice::new_mut(output, self.channels, out_n)
180 .map_err(|e| ResampleError::ResampleFailed(e.to_string()))?;
181 let (_frames_read, frames_written) = self
182 .inner
183 .process_into_buffer(&input_adapter, &mut output_adapter, None)
184 .map_err(|e| ResampleError::ResampleFailed(e.to_string()))?;
185 Ok(frames_written)
186 }
187
188 fn set_rate(&mut self, input_rate: f64, output_rate: f64) {
189 if !is_valid_rate(input_rate) || !is_valid_rate(output_rate) {
192 return;
193 }
194 let Some(rate_in) = rate_to_usize(input_rate) else {
195 return;
196 };
197 let Some(rate_out) = rate_to_usize(output_rate) else {
198 return;
199 };
200 let Ok(new_inner) = Fft::<f32>::new(
201 rate_in,
202 rate_out,
203 self.chunk_size,
204 self.channels,
205 FixedSync::Input,
206 ) else {
207 return;
208 };
209 self.inner = new_inner;
210 self.input_rate = input_rate;
211 self.output_rate = output_rate;
212 }
213}
214
215const fn is_valid_rate(rate: f64) -> bool {
217 rate.is_finite() && rate >= 1.0
218}
219
220fn rate_to_usize(rate: f64) -> Option<usize> {
226 if rate > MAX_RATE {
227 return None;
228 }
229 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
231 let value = rate as usize;
232 Some(value)
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 fn approx_eq(a: f64, b: f64) -> bool {
241 (a - b).abs() < 1e-9
242 }
243
244 #[allow(
247 clippy::cast_precision_loss,
248 clippy::cast_possible_truncation,
249 clippy::cast_sign_loss
250 )]
251 fn sine_chunk(freq: f64, rate: f64, frames: usize, phase_offset: usize) -> Vec<f32> {
252 let omega = 2.0_f64 * std::f64::consts::TAU * freq / rate;
253 (0..frames)
254 .map(|i| {
255 let t = (i + phase_offset) as f64;
256 (omega * t).sin() as f32
257 })
258 .collect()
259 }
260
261 #[test]
262 fn new_rejects_zero_sample_rate() {
263 let err = RubatoResampler::new(0.0, 48000.0, 1, 256).unwrap_err();
264 assert_eq!(err, ResampleError::InvalidSampleRate { rate: 0.0 });
265 let err = RubatoResampler::new(44100.0, 0.0, 1, 256).unwrap_err();
266 assert_eq!(err, ResampleError::InvalidSampleRate { rate: 0.0 });
267 }
268
269 #[test]
270 fn new_rejects_non_finite_sample_rate() {
271 let err = RubatoResampler::new(f64::NAN, 48000.0, 1, 256).unwrap_err();
272 assert!(err.to_string().contains("invalid sample rate"));
273 }
274
275 #[test]
276 fn new_rejects_zero_channels() {
277 let err = RubatoResampler::new(44100.0, 48000.0, 0, 256).unwrap_err();
278 assert_eq!(err, ResampleError::InvalidChannelCount(0));
279 }
280
281 #[test]
282 fn new_rejects_zero_chunk_size() {
283 let err = RubatoResampler::new(44100.0, 48000.0, 1, 0).unwrap_err();
284 assert_eq!(err, ResampleError::InvalidChunkSize(0));
285 }
286
287 #[test]
288 fn process_into_buffer_rejects_wrong_input_length() {
289 let mut rs = RubatoResampler::new(44100.0, 48000.0, 1, 256).unwrap();
290 let input = vec![0.0_f32; 128]; let mut output = vec![0.0_f32; rs.output_frames_max()];
292 let err = rs.process_into_buffer(&input, &mut output).unwrap_err();
293 assert_eq!(
294 err,
295 ResampleError::InputLengthMismatch {
296 expected: 256,
297 actual: 128,
298 }
299 );
300 }
301
302 #[test]
303 fn process_into_buffer_rejects_small_output() {
304 let mut rs = RubatoResampler::new(44100.0, 48000.0, 1, 256).unwrap();
305 let input = vec![0.0_f32; 256];
306 for _ in 0..256 {
311 let mut tiny = vec![0.0_f32; 1];
312 match rs.process_into_buffer(&input, &mut tiny) {
313 Ok(_) => {} Err(ResampleError::BufferTooSmall { needed, have }) => {
315 assert_eq!(have, 1);
316 assert!(needed > 1, "out_n must exceed 1");
317 return;
318 }
319 Err(other) => panic!("unexpected error: {other:?}"),
320 }
321 }
322 panic!("BufferTooSmall did not trigger within 256 iterations");
323 }
324
325 #[test]
326 fn process_into_buffer_writes_something_for_sine() {
327 let chunk = 1024;
331 let mut rs = RubatoResampler::new(44100.0, 48000.0, 1, chunk).unwrap();
332 let mut max_abs = 0.0_f32;
333 for blk in 0..16_usize {
334 let input = sine_chunk(1000.0, 44100.0, chunk, blk * chunk);
335 let mut output = vec![0.0_f32; rs.output_frames_max()];
336 let written = rs.process_into_buffer(&input, &mut output).unwrap();
337 for s in &output[..written] {
338 let abs = s.abs();
339 if abs > max_abs {
340 max_abs = abs;
341 }
342 }
343 }
344 assert!(
345 max_abs > 0.0,
346 "output must not be all zero (max_abs={max_abs})"
347 );
348 }
349
350 #[test]
351 fn set_rate_changes_output_rate() {
352 let mut rs = RubatoResampler::new(44100.0, 48000.0, 1, 256).unwrap();
353 rs.set_rate(48000.0, 96000.0);
354 assert!(approx_eq(rs.input_rate(), 48000.0));
355 assert!(approx_eq(rs.output_rate(), 96000.0));
356 let input = sine_chunk(1000.0, 48000.0, 256, 0);
358 let mut output = vec![0.0_f32; rs.output_frames_max()];
359 let written = rs.process_into_buffer(&input, &mut output).unwrap();
360 assert!(written > 0);
361 }
362
363 #[test]
364 fn set_rate_ignores_invalid_rate() {
365 let mut rs = RubatoResampler::new(44100.0, 48000.0, 1, 256).unwrap();
366 rs.set_rate(0.0, 96000.0);
367 assert!(approx_eq(rs.input_rate(), 44100.0));
368 assert!(approx_eq(rs.output_rate(), 48000.0));
369 rs.set_rate(-1.0, 96000.0);
370 assert!(approx_eq(rs.input_rate(), 44100.0));
371 }
372
373 #[test]
374 fn stereo_two_channels_processes_both() {
375 let chunk = 512;
376 let ch = 2;
377 let mut rs = RubatoResampler::new(44100.0, 48000.0, ch, chunk).unwrap();
378 let mut max0 = 0.0_f32;
380 let mut max1 = 0.0_f32;
381 for blk in 0..16_usize {
382 let c0 = sine_chunk(1000.0, 44100.0, chunk, blk * chunk);
383 let c1 = sine_chunk(2000.0, 44100.0, chunk, blk * chunk);
384 let mut input = Vec::with_capacity(ch * chunk);
385 input.extend_from_slice(&c0);
386 input.extend_from_slice(&c1);
387 let mut output = vec![0.0_f32; ch * rs.output_frames_max()];
388 let written = rs.process_into_buffer(&input, &mut output).unwrap();
389 for s in &output[..written] {
391 let abs = s.abs();
392 if abs > max0 {
393 max0 = abs;
394 }
395 }
396 for s in &output[written..2 * written] {
397 let abs = s.abs();
398 if abs > max1 {
399 max1 = abs;
400 }
401 }
402 }
403 assert!(max0 > 0.0, "channel 0 processed (max0={max0})");
404 assert!(max1 > 0.0, "channel 1 processed (max1={max1})");
405 }
406
407 #[test]
408 fn accessors_return_construction_values() {
409 let rs = RubatoResampler::new(44100.0, 48000.0, 2, 256).unwrap();
410 assert_eq!(rs.channels(), 2);
411 assert_eq!(rs.input_chunk_size(), 256);
412 assert!(approx_eq(rs.input_rate(), 44100.0));
413 assert!(approx_eq(rs.output_rate(), 48000.0));
414 assert!(rs.output_frames_max() > 0);
416 }
417}