use rubato::{
audioadapter_buffers::direct::SequentialSlice, Fft, FixedSync,
Resampler as RubatoResamplerTrait,
};
use crate::{ResampleError, Resampler};
const MAX_RATE: f64 = 4_000_000.0;
#[derive(Debug)]
pub struct RubatoResampler {
inner: Fft<f32>,
channels: usize,
chunk_size: usize,
input_rate: f64,
output_rate: f64,
}
impl RubatoResampler {
pub fn new(
input_rate: f64,
output_rate: f64,
channels: usize,
chunk_size: usize,
) -> Result<Self, ResampleError> {
if !is_valid_rate(input_rate) {
return Err(ResampleError::InvalidSampleRate { rate: input_rate });
}
if !is_valid_rate(output_rate) {
return Err(ResampleError::InvalidSampleRate { rate: output_rate });
}
if channels == 0 {
return Err(ResampleError::InvalidChannelCount(channels));
}
if chunk_size == 0 {
return Err(ResampleError::InvalidChunkSize(chunk_size));
}
let rate_in = rate_to_usize(input_rate)
.ok_or(ResampleError::InvalidSampleRate { rate: input_rate })?;
let rate_out = rate_to_usize(output_rate)
.ok_or(ResampleError::InvalidSampleRate { rate: output_rate })?;
let inner = Fft::<f32>::new(rate_in, rate_out, chunk_size, channels, FixedSync::Input)
.map_err(|e| ResampleError::ResampleFailed(e.to_string()))?;
Ok(Self {
inner,
channels,
chunk_size,
input_rate,
output_rate,
})
}
#[must_use]
pub fn input_chunk_size(&self) -> usize {
self.chunk_size
}
#[must_use]
pub fn output_frames_max(&self) -> usize {
self.inner.output_frames_max()
}
#[must_use]
pub fn channels(&self) -> usize {
self.channels
}
#[must_use]
pub fn input_rate(&self) -> f64 {
self.input_rate
}
#[must_use]
pub fn output_rate(&self) -> f64 {
self.output_rate
}
#[must_use]
pub fn output_delay(&self) -> usize {
self.inner.output_delay()
}
}
impl Resampler for RubatoResampler {
fn process_into_buffer(
&mut self,
input: &[f32],
output: &mut [f32],
) -> Result<usize, ResampleError> {
let expected = self.channels * self.chunk_size;
if input.len() != expected {
return Err(ResampleError::InputLengthMismatch {
expected,
actual: input.len(),
});
}
let out_n = self.inner.output_frames_next();
let needed = self.channels * out_n;
if output.len() < needed {
return Err(ResampleError::BufferTooSmall {
needed,
have: output.len(),
});
}
let input_adapter = SequentialSlice::new(input, self.channels, self.chunk_size)
.map_err(|e| ResampleError::ResampleFailed(e.to_string()))?;
let mut output_adapter = SequentialSlice::new_mut(output, self.channels, out_n)
.map_err(|e| ResampleError::ResampleFailed(e.to_string()))?;
let (_frames_read, frames_written) = self
.inner
.process_into_buffer(&input_adapter, &mut output_adapter, None)
.map_err(|e| ResampleError::ResampleFailed(e.to_string()))?;
Ok(frames_written)
}
fn set_rate(&mut self, input_rate: f64, output_rate: f64) {
if !is_valid_rate(input_rate) || !is_valid_rate(output_rate) {
return;
}
let Some(rate_in) = rate_to_usize(input_rate) else {
return;
};
let Some(rate_out) = rate_to_usize(output_rate) else {
return;
};
let Ok(new_inner) = Fft::<f32>::new(
rate_in,
rate_out,
self.chunk_size,
self.channels,
FixedSync::Input,
) else {
return;
};
self.inner = new_inner;
self.input_rate = input_rate;
self.output_rate = output_rate;
}
}
const fn is_valid_rate(rate: f64) -> bool {
rate.is_finite() && rate >= 1.0
}
fn rate_to_usize(rate: f64) -> Option<usize> {
if rate > MAX_RATE {
return None;
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let value = rate as usize;
Some(value)
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-9
}
#[allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
fn sine_chunk(freq: f64, rate: f64, frames: usize, phase_offset: usize) -> Vec<f32> {
let omega = 2.0_f64 * std::f64::consts::TAU * freq / rate;
(0..frames)
.map(|i| {
let t = (i + phase_offset) as f64;
(omega * t).sin() as f32
})
.collect()
}
#[test]
fn new_rejects_zero_sample_rate() {
let err = RubatoResampler::new(0.0, 48000.0, 1, 256).unwrap_err();
assert_eq!(err, ResampleError::InvalidSampleRate { rate: 0.0 });
let err = RubatoResampler::new(44100.0, 0.0, 1, 256).unwrap_err();
assert_eq!(err, ResampleError::InvalidSampleRate { rate: 0.0 });
}
#[test]
fn new_rejects_non_finite_sample_rate() {
let err = RubatoResampler::new(f64::NAN, 48000.0, 1, 256).unwrap_err();
assert!(err.to_string().contains("invalid sample rate"));
}
#[test]
fn new_rejects_zero_channels() {
let err = RubatoResampler::new(44100.0, 48000.0, 0, 256).unwrap_err();
assert_eq!(err, ResampleError::InvalidChannelCount(0));
}
#[test]
fn new_rejects_zero_chunk_size() {
let err = RubatoResampler::new(44100.0, 48000.0, 1, 0).unwrap_err();
assert_eq!(err, ResampleError::InvalidChunkSize(0));
}
#[test]
fn process_into_buffer_rejects_wrong_input_length() {
let mut rs = RubatoResampler::new(44100.0, 48000.0, 1, 256).unwrap();
let input = vec![0.0_f32; 128]; let mut output = vec![0.0_f32; rs.output_frames_max()];
let err = rs.process_into_buffer(&input, &mut output).unwrap_err();
assert_eq!(
err,
ResampleError::InputLengthMismatch {
expected: 256,
actual: 128,
}
);
}
#[test]
fn process_into_buffer_rejects_small_output() {
let mut rs = RubatoResampler::new(44100.0, 48000.0, 1, 256).unwrap();
let input = vec![0.0_f32; 256];
for _ in 0..256 {
let mut tiny = vec![0.0_f32; 1];
match rs.process_into_buffer(&input, &mut tiny) {
Ok(_) => {} Err(ResampleError::BufferTooSmall { needed, have }) => {
assert_eq!(have, 1);
assert!(needed > 1, "out_n must exceed 1");
return;
}
Err(other) => panic!("unexpected error: {other:?}"),
}
}
panic!("BufferTooSmall did not trigger within 256 iterations");
}
#[test]
fn process_into_buffer_writes_something_for_sine() {
let chunk = 1024;
let mut rs = RubatoResampler::new(44100.0, 48000.0, 1, chunk).unwrap();
let mut max_abs = 0.0_f32;
for blk in 0..16_usize {
let input = sine_chunk(1000.0, 44100.0, chunk, blk * chunk);
let mut output = vec![0.0_f32; rs.output_frames_max()];
let written = rs.process_into_buffer(&input, &mut output).unwrap();
for s in &output[..written] {
let abs = s.abs();
if abs > max_abs {
max_abs = abs;
}
}
}
assert!(
max_abs > 0.0,
"output must not be all zero (max_abs={max_abs})"
);
}
#[test]
fn set_rate_changes_output_rate() {
let mut rs = RubatoResampler::new(44100.0, 48000.0, 1, 256).unwrap();
rs.set_rate(48000.0, 96000.0);
assert!(approx_eq(rs.input_rate(), 48000.0));
assert!(approx_eq(rs.output_rate(), 96000.0));
let input = sine_chunk(1000.0, 48000.0, 256, 0);
let mut output = vec![0.0_f32; rs.output_frames_max()];
let written = rs.process_into_buffer(&input, &mut output).unwrap();
assert!(written > 0);
}
#[test]
fn set_rate_ignores_invalid_rate() {
let mut rs = RubatoResampler::new(44100.0, 48000.0, 1, 256).unwrap();
rs.set_rate(0.0, 96000.0);
assert!(approx_eq(rs.input_rate(), 44100.0));
assert!(approx_eq(rs.output_rate(), 48000.0));
rs.set_rate(-1.0, 96000.0);
assert!(approx_eq(rs.input_rate(), 44100.0));
}
#[test]
fn stereo_two_channels_processes_both() {
let chunk = 512;
let ch = 2;
let mut rs = RubatoResampler::new(44100.0, 48000.0, ch, chunk).unwrap();
let mut max0 = 0.0_f32;
let mut max1 = 0.0_f32;
for blk in 0..16_usize {
let c0 = sine_chunk(1000.0, 44100.0, chunk, blk * chunk);
let c1 = sine_chunk(2000.0, 44100.0, chunk, blk * chunk);
let mut input = Vec::with_capacity(ch * chunk);
input.extend_from_slice(&c0);
input.extend_from_slice(&c1);
let mut output = vec![0.0_f32; ch * rs.output_frames_max()];
let written = rs.process_into_buffer(&input, &mut output).unwrap();
for s in &output[..written] {
let abs = s.abs();
if abs > max0 {
max0 = abs;
}
}
for s in &output[written..2 * written] {
let abs = s.abs();
if abs > max1 {
max1 = abs;
}
}
}
assert!(max0 > 0.0, "channel 0 processed (max0={max0})");
assert!(max1 > 0.0, "channel 1 processed (max1={max1})");
}
#[test]
fn accessors_return_construction_values() {
let rs = RubatoResampler::new(44100.0, 48000.0, 2, 256).unwrap();
assert_eq!(rs.channels(), 2);
assert_eq!(rs.input_chunk_size(), 256);
assert!(approx_eq(rs.input_rate(), 44100.0));
assert!(approx_eq(rs.output_rate(), 48000.0));
assert!(rs.output_frames_max() > 0);
}
}