use alloc::vec::Vec;
#[cfg(not(feature = "std"))]
use num_traits::Float;
const TARGET_RATE: f64 = 12_000.0;
pub fn resample_to_12k(samples: &[i16], src_rate: u32) -> Vec<i16> {
let ratio = TARGET_RATE / src_rate as f64;
let out_len = (samples.len() as f64 * ratio).ceil() as usize;
let mut out = Vec::with_capacity(out_len);
for i in 0..out_len {
let src_pos = i as f64 / ratio;
let idx = src_pos as usize;
let frac = src_pos - idx as f64;
if idx + 1 < samples.len() {
let a = samples[idx] as f64;
let b = samples[idx + 1] as f64;
let v = a + (b - a) * frac;
out.push(v.round() as i16);
} else if idx < samples.len() {
out.push(samples[idx]);
}
}
out
}
pub fn resample_f32_to_12k(samples: &[f32], src_rate: u32) -> Vec<i16> {
const TARGET_PEAK: f64 = 0.8;
const SILENCE_FLOOR: f64 = 1e-6;
let peak = samples.iter().fold(0.0f64, |m, &s| m.max((s as f64).abs()));
let scale = if peak > SILENCE_FLOOR {
TARGET_PEAK / peak
} else {
1.0
};
let ratio = TARGET_RATE / src_rate as f64;
let out_len = (samples.len() as f64 * ratio).ceil() as usize;
let mut out = Vec::with_capacity(out_len);
for i in 0..out_len {
let src_pos = i as f64 / ratio;
let idx = src_pos as usize;
let frac = src_pos - idx as f64;
let v = if idx + 1 < samples.len() {
let a = samples[idx] as f64;
let b = samples[idx + 1] as f64;
a + (b - a) * frac
} else if idx < samples.len() {
samples[idx] as f64
} else {
continue;
};
let scaled = (v * scale * 32767.0).clamp(-32768.0, 32767.0);
out.push(scaled.round() as i16);
}
out
}
pub fn resample_f32_to_12k_f32(samples: &[f32], src_rate: u32) -> Vec<f32> {
if src_rate == 12_000 {
return samples.to_vec();
}
let ratio = TARGET_RATE / src_rate as f64;
let out_len = (samples.len() as f64 * ratio).ceil() as usize;
let mut out = Vec::with_capacity(out_len);
for i in 0..out_len {
let src_pos = i as f64 / ratio;
let idx = src_pos as usize;
let frac = src_pos - idx as f64;
let v = if idx + 1 < samples.len() {
let a = samples[idx] as f64;
let b = samples[idx + 1] as f64;
a + (b - a) * frac
} else if idx < samples.len() {
samples[idx] as f64
} else {
continue;
};
out.push(v as f32);
}
out
}
pub fn resample_i16_to_12k_f32(samples: &[i16], src_rate: u32) -> Vec<f32> {
if src_rate == 12_000 {
return samples.iter().map(|&s| s as f32 / 32768.0).collect();
}
resample_to_12k(samples, src_rate)
.into_iter()
.map(|s| s as f32 / 32768.0)
.collect()
}
pub struct LinearResamplerI16To12k {
src_rate: u32,
step_q32: u64,
phase_q32: u64,
last_in: i16,
primed: bool,
}
impl LinearResamplerI16To12k {
pub fn new(src_rate_hz: u32) -> Self {
assert!(src_rate_hz > 0, "src_rate_hz must be > 0");
let step_q32 = ((src_rate_hz as u64) << 32) / 12_000;
Self {
src_rate: src_rate_hz,
step_q32,
phase_q32: 0,
last_in: 0,
primed: false,
}
}
pub fn src_rate(&self) -> u32 {
self.src_rate
}
pub fn process(&mut self, src: &[i16], dst: &mut [i16]) -> (usize, usize) {
let mut src = src;
let mut consumed = 0usize;
let mut produced = 0usize;
if !self.primed {
if src.is_empty() {
return (0, 0);
}
self.last_in = src[0];
src = &src[1..];
consumed += 1;
self.primed = true;
}
while produced < dst.len() {
while self.phase_q32 >= 1u64 << 32 {
if src.is_empty() {
return (consumed, produced);
}
self.last_in = src[0];
src = &src[1..];
consumed += 1;
self.phase_q32 -= 1u64 << 32;
}
let out = if self.phase_q32 == 0 {
self.last_in
} else {
if src.is_empty() {
return (consumed, produced);
}
let a = self.last_in as i64;
let b = src[0] as i64;
let frac = self.phase_q32 as i64; let interp = a + (((b - a) * frac + (1 << 31)) >> 32);
interp as i16
};
dst[produced] = out;
produced += 1;
self.phase_q32 += self.step_q32;
}
(consumed, produced)
}
pub fn max_output_for(&self, src_len: usize) -> usize {
((src_len as u64) * 12_000 / self.src_rate as u64) as usize + 1
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn passthrough_at_12k() {
let input: Vec<i16> = (0..100).collect();
let out = resample_to_12k(&input, 12000);
assert_eq!(out.len(), 100);
assert_eq!(out, input);
}
#[test]
fn downsample_from_48k() {
let input: Vec<i16> = (0..4800).map(|i| (i % 100) as i16).collect();
let out = resample_to_12k(&input, 48000);
assert_eq!(out.len(), 1200);
}
#[test]
fn downsample_from_44100() {
let input: Vec<i16> = vec![0i16; 44100];
let out = resample_to_12k(&input, 44100);
assert!((out.len() as i32 - 12000).abs() <= 1);
}
#[test]
fn streaming_passthrough_at_12k() {
let input: Vec<i16> = (0..100).collect();
let mut r = LinearResamplerI16To12k::new(12_000);
let mut out = vec![0i16; 100];
let (cons, prod) = r.process(&input, &mut out);
assert_eq!(prod, 100);
assert_eq!(&out[..prod], &input[..prod]);
assert_eq!(cons, prod);
}
#[test]
fn streaming_downsample_48k_to_12k() {
let input: Vec<i16> = (0..400).map(|i| (i * 100) as i16).collect();
let mut r = LinearResamplerI16To12k::new(48_000);
let mut out = vec![0i16; 100];
let (cons, prod) = r.process(&input, &mut out);
assert_eq!(prod, 100);
assert_eq!(out[0], 0);
assert_eq!(out[1], 400);
assert_eq!(out[2], 800);
assert_eq!(cons, 397); }
#[test]
fn streaming_chunked_matches_single_call() {
let input: Vec<i16> = (0..4410).map(|i| (i % 200) as i16).collect();
let mut r1 = LinearResamplerI16To12k::new(44_100);
let mut single = vec![0i16; 1500];
let (_c1, p1) = r1.process(&input, &mut single);
let mut r2 = LinearResamplerI16To12k::new(44_100);
let mut chunked = vec![0i16; 1500];
let mut produced = 0;
let mut src_pos = 0;
while src_pos < input.len() && produced < chunked.len() {
let chunk_end = (src_pos + 137).min(input.len()); let (c, p) = r2.process(&input[src_pos..chunk_end], &mut chunked[produced..]);
src_pos += c;
produced += p;
if c == 0 && p == 0 {
break; }
}
assert_eq!(produced, p1);
assert_eq!(&chunked[..produced], &single[..p1]);
}
#[test]
fn streaming_upsample_6k_to_12k() {
let input: Vec<i16> = vec![0, 100, 200, 300, 400, 500];
let mut r = LinearResamplerI16To12k::new(6_000);
let mut out = vec![0i16; 11];
let (_cons, prod) = r.process(&input, &mut out);
assert_eq!(out[0], 0);
assert_eq!(out[1], 50);
assert_eq!(out[2], 100);
assert_eq!(out[3], 150);
assert!(prod >= 10);
}
}