Skip to main content

audio_io/
resample.rs

1use audioadapter_buffers::direct::InterleavedSlice;
2use num::Float;
3use rubato::Fft;
4use rubato::Resampler as _;
5use thiserror::Error;
6
7#[derive(Debug, Error)]
8pub enum ResampleError {
9    #[error("could not create resampler")]
10    ResamplerConstructionError(#[from] rubato::ResamplerConstructionError),
11    #[error("could not resample audio")]
12    ResampleError(#[from] rubato::ResampleError),
13}
14
15pub fn resample<F: Float + rubato::Sample>(
16    audio_interleaved: &[F],
17    num_channels: usize,
18    sr_in: u32,
19    sr_out: u32,
20) -> Result<Vec<F>, ResampleError> {
21    let mut resampler = Fft::new(
22        sr_in as usize,
23        sr_out as usize,
24        1024,
25        2,
26        num_channels,
27        rubato::FixedSync::Both,
28    )?;
29
30    let num_input_frames = audio_interleaved.len() / num_channels;
31    let buffer_in = InterleavedSlice::new(audio_interleaved, num_channels, num_input_frames)
32        .expect("Should be the right size");
33
34    let num_output_frames = resampler.process_all_needed_output_len(num_input_frames);
35    let mut out_slice = vec![F::zero(); num_output_frames * num_channels];
36    let mut buffer_out = InterleavedSlice::new_mut(&mut out_slice, num_channels, num_output_frames)
37        .expect("should be the right size");
38
39    // process_all_into_buffer returns (input_frames_used, output_frames_written)
40    // It already trims the resampler delay internally
41    let (_, actual_output_frames) =
42        resampler.process_all_into_buffer(&buffer_in, &mut buffer_out, num_input_frames, None)?;
43
44    // Truncate to actual output length
45    out_slice.truncate(actual_output_frames * num_channels);
46    Ok(out_slice)
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn test_resample_preserves_frequency() {
55        use crate::reader::{AudioReadConfig, audio_read};
56        use audio_blocks::{AudioBlock, AudioBlockInterleavedView};
57
58        // Read the test file
59        let audio =
60            audio_read::<f32>("test_data/test_4ch.wav", AudioReadConfig::default()).unwrap();
61
62        assert_eq!(audio.sample_rate, 48000);
63        assert_eq!(audio.num_channels, 4);
64
65        // Resample from 48000 Hz to 22050 Hz
66        let sr_out = 22050u32;
67        let resampled = resample(
68            &audio.samples_interleaved,
69            audio.num_channels as usize,
70            audio.sample_rate,
71            sr_out,
72        )
73        .unwrap();
74
75        let block = AudioBlockInterleavedView::from_slice(&resampled, audio.num_channels);
76
77        // Expected frames after resampling: 48000 * (22050/48000) = 22050
78        let expected_frames = 22050usize;
79        assert_eq!(
80            block.num_frames(),
81            expected_frames,
82            "Expected {} frames, got {}",
83            expected_frames,
84            block.num_frames()
85        );
86
87        // Verify sine wave frequencies are preserved after resampling
88        // Original frequencies: [440, 554.37, 659.25, 880] Hz
89        const FREQUENCIES: [f64; 4] = [440.0, 554.37, 659.25, 880.0];
90
91        // Skip first ~100 samples to avoid any edge effects from resampling
92        let start_frame = 100;
93        let test_frames = 1000;
94
95        for (ch, &freq) in FREQUENCIES.iter().enumerate() {
96            let mut max_error: f32 = 0.0;
97            for frame in start_frame..(start_frame + test_frames) {
98                let expected =
99                    (2.0 * std::f64::consts::PI * freq * frame as f64 / sr_out as f64).sin() as f32;
100                let actual = block.sample(ch as u16, frame);
101                let error = (actual - expected).abs();
102                max_error = max_error.max(error);
103            }
104            assert!(
105                max_error < 0.02,
106                "Channel {} ({}Hz): max error {} exceeds threshold",
107                ch,
108                freq,
109                max_error
110            );
111        }
112    }
113}