use crate::tf::complex::Complex64;
use crate::tf::fft::{irfft, rfft};
pub fn resample(input: &[f32], in_rate: f32, out_rate: f32) -> Vec<f32> {
if input.is_empty() || in_rate <= 0.0 || out_rate <= 0.0 {
return Vec::new();
}
if (in_rate - out_rate).abs() < 1e-6 {
return input.to_vec();
}
let n_in = input.len();
let n_out = ((n_in as f32) * out_rate / in_rate).round() as usize;
if n_out == 0 {
return Vec::new();
}
let spectrum = rfft(input);
let out_half = n_out / 2 + 1;
let copy_len = out_half.min(spectrum.len());
let scale = n_out as f32 / n_in as f32;
let mut out_spectrum = vec![Complex64::new(0.0, 0.0); out_half];
for (dst, src) in out_spectrum[..copy_len]
.iter_mut()
.zip(spectrum[..copy_len].iter())
{
*dst = src.scale(scale);
}
irfft(&out_spectrum, n_out)
}
pub fn resample_into(input: &[f32], in_rate: f32, output: &mut [f32], out_rate: f32) {
let resampled = resample(input, in_rate, out_rate);
let len = resampled.len().min(output.len());
output[..len].copy_from_slice(&resampled[..len]);
}
pub fn resample_output_len(input_len: usize, in_rate: f32, out_rate: f32) -> usize {
if input_len == 0 || in_rate <= 0.0 || out_rate <= 0.0 {
return 0;
}
((input_len as f32) * out_rate / in_rate).round() as usize
}
#[cfg(test)]
mod tests {
use super::*;
use std::f32::consts::PI;
#[test]
fn test_resample_identity() {
let input: Vec<f32> = (0..64).map(|i| (i as f32 * 0.2).sin()).collect();
let output = resample(&input, 44100.0, 44100.0);
assert_eq!(output, input);
}
#[test]
fn test_resample_output_length() {
let input = vec![0.0; 1000];
let output = resample(&input, 44100.0, 48000.0);
let expected_len = resample_output_len(1000, 44100.0, 48000.0);
assert_eq!(output.len(), expected_len);
}
#[test]
fn test_resample_preserves_tone_frequency() {
let sr_in = 8000.0;
let sr_out = 16000.0;
let freq = 200.0;
let n_in = 800; let input: Vec<f32> = (0..n_in)
.map(|i| (2.0 * PI * freq * i as f32 / sr_in).sin())
.collect();
let output = resample(&input, sr_in, sr_out);
assert_eq!(output.len(), 1600);
let crossings = output.windows(2).filter(|w| w[0] * w[1] < 0.0).count();
assert!(
(36..=44).contains(&crossings),
"expected ~40 zero crossings for a 200Hz tone, got {crossings}"
);
}
}