use super::*;
use rubato::Resampler;
#[test]
#[cfg_attr(miri, ignore = "rubato sinc resampler is too slow under Miri")]
fn test_resample_downsample_length() {
let input: Vec<f32> = (0..4800).map(|i| (i as f32).sin()).collect();
let output = resample(&input, SampleRate(48000), SampleRate(16000)).unwrap();
assert!(!output.is_empty());
assert!(
output.len() > 1400 && output.len() < 1700,
"Unexpected output length: {}",
output.len()
);
}
#[test]
#[cfg_attr(miri, ignore = "rubato sinc resampler is too slow under Miri")]
fn test_resample_upsample_length() {
let input: Vec<f32> = (0..800).map(|i| (i as f32).sin()).collect();
let output = resample(&input, SampleRate(8000), SampleRate(16000)).unwrap();
assert!(!output.is_empty());
assert!(
output.len() > 1200 && output.len() < 1700,
"Unexpected output length: {}",
output.len()
);
}
#[test]
#[cfg_attr(miri, ignore = "rubato sinc resampler is too slow under Miri")]
fn test_resample_preserves_dc() {
let input = vec![0.5_f32; 4800];
let output = resample(&input, SampleRate(48000), SampleRate(16000)).unwrap();
let start = output.len() / 10;
let end = output.len() - start;
for &sample in &output[start..end] {
assert!(
(sample - 0.5).abs() < 0.05,
"DC signal not preserved: {sample}"
);
}
}
#[test]
fn test_resample_empty() {
let output = resample(&[], SampleRate(48000), SampleRate(16000)).unwrap();
assert!(output.is_empty());
}
#[test]
fn test_resample_zero_rate_returns_empty() {
let input = vec![1.0, 2.0, 3.0];
assert!(
resample(&input, SampleRate(0), SampleRate(16000))
.unwrap()
.is_empty()
);
assert!(
resample(&input, SampleRate(16000), SampleRate(0))
.unwrap()
.is_empty()
);
}
#[test]
fn test_resample_same_rate() {
let input = vec![1.0, 2.0, 3.0, 4.0];
let output = resample(&input, SampleRate(16000), SampleRate(16000)).unwrap();
assert_eq!(output.len(), input.len());
for (a, b) in input.iter().zip(output.iter()) {
assert!((a - b).abs() < 1e-5);
}
}
#[test]
#[cfg_attr(miri, ignore = "rubato sinc resampler is too slow under Miri")]
fn test_resample_nan_input() {
let input = vec![f32::NAN; 1000];
let output = resample(&input, SampleRate(48000), SampleRate(16000)).unwrap();
assert!(!output.is_empty());
for &s in &output {
assert!(s.is_finite(), "NaN should be sanitized to zero, got {s}");
}
}
#[test]
#[cfg_attr(miri, ignore = "rubato sinc resampler is too slow under Miri")]
fn test_resample_infinity_input() {
let input = vec![f32::INFINITY; 500];
let output = resample(&input, SampleRate(48000), SampleRate(16000)).unwrap();
assert!(!output.is_empty());
for &s in &output {
assert!(
s.is_finite(),
"Infinity should be sanitized to zero, got {s}"
);
}
}
#[test]
#[cfg_attr(miri, ignore = "rubato sinc resampler is too slow under Miri")]
fn test_resample_mixed_nan_normal() {
let mut input = vec![0.5_f32; 480];
input[100] = f32::NAN;
input[200] = f32::NEG_INFINITY;
let output = resample(&input, SampleRate(48000), SampleRate(16000)).unwrap();
assert!(!output.is_empty());
for &s in &output {
assert!(s.is_finite(), "Non-finite values should be sanitized");
}
}
#[test]
fn test_sample_rate_new_zero_errors() {
let result = SampleRate::new(0);
assert!(result.is_err(), "zero sample rate must error");
}
#[test]
fn test_sample_rate_new_positive_ok() {
let sr = SampleRate::new(16000).unwrap();
assert_eq!(sr.get(), 16000);
assert_eq!(sr.0, 16000);
}
#[test]
fn test_resample_with_cache_empty_clears_buffer() {
let mut cache: Option<rubato::Async<f32>> = None;
let mut out = vec![1.0, 2.0, 3.0];
resample_with_cache(
Vec::new(),
SampleRate(48000),
SampleRate(16000),
&mut cache,
&mut out,
)
.unwrap();
assert!(out.is_empty(), "empty input must clear the output buffer");
assert!(cache.is_none(), "no resampler created for empty input");
}
#[test]
fn test_resample_with_cache_zero_rate_clears_buffer() {
let mut cache: Option<rubato::Async<f32>> = None;
let mut out = vec![9.0];
resample_with_cache(
vec![1.0, 2.0],
SampleRate(0),
SampleRate(16000),
&mut cache,
&mut out,
)
.unwrap();
assert!(out.is_empty());
let mut out2 = vec![9.0];
resample_with_cache(
vec![1.0, 2.0],
SampleRate(16000),
SampleRate(0),
&mut cache,
&mut out2,
)
.unwrap();
assert!(out2.is_empty());
}
#[test]
fn test_resample_with_cache_same_rate_passthrough() {
let mut cache: Option<rubato::Async<f32>> = None;
let input = vec![1.0, 2.0, 3.0, 4.0];
let mut out = Vec::new();
resample_with_cache(
input.clone(),
SampleRate(16000),
SampleRate(16000),
&mut cache,
&mut out,
)
.unwrap();
assert_eq!(out, input, "same rate must pass through unchanged");
assert!(
cache.is_none(),
"no resampler created for same-rate passthrough"
);
}
#[test]
#[cfg_attr(miri, ignore = "rubato sinc resampler is too slow under Miri")]
fn test_resample_with_cache_sanitizes_non_finite() {
let mut cache: Option<rubato::Async<f32>> = None;
let mut input = vec![0.5_f32; 480];
input[10] = f32::NAN;
input[20] = f32::INFINITY;
input[30] = f32::NEG_INFINITY;
let mut out = Vec::new();
resample_with_cache(
input,
SampleRate(48000),
SampleRate(16000),
&mut cache,
&mut out,
)
.unwrap();
assert!(!out.is_empty());
assert!(
cache.is_some(),
"resampler should be cached after first use"
);
for &s in &out {
assert!(
s.is_finite(),
"non-finite values must be sanitized, got {s}"
);
}
}
#[test]
#[cfg_attr(miri, ignore = "rubato sinc resampler is too slow under Miri")]
fn test_resample_with_cache_growing_chunks_match_one_shot() {
use std::f32::consts::PI;
let n = 48_000usize;
let signal: Vec<f32> = (0..n)
.map(|i| {
let t = i as f32 / 48_000.0;
0.5 * (2.0 * PI * 440.0 * t).sin() + 0.3 * (2.0 * PI * 1_200.0 * t).sin()
})
.collect();
let reference = resample(&signal, SampleRate(48_000), SampleRate(16_000)).unwrap();
let mut cache: Option<rubato::Async<f32>> = None;
let mut out = Vec::new();
let mut streamed = Vec::new();
let mut pos = 0usize;
let mut chunk = 480usize;
while pos < signal.len() {
let end = (pos + chunk).min(signal.len());
resample_with_cache(
signal[pos..end].to_vec(),
SampleRate(48_000),
SampleRate(16_000),
&mut cache,
&mut out,
)
.unwrap();
streamed.extend_from_slice(&out);
pos = end;
chunk += 480;
}
assert!(streamed.iter().all(|s| s.is_finite()));
let len_diff = reference.len().abs_diff(streamed.len());
assert!(
len_diff <= 2,
"chunked stream diverged from one-shot reference: {} vs {} samples",
streamed.len(),
reference.len()
);
let skip = 128;
let cmp_len = reference.len().min(streamed.len());
assert!(cmp_len > skip + 1_000, "not enough overlap to compare");
let mut max_diff = 0.0f32;
let mut max_at = 0usize;
for i in skip..cmp_len {
let d = (reference[i] - streamed[i]).abs();
if d > max_diff {
max_diff = d;
max_at = i;
}
}
assert!(
max_diff < 1e-3,
"seam discontinuity: max |streamed - reference| = {max_diff} at sample {max_at}"
);
}
#[test]
#[cfg_attr(miri, ignore = "rubato sinc resampler is too slow under Miri")]
fn test_resample_with_cache_growth_keeps_instance() {
let mut cache: Option<rubato::Async<f32>> = None;
let mut out = Vec::new();
let feed = |cache: &mut Option<rubato::Async<f32>>, out: &mut Vec<f32>, n: usize, seed: f32| {
let input: Vec<f32> = (0..n).map(|i| (i as f32 * seed).sin()).collect();
resample_with_cache(input, SampleRate(48_000), SampleRate(16_000), cache, out).unwrap();
};
feed(&mut cache, &mut out, 480, 0.01);
let capacity = cache.as_ref().unwrap().input_frames_max();
assert!(capacity >= 480);
feed(&mut cache, &mut out, 960, 0.02);
assert_eq!(
cache.as_ref().unwrap().input_frames_max(),
capacity,
"resampler recreated on frame growth"
);
feed(&mut cache, &mut out, 2_000, 0.03);
assert_eq!(cache.as_ref().unwrap().input_frames_max(), capacity);
feed(&mut cache, &mut out, capacity + 1_001, 0.01);
assert_eq!(
cache.as_ref().unwrap().input_frames_max(),
capacity,
"oversized frame must be split, not trigger recreation"
);
assert!(out.iter().all(|s| s.is_finite()));
feed(&mut cache, &mut out, capacity + 1, 0.02);
assert_eq!(cache.as_ref().unwrap().input_frames_max(), capacity);
assert!(out.iter().all(|s| s.is_finite()));
}
#[test]
#[cfg_attr(miri, ignore = "rubato sinc resampler is too slow under Miri")]
fn test_resample_with_cache_reuses_across_chunk_sizes() {
let mut cache: Option<rubato::Async<f32>> = None;
let mut out = Vec::new();
let input1: Vec<f32> = (0..480).map(|i| (i as f32 * 0.01).sin()).collect();
resample_with_cache(
input1,
SampleRate(48000),
SampleRate(16000),
&mut cache,
&mut out,
)
.unwrap();
assert!(cache.is_some());
let len_first = out.len();
assert!(len_first > 0);
let input2: Vec<f32> = (0..480).map(|i| (i as f32 * 0.02).cos()).collect();
resample_with_cache(
input2,
SampleRate(48000),
SampleRate(16000),
&mut cache,
&mut out,
)
.unwrap();
assert!(cache.is_some());
assert!(!out.is_empty());
let input3: Vec<f32> = (0..960).map(|i| (i as f32 * 0.01).sin()).collect();
resample_with_cache(
input3,
SampleRate(48000),
SampleRate(16000),
&mut cache,
&mut out,
)
.unwrap();
assert!(cache.is_some());
assert!(!out.is_empty());
for &s in &out {
assert!(s.is_finite());
}
}