#![forbid(unsafe_code)]
use std::f64::consts::PI;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ResamplingQuality {
Fast,
Medium,
High,
}
pub struct SampleRateConverter {
pub src_rate: u32,
pub dst_rate: u32,
pub quality: ResamplingQuality,
pub channels: usize,
phase: f64,
history: Vec<f32>,
}
const HIGH_HISTORY: usize = 64;
const MEDIUM_HISTORY: usize = 4; const FAST_HISTORY: usize = 1;
const SINC_TAPS: usize = 64;
impl SampleRateConverter {
#[must_use]
pub fn new(src: u32, dst: u32, channels: usize, quality: ResamplingQuality) -> Self {
let channels = channels.max(1);
let history_frames = match quality {
ResamplingQuality::Fast => FAST_HISTORY,
ResamplingQuality::Medium => MEDIUM_HISTORY,
ResamplingQuality::High => HIGH_HISTORY,
};
let history = vec![0.0f32; history_frames * channels];
Self {
src_rate: src,
dst_rate: dst,
quality,
channels,
phase: 0.0,
history,
}
}
#[must_use]
pub fn ratio(&self) -> f64 {
self.dst_rate as f64 / self.src_rate as f64
}
#[must_use]
pub fn convert(&mut self, input: &[f32]) -> Vec<f32> {
if self.src_rate == self.dst_rate {
return input.to_vec();
}
if input.is_empty() {
return Vec::new();
}
match self.quality {
ResamplingQuality::Fast => self.convert_linear(input),
ResamplingQuality::Medium => self.convert_hermite(input),
ResamplingQuality::High => self.convert_sinc(input),
}
}
fn convert_linear(&mut self, input: &[f32]) -> Vec<f32> {
let ch = self.channels;
let input_frames = input.len() / ch;
if input_frames == 0 {
return Vec::new();
}
let ratio = self.ratio();
let step = 1.0 / ratio;
let estimated = ((input_frames as f64 / step) + 2.0) as usize;
let mut output = Vec::with_capacity(estimated * ch);
let history = &self.history;
let get_frame = |frame_idx: i64, input: &[f32]| -> Vec<f32> {
if frame_idx < 0 {
let h_idx = (FAST_HISTORY as i64 + frame_idx) as usize;
if h_idx < FAST_HISTORY {
history[h_idx * ch..(h_idx + 1) * ch].to_vec()
} else {
vec![0.0; ch]
}
} else {
let fi = frame_idx as usize;
if fi < input_frames {
input[fi * ch..(fi + 1) * ch].to_vec()
} else {
vec![0.0; ch]
}
}
};
while self.phase < input_frames as f64 {
let frame0 = self.phase.floor() as i64;
let frac = (self.phase - self.phase.floor()) as f32;
let s0 = get_frame(frame0, input);
let s1 = get_frame(frame0 + 1, input);
for c in 0..ch {
output.push(s0[c] + frac * (s1[c] - s0[c]));
}
self.phase += step;
}
self.phase -= input_frames as f64;
let hist_start = input_frames.saturating_sub(FAST_HISTORY);
let copy_frames = input_frames - hist_start;
let mut new_history = vec![0.0f32; FAST_HISTORY * ch];
let dest_offset = FAST_HISTORY.saturating_sub(copy_frames);
for f in 0..copy_frames {
let src_f = hist_start + f;
for c in 0..ch {
new_history[(dest_offset + f) * ch + c] = input[src_f * ch + c];
}
}
self.history = new_history;
output
}
fn convert_hermite(&mut self, input: &[f32]) -> Vec<f32> {
let ch = self.channels;
let input_frames = input.len() / ch;
if input_frames == 0 {
return Vec::new();
}
let ratio = self.ratio();
let step = 1.0 / ratio;
let estimated = ((input_frames as f64 / step) + 2.0) as usize;
let mut output = Vec::with_capacity(estimated * ch);
let history = &self.history;
let get_frame = |frame_idx: i64, input: &[f32]| -> Vec<f32> {
if frame_idx < 0 {
let h_idx = (MEDIUM_HISTORY as i64 + frame_idx) as usize;
if h_idx < MEDIUM_HISTORY {
history[h_idx * ch..(h_idx + 1) * ch].to_vec()
} else {
vec![0.0; ch]
}
} else {
let fi = frame_idx as usize;
if fi < input_frames {
input[fi * ch..(fi + 1) * ch].to_vec()
} else if fi == input_frames {
vec![0.0; ch]
} else {
vec![0.0; ch]
}
}
};
while self.phase < input_frames as f64 {
let frame0 = self.phase.floor() as i64;
let frac = (self.phase - self.phase.floor()) as f32;
let xm1 = get_frame(frame0 - 1, input);
let x0 = get_frame(frame0, input);
let x1 = get_frame(frame0 + 1, input);
let x2 = get_frame(frame0 + 2, input);
for c in 0..ch {
output.push(hermite_interp(xm1[c], x0[c], x1[c], x2[c], frac));
}
self.phase += step;
}
self.phase -= input_frames as f64;
let hist_start = input_frames.saturating_sub(MEDIUM_HISTORY);
let copy_frames = input_frames - hist_start;
let mut new_history = vec![0.0f32; MEDIUM_HISTORY * ch];
let dest_offset = MEDIUM_HISTORY.saturating_sub(copy_frames);
for f in 0..copy_frames {
let src_f = hist_start + f;
for c in 0..ch {
new_history[(dest_offset + f) * ch + c] = input[src_f * ch + c];
}
}
self.history = new_history;
output
}
fn convert_sinc(&mut self, input: &[f32]) -> Vec<f32> {
let ch = self.channels;
let input_frames = input.len() / ch;
if input_frames == 0 {
return Vec::new();
}
let ratio = self.ratio();
let step = 1.0 / ratio;
let cutoff = if self.dst_rate < self.src_rate {
ratio } else {
1.0
};
let kaiser_win = kaiser_window(SINC_TAPS, 8.0);
let estimated = ((input_frames as f64 / step) + 2.0) as usize;
let mut output = Vec::with_capacity(estimated * ch);
let history = &self.history;
let total_history = HIGH_HISTORY;
let get_sample = |frame_idx: i64, ch_idx: usize| -> f32 {
if frame_idx < 0 {
let h_frame = (total_history as i64 + frame_idx) as usize;
if h_frame < total_history {
history[h_frame * ch + ch_idx]
} else {
0.0
}
} else {
let fi = frame_idx as usize;
if fi < input_frames {
input[fi * ch + ch_idx]
} else {
0.0
}
}
};
let half_taps = (SINC_TAPS / 2) as i64;
while self.phase < input_frames as f64 {
let center = self.phase.floor() as i64;
let frac = self.phase - self.phase.floor();
for c in 0..ch {
let mut acc = 0.0f64;
for k in -half_taps..half_taps {
let tap_pos = center + k; let x_k = get_sample(tap_pos, c) as f64;
let t = k as f64 - frac;
let sinc_val = sinc(t * cutoff);
let win_idx = (k + half_taps) as usize;
let w = kaiser_win[win_idx.min(SINC_TAPS - 1)];
acc += x_k * sinc_val * w as f64;
}
output.push((acc * cutoff) as f32);
}
self.phase += step;
}
self.phase -= input_frames as f64;
let hist_start = input_frames.saturating_sub(HIGH_HISTORY);
let copy_frames = input_frames - hist_start;
let mut new_history = vec![0.0f32; HIGH_HISTORY * ch];
let dest_offset = HIGH_HISTORY.saturating_sub(copy_frames);
for f in 0..copy_frames {
let src_f = hist_start + f;
for c in 0..ch {
new_history[(dest_offset + f) * ch + c] = input[src_f * ch + c];
}
}
self.history = new_history;
output
}
#[must_use]
pub fn to_48k(input: &[f32], src_rate: u32, channels: usize) -> Vec<f32> {
let mut conv = Self::new(src_rate, 48_000, channels, ResamplingQuality::High);
conv.convert(input)
}
#[must_use]
pub fn to_44100(input: &[f32], src_rate: u32, channels: usize) -> Vec<f32> {
let mut conv = Self::new(src_rate, 44_100, channels, ResamplingQuality::High);
conv.convert(input)
}
}
#[inline]
fn sinc(x: f64) -> f64 {
if x.abs() < 1e-10 {
1.0
} else {
let px = PI * x;
px.sin() / px
}
}
#[inline]
fn hermite_interp(xm1: f32, x0: f32, x1: f32, x2: f32, frac: f32) -> f32 {
let c = (x1 - xm1) * 0.5;
let v = x0 - x1;
let w = c + v;
let a = w + v + (x2 - x0) * 0.5;
let b = w + a;
(((a * frac) - b) * frac + c) * frac + x0
}
#[must_use]
fn kaiser_window(n: usize, beta: f64) -> Vec<f32> {
if n == 0 {
return Vec::new();
}
let n_f = (n - 1) as f64;
let i0_beta = bessel_i0(beta);
(0..n)
.map(|k| {
let x = 2.0 * k as f64 / n_f - 1.0;
let arg = beta * (1.0 - x * x).max(0.0).sqrt();
(bessel_i0(arg) / i0_beta) as f32
})
.collect()
}
#[must_use]
pub fn bessel_i0(x: f64) -> f64 {
let x2 = (x / 2.0) * (x / 2.0);
let mut term = 1.0f64;
let mut sum = 1.0f64;
for k in 1u32..=50 {
term *= x2 / (k as f64 * k as f64);
sum += term;
if term < sum * 1e-15 {
break;
}
}
sum
}
#[cfg(test)]
mod tests {
use super::*;
use std::f32::consts::PI;
fn sine_wave(freq_hz: f32, sample_rate: u32, num_samples: usize) -> Vec<f32> {
let sr = sample_rate as f32;
(0..num_samples)
.map(|i| (2.0 * PI * freq_hz * i as f32 / sr).sin())
.collect()
}
#[test]
fn test_bessel_i0_zero() {
let val = bessel_i0(0.0);
assert!((val - 1.0).abs() < 1e-10, "I0(0) should be 1, got {val}");
}
#[test]
fn test_bessel_i0_known_value() {
let val = bessel_i0(1.0);
assert!(
(val - 1.266_065_8).abs() < 1e-5,
"I0(1) should be ~1.2661, got {val}"
);
}
#[test]
fn test_ratio_44100_to_48000() {
let conv = SampleRateConverter::new(44100, 48000, 1, ResamplingQuality::Fast);
let r = conv.ratio();
assert!((r - 48000.0 / 44100.0).abs() < 1e-10);
}
#[test]
fn test_passthrough_identity() {
let mut conv = SampleRateConverter::new(44100, 44100, 1, ResamplingQuality::Fast);
let input = vec![0.1, 0.2, 0.3, 0.4];
let output = conv.convert(&input);
assert_eq!(output, input, "Same-rate should be passthrough");
}
#[test]
fn test_fast_output_length_upsample() {
let input = sine_wave(440.0, 44100, 4410);
let mut conv = SampleRateConverter::new(44100, 48000, 1, ResamplingQuality::Fast);
let output = conv.convert(&input);
let expected = (4410.0 * 48000.0 / 44100.0) as usize;
assert!(
output.len().abs_diff(expected) <= 2,
"output len {} should be ≈ {}",
output.len(),
expected
);
}
#[test]
fn test_fast_output_length_downsample() {
let input = sine_wave(440.0, 48000, 4800);
let mut conv = SampleRateConverter::new(48000, 44100, 1, ResamplingQuality::Fast);
let output = conv.convert(&input);
let expected = (4800.0 * 44100.0 / 48000.0) as usize;
assert!(
output.len().abs_diff(expected) <= 2,
"output len {} should be ≈ {}",
output.len(),
expected
);
}
#[test]
fn test_medium_output_length() {
let input = sine_wave(440.0, 44100, 4410);
let mut conv = SampleRateConverter::new(44100, 48000, 1, ResamplingQuality::Medium);
let output = conv.convert(&input);
let expected = (4410.0 * 48000.0 / 44100.0) as usize;
assert!(
output.len().abs_diff(expected) <= 2,
"output len {} should be ≈ {}",
output.len(),
expected
);
}
#[test]
fn test_high_output_length() {
let input = sine_wave(440.0, 44100, 4410);
let mut conv = SampleRateConverter::new(44100, 48000, 1, ResamplingQuality::High);
let output = conv.convert(&input);
let expected = (4410.0 * 48000.0 / 44100.0) as usize;
assert!(
output.len().abs_diff(expected) <= 2,
"output len {} should be ≈ {}",
output.len(),
expected
);
}
#[test]
fn test_fast_output_all_finite() {
let input = sine_wave(440.0, 44100, 2048);
let mut conv = SampleRateConverter::new(44100, 48000, 1, ResamplingQuality::Fast);
let output = conv.convert(&input);
assert!(
output.iter().all(|x| x.is_finite()),
"Fast output must be all finite"
);
}
#[test]
fn test_medium_output_all_finite() {
let input = sine_wave(440.0, 44100, 2048);
let mut conv = SampleRateConverter::new(44100, 48000, 1, ResamplingQuality::Medium);
let output = conv.convert(&input);
assert!(
output.iter().all(|x| x.is_finite()),
"Medium output must be all finite"
);
}
#[test]
fn test_high_output_all_finite() {
let input = sine_wave(440.0, 44100, 2048);
let mut conv = SampleRateConverter::new(44100, 48000, 1, ResamplingQuality::High);
let output = conv.convert(&input);
assert!(
output.iter().all(|x| x.is_finite()),
"High output must be all finite"
);
}
#[test]
fn test_to_48k_length() {
let input = sine_wave(440.0, 44100, 4410);
let output = SampleRateConverter::to_48k(&input, 44100, 1);
let expected = (4410.0 * 48000.0 / 44100.0) as usize;
assert!(
output.len().abs_diff(expected) <= 2,
"to_48k len {} should be ≈ {}",
output.len(),
expected
);
}
#[test]
fn test_to_44100_length() {
let input = sine_wave(440.0, 48000, 4800);
let output = SampleRateConverter::to_44100(&input, 48000, 1);
let expected = (4800.0 * 44100.0 / 48000.0) as usize;
assert!(
output.len().abs_diff(expected) <= 2,
"to_44100 len {} should be ≈ {}",
output.len(),
expected
);
}
}