use rubato::{
Resampler, SincFixedOut, SincInterpolationParameters, SincInterpolationType, WindowFunction,
};
pub struct AdaptiveResampler {
resampler: SincFixedOut<f32>,
target_fill: f64,
alpha: f64,
k_p: f64,
k_i: f64,
smoothed_error: f64,
integral_error: f64,
adjustment_interval: usize,
adjustment_counter: usize,
leftover: Vec<f32>,
resample_ratio: f64,
nominal_ratio: f64,
channels: usize,
}
impl AdaptiveResampler {
pub fn new(
initial_ratio: f64,
adjustment_interval: usize,
channels: usize,
) -> Result<Self, String> {
let params = SincInterpolationParameters {
sinc_len: 256,
f_cutoff: 0.95,
interpolation: SincInterpolationType::Cubic,
oversampling_factor: 160,
window: WindowFunction::BlackmanHarris2,
};
let output_frames = 1024;
let max_resample_ratio_relative = 1.02;
let resampler = SincFixedOut::<f32>::new(
initial_ratio,
max_resample_ratio_relative,
params,
output_frames,
channels,
)
.map_err(|e| format!("Failed to create resampler: {:?}", e))?;
Ok(Self {
resampler,
target_fill: 0.3,
alpha: 0.95,
k_p: 0.0001,
k_i: 1e-7,
smoothed_error: 0.0,
integral_error: 0.0,
adjustment_interval,
adjustment_counter: 0,
leftover: Vec::new(),
resample_ratio: initial_ratio,
nominal_ratio: initial_ratio,
channels,
})
}
pub fn with_params(
initial_ratio: f64,
adjustment_interval: usize,
channels: usize,
target_fill: f64,
k_p: f64,
k_i: f64,
) -> Result<Self, String> {
let mut resampler = Self::new(initial_ratio, adjustment_interval, channels)?;
resampler.target_fill = target_fill;
resampler.k_p = k_p;
resampler.k_i = k_i;
Ok(resampler)
}
#[allow(clippy::needless_range_loop)]
pub fn process(&mut self, input: &[f32]) -> Vec<f32> {
self.leftover.extend_from_slice(input);
let mut output = Vec::new();
loop {
let input_frames_needed = self.resampler.input_frames_next();
let samples_needed = input_frames_needed * self.channels;
if self.leftover.len() < samples_needed {
break;
}
let chunk: Vec<f32> = self.leftover.drain(..samples_needed).collect();
#[allow(clippy::needless_range_loop)]
let mut channels_data: Vec<Vec<f32>> =
vec![Vec::with_capacity(input_frames_needed); self.channels];
for frame_idx in 0..input_frames_needed {
for ch in 0..self.channels {
let idx = frame_idx * self.channels + ch;
channels_data[ch].push(chunk[idx]);
}
}
match self.resampler.process(&channels_data, None) {
Ok(output_blocks) => {
let out_frames = output_blocks[0].len();
#[allow(clippy::needless_range_loop)]
for i in 0..out_frames {
for ch in 0..self.channels {
output.push(output_blocks[ch][i]);
}
}
}
Err(_) => break,
}
}
output
}
pub fn adjust_ratio(&mut self, buffer_fill: f64) {
self.adjustment_counter += 1;
if self.adjustment_counter < self.adjustment_interval {
return;
}
self.adjustment_counter = 0;
let error = self.target_fill - buffer_fill;
self.smoothed_error = self.alpha * self.smoothed_error + (1.0 - self.alpha) * error;
if self.smoothed_error.abs() < 0.01 {
self.smoothed_error = 0.0;
}
if self.smoothed_error.abs() < 0.15 {
self.integral_error += self.smoothed_error;
self.integral_error = self.integral_error.clamp(-100.0, 100.0);
}
self.resample_ratio += self.k_p * self.smoothed_error + self.k_i * self.integral_error;
let min_ratio = self.nominal_ratio * 0.97;
let max_ratio = self.nominal_ratio * 1.03;
self.resample_ratio = self.resample_ratio.clamp(min_ratio, max_ratio);
let _ = self.resampler.set_resample_ratio(self.resample_ratio, true);
}
pub fn ratio(&self) -> f64 {
self.resample_ratio
}
pub fn nominal_ratio(&self) -> f64 {
self.nominal_ratio
}
pub fn error(&self) -> f64 {
self.smoothed_error
}
pub fn leftover_samples(&self) -> usize {
self.leftover.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_adaptive_resampler_creation() {
let resampler = AdaptiveResampler::new(0.2, 1, 1);
assert!(resampler.is_ok());
let resampler = resampler.unwrap();
assert_eq!(resampler.ratio(), 0.2);
assert_eq!(resampler.nominal_ratio(), 0.2);
}
#[test]
fn test_adaptive_resampler_stereo() {
let resampler = AdaptiveResampler::new(0.2, 1, 2);
assert!(resampler.is_ok());
}
#[test]
fn test_adaptive_resampler_with_params() {
let resampler = AdaptiveResampler::with_params(0.2, 5, 1, 0.4, 0.002, 5e-6);
assert!(resampler.is_ok());
let resampler = resampler.unwrap();
assert_eq!(resampler.target_fill, 0.4);
assert_eq!(resampler.k_p, 0.002);
assert_eq!(resampler.k_i, 5e-6);
}
#[test]
fn test_adaptive_resampler_process() {
let mut resampler = AdaptiveResampler::new(1.0, 1, 1).unwrap();
let input = vec![0.5; 2048];
let output = resampler.process(&input);
assert!(!output.is_empty());
}
#[test]
fn test_adaptive_resampler_ratio_adjustment() {
let mut resampler = AdaptiveResampler::new(0.2, 1, 1).unwrap();
let initial_ratio = resampler.ratio();
resampler.adjust_ratio(0.8);
let adjusted_ratio = resampler.ratio();
assert_ne!(initial_ratio, adjusted_ratio);
}
#[test]
fn test_adaptive_resampler_leftover() {
let mut resampler = AdaptiveResampler::new(1.0, 1, 1).unwrap();
let input = vec![0.5; 10];
let _ = resampler.process(&input);
assert!(resampler.leftover_samples() > 0);
}
}