pub fn resample_linear(samples: &[f32], from_hz: u32, to_hz: u32) -> Vec<f32> {
if from_hz == to_hz || samples.is_empty() {
return samples.to_vec();
}
let ratio = from_hz as f64 / to_hz as f64;
let out_len = (samples.len() as f64 / ratio).ceil() as usize;
(0..out_len)
.map(|i| {
let src = i as f64 * ratio;
let lo = src.floor() as usize;
let hi = (lo + 1).min(samples.len() - 1);
let frac = (src - lo as f64) as f32;
samples[lo] * (1.0 - frac) + samples[hi] * frac
})
.collect()
}
pub fn resample_linear_interleaved(
samples: &[f32],
channels: usize,
from_hz: u32,
to_hz: u32,
) -> Vec<f32> {
debug_assert!(channels >= 1, "channels must be >= 1");
if channels <= 1 {
return resample_linear(samples, from_hz, to_hz);
}
if from_hz == to_hz || samples.is_empty() {
return samples.to_vec();
}
let in_frames = samples.len() / channels;
if in_frames == 0 {
return Vec::new();
}
let ratio = from_hz as f64 / to_hz as f64;
let out_frames = (in_frames as f64 / ratio).ceil() as usize;
let mut out = vec![0.0f32; out_frames * channels];
for of in 0..out_frames {
let src = of as f64 * ratio;
let lo = src.floor() as usize;
let hi = (lo + 1).min(in_frames - 1);
let frac = (src - lo as f64) as f32;
for c in 0..channels {
let a = samples[lo * channels + c];
let b = samples[hi * channels + c];
out[of * channels + c] = a * (1.0 - frac) + b * frac;
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identity_when_rates_match() {
let x = vec![0.1, 0.2, 0.3, 0.4];
assert_eq!(resample_linear(&x, 24_000, 24_000), x);
assert_eq!(resample_linear_interleaved(&x, 2, 48_000, 48_000), x);
}
#[test]
fn empty_input() {
assert!(resample_linear(&[], 16_000, 24_000).is_empty());
assert!(resample_linear_interleaved(&[], 2, 16_000, 24_000).is_empty());
}
#[test]
fn downsample_halves_length() {
let x: Vec<f32> = (0..100).map(|i| i as f32).collect();
let y = resample_linear(&x, 48_000, 24_000);
assert_eq!(y.len(), 50);
assert!((y[0] - 0.0).abs() < 1e-6);
assert!((y[10] - 20.0).abs() < 1e-3);
}
#[test]
fn upsample_doubles_length() {
let x: Vec<f32> = (0..50).map(|i| i as f32).collect();
let y = resample_linear(&x, 24_000, 48_000);
assert_eq!(y.len(), 100);
assert!((y[2] - 1.0).abs() < 1e-3);
}
#[test]
fn interleaved_matches_per_channel() {
let frames = 40usize;
let mut x = vec![0.0f32; frames * 2];
for f in 0..frames {
x[f * 2] = f as f32;
x[f * 2 + 1] = (frames - f) as f32;
}
let y = resample_linear_interleaved(&x, 2, 40_000, 20_000);
let ch0: Vec<f32> = (0..frames).map(|f| f as f32).collect();
let ch1: Vec<f32> = (0..frames).map(|f| (frames - f) as f32).collect();
let y0 = resample_linear(&ch0, 40_000, 20_000);
let y1 = resample_linear(&ch1, 40_000, 20_000);
assert_eq!(y.len(), y0.len() * 2);
for f in 0..y0.len() {
assert!((y[f * 2] - y0[f]).abs() < 1e-6);
assert!((y[f * 2 + 1] - y1[f]).abs() < 1e-6);
}
}
}