Skip to main content

sound_to_patch/audio/
resample.rs

1//! Resampling of a mono signal for a consumer with a fixed input rate.
2
3use std::error::Error;
4use std::fmt;
5
6use rubato::audioadapter_buffers::direct::SequentialSlice;
7use rubato::{Fft, FixedSync, Resampler};
8
9use super::SampleRate;
10
11/// Frames per call inside the resampler. Only speed depends on it.
12const CHUNK: usize = 1024;
13
14/// Resamples a mono signal from one rate to another in `f64`.
15///
16/// The synchronous FFT resampler of rubato does the work: its band-limited
17/// conversion keeps the passband and removes everything above the new
18/// Nyquist frequency. The output has `ceil(len * to / from)` samples, with
19/// the resampler's whole-sample delay trimmed, so sample `n` of the output
20/// sits at the time of sample `n * from / to` of the input. Equal rates
21/// return a copy.
22///
23/// rubato leaves a fractional delay that this trim cannot remove: for some
24/// rate pairs, among them 16, 32, 48, 96 and 192 kHz to 22050 Hz and 48 to
25/// 8 kHz, the output lags by half an output sample, 23 us at 22050 Hz.
26/// Pairs such as 44.1 to 22.05 kHz, 48 to 44.1 kHz and 22.05 to 48 kHz
27/// have no lag. Half a sample is far below any time this program reports.
28///
29/// Resampling to a lower rate removes the band above the new Nyquist
30/// frequency. Never feed such output to a spectral descriptor.
31///
32/// # Errors
33///
34/// Returns a [`ResampleError`] when a sample is NaN or infinite, which
35/// would make rubato panic, and when rubato rejects the rates or fails.
36/// Equal rates copy the samples unchecked.
37pub fn resample(
38    samples: &[f64],
39    from: SampleRate,
40    to: SampleRate,
41) -> Result<Vec<f64>, ResampleError> {
42    if from == to || samples.is_empty() {
43        return Ok(samples.to_vec());
44    }
45    let fail = |message: String| ResampleError { from, to, message };
46    if let Some((index, value)) = samples.iter().enumerate().find(|(_, x)| !x.is_finite()) {
47        return Err(fail(format!("sample {index} is not finite ({value})")));
48    }
49    let rate = |rate: SampleRate| usize::try_from(rate.get()).map_err(|e| fail(e.to_string()));
50    let mut resampler = Fft::<f64>::new(rate(from)?, rate(to)?, CHUNK, 1, FixedSync::Input)
51        .map_err(|e| fail(e.to_string()))?;
52    let input = SequentialSlice::new(samples, 1, samples.len()).map_err(|e| fail(e.to_string()))?;
53    let output = resampler
54        .process_all(&input, samples.len(), None)
55        .map_err(|e| fail(e.to_string()))?;
56    let mut output = output.take_data();
57    // rubato can return one sample more when upsampling.
58    let len = (u64::try_from(samples.len()).map_err(|e| fail(e.to_string()))?
59        * u64::from(to.get()))
60    .div_ceil(u64::from(from.get()));
61    output.truncate(usize::try_from(len).map_err(|e| fail(e.to_string()))?);
62    Ok(output)
63}
64
65/// A resampling that failed: both rates and rubato's message.
66#[derive(Clone, Debug, PartialEq, Eq)]
67pub struct ResampleError {
68    from: SampleRate,
69    to: SampleRate,
70    message: String,
71}
72
73impl fmt::Display for ResampleError {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        write!(
76            f,
77            "cannot resample from {} to {}: {}",
78            self.from, self.to, self.message
79        )
80    }
81}
82
83impl Error for ResampleError {}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    fn rate(hz: u32) -> SampleRate {
90        SampleRate::new(hz).unwrap()
91    }
92
93    #[test]
94    fn output_length_follows_the_ratio() {
95        let input = vec![0.0; 48_000];
96        let output = resample(&input, rate(48_000), rate(22_050)).unwrap();
97        assert_eq!(output.len(), 22_050);
98        let output = resample(&input[..1000], rate(48_000), rate(22_050)).unwrap();
99        // ceil(1000 * 22050 / 48000) = ceil(459.375)
100        assert_eq!(output.len(), 460);
101    }
102
103    #[test]
104    fn upsampling_keeps_the_ceiling_length() {
105        let input = vec![0.0; 44_100];
106        let output = resample(&input, rate(22_050), rate(48_000)).unwrap();
107        assert_eq!(output.len(), 96_000);
108    }
109
110    /// The lag of the output in output samples, from the phase of a 100 Hz
111    /// sine over the middle half.
112    fn lag(from: u32, to: u32) -> f64 {
113        let tone = |n: usize, fs: u32| {
114            std::f64::consts::TAU * 100.0 * f64::from(u32::try_from(n).unwrap()) / f64::from(fs)
115        };
116        let x: Vec<f64> = (0..2 * usize::try_from(from).unwrap())
117            .map(|n| tone(n, from).sin())
118            .collect();
119        let y = resample(&x, rate(from), rate(to)).unwrap();
120        let (mut s, mut c) = (0.0, 0.0);
121        for (n, value) in y.iter().enumerate().take(y.len() * 3 / 4).skip(y.len() / 4) {
122            s += value * tone(n, to).sin();
123            c += value * tone(n, to).cos();
124        }
125        -c.atan2(s) / std::f64::consts::TAU / 100.0 * f64::from(to)
126    }
127
128    #[test]
129    fn some_rate_pairs_lag_by_half_an_output_sample() {
130        for from in [16_000, 32_000, 48_000, 96_000, 192_000] {
131            assert!((lag(from, 22_050) - 0.5).abs() < 1e-3, "{from}");
132        }
133        for (from, to) in [(44_100, 22_050), (48_000, 44_100), (22_050, 48_000)] {
134            assert!(lag(from, to).abs() < 1e-3, "{from} {to}");
135        }
136    }
137
138    #[test]
139    fn a_non_finite_sample_is_an_error_at_any_rate_pair() {
140        for bad in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
141            let mut input = vec![0.1; 48_000];
142            input[5] = bad;
143            for (from, to) in [(48_000, 22_050), (8_000, 22_050), (22_050, 48_000)] {
144                let error = resample(&input, rate(from), rate(to)).unwrap_err();
145                assert_eq!(
146                    error.to_string(),
147                    format!(
148                        "cannot resample from {from} Hz to {to} Hz: sample 5 is not finite ({bad})"
149                    )
150                );
151            }
152        }
153    }
154
155    #[test]
156    fn equal_rates_and_empty_input_return_a_copy() {
157        let input = vec![0.25, -0.5];
158        assert_eq!(resample(&input, rate(48_000), rate(48_000)).unwrap(), input);
159        assert_eq!(
160            resample(&[], rate(48_000), rate(22_050)).unwrap(),
161            Vec::<f64>::new()
162        );
163    }
164}