#[cfg(test)]
mod block_tests {
use super::super::test_util::infra::{TrackingGuard, get_alloc_count};
use super::super::*;
use crate::common::params::AdaptiveComputeMode;
use crate::common::spsc::RtStatusFlags;
use crate::dsp::adaptive::AdaptiveCompute;
use crate::dsp::gate::{DynamicHysteresis, GateParams};
use crate::dsp::oversample::{OversampleEngine, OversampleFactor};
use crate::dsp::resampler::NamResampler;
use crate::loader::dispatcher::build_model;
use crate::loader::nam_json::parse_nam_json;
use crate::models::StaticModel;
use proptest::prelude::*;
use std::fs;
use std::path::PathBuf;
use std::sync::atomic::Ordering;
fn get_test_model_path(name: &str) -> PathBuf {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("tests/fixtures/models");
path.push(name);
path
}
fn load_test_model(name: &str) -> Box<StaticModel> {
let path = get_test_model_path(name);
let json_data = fs::read_to_string(path).expect("Failed to read model file");
let model_data = parse_nam_json(&json_data).expect("Failed to process model JSON");
build_model(&model_data).expect("Failed to build model")
}
fn run_block_size_test(model_name: Option<&str>, block_size: usize) {
run_block_size_test_with_iterations(model_name, block_size, 1);
}
fn run_block_size_test_with_iterations(
model_name: Option<&str>,
block_size: usize,
iterations: usize,
) {
if block_size == 0 {
return;
}
let n = block_size.min(MAX_BRIDGE_BUF);
let mut model = model_name.map(load_test_model);
if let Some(ref mut m) = model {
m.prewarm(2048);
}
let mut resampler = NamResampler::new(48000, 48000, n).unwrap();
let rt_status = RtStatusFlags::default();
let mut bridge = Box::new(DspBridge {
buffers: [
BridgeBuffer {
buf_l: [0.0; MAX_BRIDGE_BUF],
buf_r: [0.0; MAX_BRIDGE_BUF],
n_samples: 0,
},
BridgeBuffer {
buf_l: [0.0; MAX_BRIDGE_BUF],
buf_r: [0.0; MAX_BRIDGE_BUF],
n_samples: 0,
},
],
active_read_idx: std::sync::atomic::AtomicUsize::new(0),
generation: std::sync::atomic::AtomicU64::new(0),
consumed_gen: std::sync::atomic::AtomicU64::new(0),
dropped_frames: std::sync::atomic::AtomicU32::new(0),
});
let mut resamp_mid_l = vec![0.0; MAX_RESAMP_BUF];
let mut resamp_mid_r = vec![0.0; MAX_RESAMP_BUF];
let mut resamp_out_l = vec![0.0; MAX_RESAMP_BUF];
let mut resamp_out_r = [0.0; MAX_RESAMP_BUF];
let mut model_out_l = [0.0; MAX_RESAMP_BUF];
let mut model_out_r = [0.0; MAX_RESAMP_BUF];
let gate_params = GateParams::default();
let mut silence_hysteresis = DynamicHysteresis::new();
let mut mono_hysteresis = DynamicHysteresis::new();
let mut process_mono = false;
let mut samples_l = vec![0.1; n];
let mut samples_r = vec![0.1; n];
let mut os_engine_l = OversampleEngine::new(OversampleFactor::Off, MAX_RESAMP_BUF).unwrap();
let mut os_engine_r = OversampleEngine::new(OversampleFactor::Off, MAX_RESAMP_BUF).unwrap();
let _guard = TrackingGuard::new();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Off);
for _ in 0..iterations {
let ctx = DspPipelineContext {
resampler: &mut resampler,
os_l: &mut os_engine_l,
os_r: &mut os_engine_r,
active_model_l: &mut model,
active_model_r: &mut None,
input_gain_mult: 1.0,
output_gain_mult: 1.0,
gate_params: &gate_params,
silence_hysteresis: &mut silence_hysteresis,
mono_hysteresis: &mut mono_hysteresis,
threshold_open_sq: 0.0,
threshold_close_sq: 0.0,
process_mono: &mut process_mono,
rt_status: &rt_status,
adaptive: &mut adaptive,
bridge_writer: unsafe {
Some(DspBridgeWriter::new(&mut *bridge as *mut DspBridge))
},
conv: None,
};
let mut os_buf: [f32; MAX_RESAMP_BUF * 4] = [0.0f32; MAX_RESAMP_BUF * 4];
let (os_in_l_slice, rest) = os_buf.split_at_mut(MAX_RESAMP_BUF);
let (os_in_r_slice, rest) = rest.split_at_mut(MAX_RESAMP_BUF);
let (os_model_l_slice, os_model_r_slice) = rest.split_at_mut(MAX_RESAMP_BUF);
let bufs = DspBuffers {
resamp_mid_l: &mut resamp_mid_l,
resamp_mid_r: &mut resamp_mid_r,
resamp_out_l: &mut resamp_out_l,
resamp_out_r: &mut resamp_out_r,
model_out_l: &mut model_out_l,
model_out_r: &mut model_out_r,
os_in_l: os_in_l_slice,
os_in_r: os_in_r_slice,
os_model_l: os_model_l_slice,
os_model_r: os_model_r_slice,
};
capture_dsp_pipeline(&mut samples_l, &mut samples_r, n, ctx, bufs, 48000);
}
let allocs = get_alloc_count();
drop(_guard);
assert_eq!(
allocs, 0,
"Allocation detected in {} iterations",
iterations
);
let read_idx = bridge.active_read_idx.load(Ordering::Acquire);
let out_buf = &bridge.buffers[read_idx];
assert_eq!(out_buf.n_samples as usize, n);
for i in 0..n {
assert!(out_buf.buf_l[i].is_finite());
assert!(out_buf.buf_r[i].is_finite());
}
}
#[test]
fn test_unconventional_block_sizes_lstm() {
let sizes = [1, 3, 7, 8, 9, 17, 33, 53, 64, 128, 256, 512];
for &size in &sizes {
run_block_size_test(Some("BossLSTM-1x16.nam"), size);
}
}
#[test]
fn test_unconventional_block_sizes_wavenet() {
let sizes = [1, 3, 7, 8, 9, 17, 33, 53, 64, 128, 256, 512];
for &size in &sizes {
run_block_size_test(Some("BossWN-nano.nam"), size);
}
}
#[test]
fn test_zero_alloc_edge_cases() {
run_block_size_test(Some("BossWN-nano.nam"), 1);
run_block_size_test(Some("BossWN-nano.nam"), MAX_BRIDGE_BUF);
}
#[test]
fn test_zero_alloc_stress_edge_cases() {
run_block_size_test_with_iterations(Some("BossWN-nano.nam"), 1, 1000);
run_block_size_test_with_iterations(Some("BossWN-nano.nam"), MAX_BRIDGE_BUF, 100);
}
proptest! {
#![proptest_config(ProptestConfig {
failure_persistence: Some(Box::new(proptest::test_runner::FileFailurePersistence::SourceParallel("tests/proptest-regressions"))),
.. ProptestConfig::with_cases(2_000)
})]
#[test]
#[ignore]
fn test_random_block_sizes_proptest(size in 1..8192usize) {
run_block_size_test(Some("BossWN-nano.nam"), size);
}
}
}