use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};
use audio_resample_bsd::{Resampler, RubatoResampler};
struct CountingAllocator;
static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
static COUNTING_ENABLED: AtomicUsize = AtomicUsize::new(0);
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if COUNTING_ENABLED.load(Ordering::Relaxed) == 1 {
ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
}
System.alloc(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
System.dealloc(ptr, layout);
}
}
#[global_allocator]
static A: CountingAllocator = CountingAllocator;
fn count_allocs<F: FnOnce()>(f: F) -> usize {
ALLOC_COUNT.store(0, Ordering::Relaxed);
COUNTING_ENABLED.store(1, Ordering::Relaxed);
f();
COUNTING_ENABLED.store(0, Ordering::Relaxed);
ALLOC_COUNT.load(Ordering::Relaxed)
}
#[test]
fn process_into_buffer_allocates_zero_on_rt_path() {
let mut rs = RubatoResampler::new(44_100.0, 48_000.0, 2, 1024).expect("construct");
let chunk = rs.input_chunk_size();
let ch = rs.channels();
let input: Vec<f32> = {
let mut v = vec![0.0_f32; chunk * ch];
for ch_idx in 0..ch {
for i in 0..chunk {
let t = i as f32;
let freq = if ch_idx == 0 { 1000.0_f32 } else { 2000.0 };
v[ch_idx * chunk + i] =
(2.0_f32 * std::f32::consts::PI * freq * t / 44_100.0).sin();
}
}
v
};
let mut output = vec![0.0_f32; rs.output_frames_max() * ch];
for _ in 0..16 {
let _ = rs.process_into_buffer(&input, &mut output);
}
let allocs = count_allocs(|| {
for _ in 0..32 {
let _ = rs.process_into_buffer(&input, &mut output);
}
});
assert_eq!(
allocs, 0,
"{allocs} allocations on the RT path — RT-safety violation \
(alloc must be 0 on process_into_buffer)"
);
}
#[test]
fn process_into_buffer_zero_alloc_upsample_96k() {
let mut rs = RubatoResampler::new(48_000.0, 96_000.0, 1, 512).expect("construct");
let chunk = rs.input_chunk_size();
let input: Vec<f32> = (0..chunk)
.map(|i| (2.0_f32 * std::f32::consts::PI * 440.0 * i as f32 / 48_000.0).sin())
.collect();
let mut output = vec![0.0_f32; rs.output_frames_max()];
for _ in 0..16 {
let _ = rs.process_into_buffer(&input, &mut output);
}
let allocs = count_allocs(|| {
for _ in 0..32 {
let _ = rs.process_into_buffer(&input, &mut output);
}
});
assert_eq!(allocs, 0, "48k->96k RT path allocated {allocs} times");
}
#[test]
fn process_into_buffer_zero_alloc_downsample_441_from_96() {
let mut rs = RubatoResampler::new(96_000.0, 44_100.0, 2, 1024).expect("construct");
let chunk = rs.input_chunk_size();
let ch = rs.channels();
let input = vec![0.5_f32; chunk * ch];
let mut output = vec![0.0_f32; rs.output_frames_max() * ch];
for _ in 0..16 {
let _ = rs.process_into_buffer(&input, &mut output);
}
let allocs = count_allocs(|| {
for _ in 0..32 {
let _ = rs.process_into_buffer(&input, &mut output);
}
});
assert_eq!(allocs, 0, "96k->44.1k RT path allocated {allocs} times");
}
#[test]
fn construction_allocates_but_isolated_from_rt_path() {
let allocs = count_allocs(|| {
let _rs = RubatoResampler::new(44_100.0, 48_000.0, 2, 1024).expect("construct");
});
assert!(
allocs > 0,
"construction must allocate (it is not RT-safe) — measurement gate check"
);
}