use audio_core_bsd::AudioFrame;
use audio_resample_bsd::{Resampler, RubatoResampler};
fn sine_frame(channels: u16, num_frames: usize, sample_rate: u32, freqs: &[f32]) -> AudioFrame {
let mut frame = AudioFrame::silence(channels, num_frames, sample_rate);
let nf = frame.num_frames();
for ch_idx in 0..channels as usize {
let freq = freqs.get(ch_idx).copied().unwrap_or(1000.0);
let slice = frame.channel_slice_mut(ch_idx);
for (i, s) in slice.iter_mut().enumerate().take(nf) {
*s = (2.0_f32 * std::f32::consts::PI * freq * i as f32 / sample_rate as f32).sin();
}
}
frame
}
#[test]
fn audio_frame_samples_pass_directly_to_resampler() {
let in_rate = 44_100_u32;
let out_rate = 48_000_u32;
let chunk = 1024_usize;
let ch = 2_u16;
let mut rs = RubatoResampler::new(f64::from(in_rate), f64::from(out_rate), ch.into(), chunk)
.expect("construct");
let frame = sine_frame(ch, chunk, in_rate, &[1000.0, 2000.0]);
assert_eq!(frame.samples.len(), chunk * ch as usize);
let mut out_samples = vec![0.0_f32; rs.output_frames_max() * ch as usize];
let mut last_written = 0;
for _ in 0..16 {
last_written = rs
.process_into_buffer(&frame.samples, &mut out_samples)
.expect("process");
}
assert!(last_written > 0, "output frames must be produced");
let out_len = ch as usize * last_written;
let out_frame = AudioFrame::from_planar(ch, out_rate, out_samples[..out_len].to_vec());
assert_eq!(out_frame.channels, ch);
assert_eq!(out_frame.num_frames(), last_written);
assert_eq!(out_frame.channel_slice(0).len(), last_written);
assert_eq!(out_frame.channel_slice(1).len(), last_written);
}
#[test]
fn audio_frame_planar_disjointness_survives_resample() {
let in_rate = 48_000_u32;
let ch = 2_u16;
let chunk = 512;
let mut rs =
RubatoResampler::new(f64::from(in_rate), 96_000.0, ch.into(), chunk).expect("construct");
let mut frame = sine_frame(ch, chunk, in_rate, &[500.0, 500.0]);
for s in frame.channel_slice_mut(0) {
*s = 0.0;
}
assert!(frame.channel_slice(1).iter().any(|&s| s.abs() > 1e-6));
let mut out = vec![0.0_f32; rs.output_frames_max() * ch as usize];
let mut written = 0;
for _ in 0..16 {
written = rs
.process_into_buffer(&frame.samples, &mut out)
.expect("process");
}
assert!(written > 0);
let out_frame = AudioFrame::from_planar(ch, 96_000, out[..ch as usize * written].to_vec());
assert_eq!(
out_frame.channel_slice(0).len(),
out_frame.channel_slice(1).len()
);
}
#[test]
fn round_trip_audio_frame_preserves_structure() {
let rate = 44_100_u32;
let chunk = 1024;
let ch = 1_u16;
let mut up = RubatoResampler::new(f64::from(rate), 48_000.0, ch.into(), chunk).unwrap();
let down_chunk = 1024;
let mut down = RubatoResampler::new(48_000.0, f64::from(rate), ch.into(), down_chunk).unwrap();
let frame = sine_frame(ch, chunk, rate, &[1000.0]);
let mut mid: Vec<f32> = Vec::new();
let mut mid_tmp = vec![0.0_f32; up.output_frames_max() * ch as usize];
for _ in 0..32 {
let w = up
.process_into_buffer(&frame.samples, &mut mid_tmp)
.unwrap();
mid.extend_from_slice(&mid_tmp[..w * ch as usize]);
}
let mut out: Vec<f32> = Vec::new();
let mut down_tmp = vec![0.0_f32; down.output_frames_max() * ch as usize];
let stride = down_chunk * ch as usize;
let mut pos = 0;
while pos + stride <= mid.len() {
let w = down
.process_into_buffer(&mid[pos..pos + stride], &mut down_tmp)
.unwrap();
out.extend_from_slice(&down_tmp[..w * ch as usize]);
pos += stride;
}
assert!(!out.is_empty(), "round-trip output must not be empty");
let roundtrip = AudioFrame::from_planar(ch, rate, out);
assert_eq!(roundtrip.channels, ch);
assert!(roundtrip.num_frames() > 0);
}