pub struct AudioResampler {
phase: f32,
rate: f32,
target_fill: usize,
current: [f32; 2],
next: [f32; 2],
has_current: bool,
has_next: bool,
}
impl AudioResampler {
pub const MAX_RATE_ADJUST: f32 = 0.005;
pub fn new(target_fill: usize) -> Self {
Self {
phase: 0.0,
rate: 1.0,
target_fill,
current: [0.0; 2],
next: [0.0; 2],
has_current: false,
has_next: false,
}
}
pub fn update_rate(&mut self, fill_level: usize) {
let target = self.target_fill.max(1) as f32;
let delta = fill_level as f32 - target;
let normalized = (delta / target).clamp(-1.0, 1.0);
self.rate = 1.0 + normalized * Self::MAX_RATE_ADJUST;
}
#[cfg(test)]
pub fn rate(&self) -> f32 {
self.rate
}
#[cfg(test)]
pub fn set_rate_for_test(&mut self, rate: f32) {
self.rate = rate;
}
pub fn reset(&mut self) {
self.phase = 0.0;
self.rate = 1.0;
self.current = [0.0; 2];
self.next = [0.0; 2];
self.has_current = false;
self.has_next = false;
}
pub fn render_next<F>(&mut self, pop_sample: &mut F) -> Option<[f32; 2]>
where
F: FnMut() -> Option<[f32; 2]>,
{
if !self.has_current {
self.current = pop_sample()?;
self.has_current = true;
}
if !self.has_next {
if let Some(next) = pop_sample() {
self.next = next;
} else {
self.next = self.current;
}
self.has_next = true;
}
let l = self.current[0] + (self.next[0] - self.current[0]) * self.phase;
let r = self.current[1] + (self.next[1] - self.current[1]) * self.phase;
let sample = [l, r];
self.phase += self.rate;
while self.phase >= 1.0 {
self.phase -= 1.0;
self.current = self.next;
if let Some(next) = pop_sample() {
self.next = next;
} else {
self.next = self.current;
}
self.has_next = true;
}
Some(sample)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::VecDeque;
#[test]
fn test_resampler_rate_clamps_to_limits() {
let mut resampler = AudioResampler::new(100);
resampler.update_rate(100);
assert!((resampler.rate() - 1.0).abs() < 0.00001);
resampler.update_rate(0);
assert!((resampler.rate() - (1.0 - AudioResampler::MAX_RATE_ADJUST)).abs() < 0.00001);
resampler.update_rate(200);
assert!((resampler.rate() - (1.0 + AudioResampler::MAX_RATE_ADJUST)).abs() < 0.00001);
}
#[test]
fn test_resampler_outputs_source_sequence_at_unity_rate() {
let mut resampler = AudioResampler::new(4);
resampler.set_rate_for_test(1.0);
let mut samples = VecDeque::from([[0.0_f32, 0.0_f32], [1.0, 1.0], [0.0, 0.0], [1.0, 1.0]]);
let mut pop_sample = || samples.pop_front();
let first = resampler
.render_next(&mut pop_sample)
.expect("first sample");
let second = resampler
.render_next(&mut pop_sample)
.expect("second sample");
let third = resampler
.render_next(&mut pop_sample)
.expect("third sample");
assert!((first[0] - 0.0).abs() < 0.00001);
assert!((first[1] - 0.0).abs() < 0.00001);
assert!((second[0] - 1.0).abs() < 0.00001);
assert!((second[1] - 1.0).abs() < 0.00001);
assert!((third[0] - 0.0).abs() < 0.00001);
assert!((third[1] - 0.0).abs() < 0.00001);
}
#[test]
fn test_resampler_interpolates_stereo_channels_independently() {
let mut resampler = AudioResampler::new(4);
resampler.set_rate_for_test(0.5);
let mut samples = VecDeque::from([[0.0_f32, 1.0_f32], [1.0, 0.0]]);
let mut pop_sample = || samples.pop_front();
let first = resampler.render_next(&mut pop_sample).expect("first");
assert!(
(first[0] - 0.0).abs() < 0.00001,
"L at phase 0: {}",
first[0]
);
assert!(
(first[1] - 1.0).abs() < 0.00001,
"R at phase 0: {}",
first[1]
);
let second = resampler.render_next(&mut pop_sample).expect("second");
assert!(
(second[0] - 0.5).abs() < 0.00001,
"L at phase 0.5: {}",
second[0]
);
assert!(
(second[1] - 0.5).abs() < 0.00001,
"R at phase 0.5: {}",
second[1]
);
}
#[test]
fn test_reset_clears_interpolation_state() {
let mut resampler = AudioResampler::new(4);
resampler.set_rate_for_test(1.0);
let mut samples = VecDeque::from([[0.5_f32, 0.5_f32], [0.8, 0.8]]);
resampler.render_next(&mut || samples.pop_front());
resampler.reset();
let result = resampler.render_next(&mut || None);
assert!(
result.is_none(),
"after reset(), render_next with empty source must return None (no stale buffered samples)"
);
assert!(
(resampler.rate() - 1.0).abs() < 0.00001,
"reset() must restore rate to 1.0"
);
}
}