use br41ndmg::{PolyphaseFilterParams, Resampler, Window};
const EPSILON: f32 = 1.0e-6;
fn assert_close(actual: &[f32], expected: &[f32]) {
assert_eq!(actual.len(), expected.len());
for (index, (&a, &b)) in actual.iter().zip(expected).enumerate() {
assert!(
(a - b).abs() <= EPSILON,
"sample {index} differed: {a} vs {b}"
);
}
}
fn deinterleave(input: &[f32], channels: usize) -> Vec<Vec<f32>> {
let mut per_channel = vec![Vec::with_capacity(input.len() / channels); channels];
for frame in input.chunks_exact(channels) {
for (channel, sample) in frame.iter().enumerate() {
per_channel[channel].push(*sample);
}
}
per_channel
}
fn reinterleave(channels: &[Vec<f32>]) -> Vec<f32> {
let frames = channels[0].len();
let mut output = Vec::with_capacity(frames * channels.len());
for frame in 0..frames {
for channel in channels {
output.push(channel[frame]);
}
}
output
}
#[test]
fn stereo_interleaved_matches_per_channel_resampling() {
let mut input = Vec::new();
for index in 0..64 {
let t = index as f32;
input.push((t * 0.21).sin() * 0.7 + (t * 0.03).cos() * 0.1);
input.push((t * 0.17).cos() * 0.4 - (t * 0.05).sin() * 0.2);
}
let resampler = Resampler::new(44_100.0, 48_000.0).unwrap();
let actual = resampler.resample_interleaved(&input, 2).unwrap();
let expected = reinterleave(
&deinterleave(&input, 2)
.into_iter()
.map(|channel| resampler.resample(&channel).unwrap())
.collect::<Vec<_>>(),
);
assert_close(&actual, &expected);
}
#[test]
fn multichannel_interleaved_matches_per_channel_resampling() {
let mut input = Vec::new();
for index in 0..48 {
let t = index as f32;
input.push((t * 0.11).sin() * 0.6);
input.push((t * 0.07).cos() * 0.5);
input.push(((index % 9) as f32 - 4.0) * 0.1);
}
let resampler = Resampler::new(48_000.0, 44_100.0).unwrap();
let actual = resampler.resample_interleaved(&input, 3).unwrap();
let expected = reinterleave(
&deinterleave(&input, 3)
.into_iter()
.map(|channel| resampler.resample(&channel).unwrap())
.collect::<Vec<_>>(),
);
assert_close(&actual, &expected);
}
#[test]
fn resampler_accepts_f32_sample_rates() {
let resampler = Resampler::new(44_100.0_f32, 48_000.0_f32).unwrap();
assert!((resampler.input_rate() - 44_100.0).abs() <= f64::EPSILON);
assert!((resampler.output_rate() - 48_000.0).abs() <= f64::EPSILON);
}
#[test]
fn resampler_accepts_custom_filter_params() {
let params = PolyphaseFilterParams {
phases: 64,
taps_per_phase: 31,
window: Window::Hann,
};
let resampler = Resampler::with_filter_params(44_100.0, 48_000.0, params).unwrap();
assert_eq!(resampler.filter_params(), params);
}
#[test]
fn resampler_rejects_invalid_filter_params() {
let error = Resampler::with_filter_params(
44_100.0,
48_000.0,
PolyphaseFilterParams {
taps_per_phase: 32,
..PolyphaseFilterParams::default()
},
)
.unwrap_err();
assert!(error.to_string().contains("tap count"));
}
#[test]
fn resampler_rejects_invalid_kaiser_window() {
let error = Resampler::with_filter_params(
44_100.0,
48_000.0,
PolyphaseFilterParams {
window: Window::Kaiser { beta: f64::NAN },
..PolyphaseFilterParams::default()
},
)
.unwrap_err();
assert!(error.to_string().contains("kaiser beta"));
}