#![allow(clippy::needless_range_loop)]
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Quality {
Linear,
HighQuality,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum ResampleError {
Rubato(rubato::ResampleError),
Construction(rubato::ResamplerConstructionError),
UnalignedBuffer {
samples: usize,
channels: usize,
},
}
impl core::fmt::Display for ResampleError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ResampleError::Rubato(e) => write!(f, "rubato: {e}"),
ResampleError::Construction(e) => write!(f, "rubato construction: {e}"),
ResampleError::UnalignedBuffer { samples, channels } => write!(
f,
"buffer length {samples} is not a multiple of channel count {channels}"
),
}
}
}
impl std::error::Error for ResampleError {}
impl From<rubato::ResampleError> for ResampleError {
fn from(e: rubato::ResampleError) -> Self {
ResampleError::Rubato(e)
}
}
impl From<rubato::ResamplerConstructionError> for ResampleError {
fn from(e: rubato::ResamplerConstructionError) -> Self {
ResampleError::Construction(e)
}
}
enum Inner {
Linear(rubato::FastFixedIn<f32>),
Sinc(rubato::SincFixedIn<f32>),
}
impl Inner {
fn input_frames_next(&self) -> usize {
use rubato::Resampler;
match self {
Inner::Linear(r) => r.input_frames_next(),
Inner::Sinc(r) => r.input_frames_next(),
}
}
fn output_frames_next(&self) -> usize {
use rubato::Resampler;
match self {
Inner::Linear(r) => r.output_frames_next(),
Inner::Sinc(r) => r.output_frames_next(),
}
}
fn process_into_buffer(
&mut self,
input: &[&[f32]],
output: &mut [&mut [f32]],
) -> Result<(usize, usize), rubato::ResampleError> {
use rubato::Resampler;
match self {
Inner::Linear(r) => r.process_into_buffer(input, output, None),
Inner::Sinc(r) => r.process_into_buffer(input, output, None),
}
}
}
pub struct Resampler {
inner: Inner,
in_rate: u32,
out_rate: u32,
channels: usize,
pending: Vec<Vec<f32>>,
}
impl core::fmt::Debug for Resampler {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Resampler")
.field("in_rate", &self.in_rate)
.field("out_rate", &self.out_rate)
.field("channels", &self.channels)
.finish_non_exhaustive()
}
}
impl Resampler {
pub fn new(
in_rate: u32,
out_rate: u32,
channels: usize,
quality: Quality,
) -> Result<Self, ResampleError> {
let ratio = out_rate as f64 / in_rate as f64;
let chunk_size = 1024;
let inner = match quality {
Quality::Linear => {
use rubato::{FastFixedIn, PolynomialDegree};
Inner::Linear(FastFixedIn::new(
ratio,
1.0,
PolynomialDegree::Linear,
chunk_size,
channels,
)?)
}
Quality::HighQuality => {
use rubato::{
SincFixedIn, SincInterpolationParameters, SincInterpolationType, WindowFunction,
};
let params = SincInterpolationParameters {
sinc_len: 128,
f_cutoff: 0.95,
interpolation: SincInterpolationType::Quadratic,
oversampling_factor: 256,
window: WindowFunction::Blackman2,
};
Inner::Sinc(SincFixedIn::new(ratio, 1.0, params, chunk_size, channels)?)
}
};
Ok(Self {
inner,
in_rate,
out_rate,
channels,
pending: (0..channels).map(|_| Vec::new()).collect(),
})
}
pub fn process_interleaved(&mut self, input: &[f32]) -> Result<Vec<f32>, ResampleError> {
if self.channels == 0 {
return Ok(Vec::new());
}
if input.len() % self.channels != 0 {
return Err(ResampleError::UnalignedBuffer {
samples: input.len(),
channels: self.channels,
});
}
let frames = input.len() / self.channels;
for f in 0..frames {
for c in 0..self.channels {
self.pending[c].push(input[f * self.channels + c]);
}
}
let mut output_frames: Vec<Vec<f32>> = (0..self.channels).map(|_| Vec::new()).collect();
loop {
let needed = self.inner.input_frames_next();
if self.pending[0].len() < needed {
break;
}
let chunks: Vec<&[f32]> = (0..self.channels)
.map(|c| &self.pending[c][..needed])
.collect();
let mut out: Vec<Vec<f32>> = (0..self.channels)
.map(|_| Vec::with_capacity(self.inner.output_frames_next()))
.collect();
for buf in &mut out {
buf.resize(self.inner.output_frames_next(), 0.0);
}
let mut out_slices: Vec<&mut [f32]> =
out.iter_mut().map(|v| v.as_mut_slice()).collect();
let (consumed_in, produced_out) =
self.inner.process_into_buffer(&chunks, &mut out_slices)?;
for c in 0..self.channels {
self.pending[c].drain(..consumed_in);
output_frames[c].extend_from_slice(&out[c][..produced_out]);
}
}
let frames_out = output_frames[0].len();
let mut out = Vec::with_capacity(frames_out * self.channels);
for f in 0..frames_out {
for c in 0..self.channels {
out.push(output_frames[c][f]);
}
}
Ok(out)
}
#[must_use]
pub fn input_rate(&self) -> u32 {
self.in_rate
}
#[must_use]
pub fn output_rate(&self) -> u32 {
self.out_rate
}
#[must_use]
pub fn channels(&self) -> usize {
self.channels
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn upsample_8k_to_48k_preserves_signal_length_approximately() {
let mut r = Resampler::new(8_000, 48_000, 2, Quality::Linear).unwrap();
let input = vec![0.1f32; 16_000];
let out = r.process_interleaved(&input).unwrap();
assert!(
out.len() > 80_000 && out.len() < 100_000,
"got {} output samples, expected near 96000",
out.len()
);
}
#[test]
fn highquality_path_works_at_44_1_to_48() {
let mut r = Resampler::new(44_100, 48_000, 2, Quality::HighQuality).unwrap();
let n = 22_050usize;
let omega = 2.0 * std::f32::consts::PI * 1000.0 / 44_100.0;
let mut input = Vec::with_capacity(n * 2);
for i in 0..n {
let v = 0.5 * (omega * i as f32).sin();
input.push(v);
input.push(v);
}
let out = r.process_interleaved(&input).unwrap();
assert!(!out.is_empty());
}
#[test]
fn unaligned_buffer_rejected() {
let mut r = Resampler::new(48_000, 48_000, 2, Quality::Linear).unwrap();
let err = r.process_interleaved(&[0.0, 0.0, 0.0]).unwrap_err();
assert!(matches!(err, ResampleError::UnalignedBuffer { .. }));
}
}