use timestretch::analysis::transient::detect_transients;
use timestretch::io::wav::read_wav_file;
const FIXTURE: &str = "test_audio/dense_mastered_128bpm.wav";
const SR: u32 = 44_100;
fn load_dense_mid() -> Vec<f32> {
let buffer = read_wav_file(FIXTURE).unwrap_or_else(|e| {
panic!("missing {FIXTURE}: run `cargo run --example generate_test_audio` first ({e})")
});
timestretch::downmix_to_mid(&buffer.data, buffer.channels.count())
}
#[test]
fn dense_material_produces_onsets_at_all_sensitivities() {
let mid = load_dense_mid();
let seconds = mid.len() as f64 / SR as f64;
let mut prev = 0usize;
for &sensitivity in &[0.2f32, 0.4, 0.8] {
let map = detect_transients(&mid, SR, 2048, 512, sensitivity);
let rate = map.onsets.len() as f64 / seconds;
assert!(
map.onsets.len() >= 8,
"sensitivity {sensitivity}: dense material must yield onsets, got {}",
map.onsets.len()
);
assert!(
(0.5..=15.0).contains(&rate),
"sensitivity {sensitivity}: onset rate {rate:.2}/s outside plausible band"
);
assert!(
map.onsets.len() >= prev,
"onset count must be monotone in sensitivity"
);
prev = map.onsets.len();
}
}
#[test]
fn dense_material_analyze_produces_usable_artifact() {
let mid = load_dense_mid();
let artifact = timestretch::analyze_for_dj(&mid, SR);
assert!(
artifact.bpm > 0.0,
"dense material must yield a BPM, got {}",
artifact.bpm
);
let bpm = artifact.bpm;
let near = |target: f64| (bpm - target).abs() / target < 0.04;
assert!(
near(128.0) || near(64.0) || near(256.0),
"dense BPM {bpm} not near 128 or an octave"
);
assert!(
artifact.is_usable(SR, 0.35),
"dense artifact not usable: confidence {}, {} beats, {} onsets",
artifact.confidence,
artifact.beat_positions.len(),
artifact.transient_onsets.len()
);
assert!(
artifact.confidence >= 0.5,
"dense material confidence too low: {}",
artifact.confidence
);
}