Skip to main content

wavekat_vad/preprocessing/
resample.rs

1//! Audio resampling utilities.
2//!
3//! FFT-based sample rate conversion for audio preprocessing.
4
5use rubato::{FftFixedIn, Resampler};
6
7/// Resampler for converting between sample rates.
8///
9/// Uses FFT-based resampling for high quality.
10pub struct AudioResampler {
11    resampler: FftFixedIn<f64>,
12    input_buffer: Vec<f64>,
13    output_buffer: Vec<i16>,
14}
15
16impl AudioResampler {
17    /// Create a new resampler.
18    ///
19    /// # Arguments
20    /// * `source_rate` - Input sample rate in Hz
21    /// * `target_rate` - Output sample rate in Hz
22    ///
23    /// # Errors
24    /// Returns an error if the resampler cannot be created.
25    pub fn new(source_rate: u32, target_rate: u32) -> Result<Self, String> {
26        let chunk_size = 1024;
27        let resampler = FftFixedIn::<f64>::new(
28            source_rate as usize,
29            target_rate as usize,
30            chunk_size,
31            2, // sub_chunks for lower latency
32            1, // mono
33        )
34        .map_err(|e| format!("failed to create resampler: {e}"))?;
35
36        Ok(Self {
37            resampler,
38            input_buffer: Vec::new(),
39            output_buffer: Vec::new(),
40        })
41    }
42
43    /// Process audio samples and return resampled output.
44    ///
45    /// Due to buffering, output length may differ from input length.
46    /// Call `flush` at the end of a stream to get remaining samples.
47    pub fn process(&mut self, samples: &[i16]) -> Vec<i16> {
48        // Convert input samples to f64 and add to buffer
49        self.input_buffer
50            .extend(samples.iter().map(|&s| s as f64 / i16::MAX as f64));
51
52        // Process complete chunks through the resampler
53        let input_frames_needed = self.resampler.input_frames_next();
54        while self.input_buffer.len() >= input_frames_needed {
55            let chunk: Vec<f64> = self.input_buffer.drain(..input_frames_needed).collect();
56            match self.resampler.process(&[chunk], None) {
57                Ok(output) => {
58                    if !output.is_empty() {
59                        self.output_buffer
60                            .extend(output[0].iter().map(|&s| (s * i16::MAX as f64) as i16));
61                    }
62                }
63                Err(e) => {
64                    // Log error but don't fail - return what we have
65                    eprintln!("resampling error: {e}");
66                }
67            }
68        }
69
70        // Return all available output
71        std::mem::take(&mut self.output_buffer)
72    }
73
74    /// Reset the resampler state.
75    pub fn reset(&mut self) {
76        self.input_buffer.clear();
77        self.output_buffer.clear();
78        // Note: FftFixedIn doesn't have a reset method, but clearing buffers is sufficient
79        // since it's stateless beyond the internal FFT buffers which get overwritten
80    }
81
82    /// Returns the number of input samples currently buffered.
83    pub fn buffered_input(&self) -> usize {
84        self.input_buffer.len()
85    }
86
87    /// Returns the number of output samples ready to be retrieved.
88    pub fn buffered_output(&self) -> usize {
89        self.output_buffer.len()
90    }
91}
92
93impl std::fmt::Debug for AudioResampler {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        f.debug_struct("AudioResampler")
96            .field("input_buffer_len", &self.input_buffer.len())
97            .field("output_buffer_len", &self.output_buffer.len())
98            .finish_non_exhaustive()
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn test_resampler_creation() {
108        let resampler = AudioResampler::new(16000, 48000);
109        assert!(resampler.is_ok());
110    }
111
112    #[test]
113    fn test_resample_16k_to_48k() {
114        let mut resampler = AudioResampler::new(16000, 48000).unwrap();
115
116        // Process enough samples to get output (need to fill internal buffer)
117        let input: Vec<i16> = vec![1000; 2048];
118        let output = resampler.process(&input);
119
120        // Output should be roughly 3x the input (48000/16000 = 3)
121        // Due to buffering, we may not get exactly 3x, but should be close
122        assert!(!output.is_empty(), "Should have some output");
123    }
124
125    #[test]
126    fn test_resample_48k_to_16k() {
127        let mut resampler = AudioResampler::new(48000, 16000).unwrap();
128
129        // Process enough samples
130        let input: Vec<i16> = vec![1000; 2048];
131        let output = resampler.process(&input);
132
133        // Output should be roughly 1/3 the input
134        assert!(!output.is_empty(), "Should have some output");
135    }
136
137    #[test]
138    fn test_resampler_reset() {
139        let mut resampler = AudioResampler::new(16000, 48000).unwrap();
140
141        // Buffer some samples (not enough for output)
142        let input: Vec<i16> = vec![1000; 100];
143        let _ = resampler.process(&input);
144        assert!(resampler.buffered_input() > 0);
145
146        // Reset
147        resampler.reset();
148        assert_eq!(resampler.buffered_input(), 0);
149        assert_eq!(resampler.buffered_output(), 0);
150    }
151}