use skadoosh::vad::{VadEvent, VadSegmenter, FRAME_LEN};
fn frame(value: f32) -> [f32; FRAME_LEN] {
[value; FRAME_LEN]
}
fn push_n(seg: &mut VadSegmenter, frame: &[f32; FRAME_LEN], prob: f32, n: usize) -> Vec<VadEvent> {
(0..n).filter_map(|_| seg.push(frame, prob)).collect()
}
fn assert_frame_eq(audio: &[f32], f: usize, value: f32) {
assert!(
audio[f * FRAME_LEN..(f + 1) * FRAME_LEN]
.iter()
.all(|&x| x == value),
"frame {f} of segment is not all {value}"
);
}
#[test]
fn onset_fires_speech_start() {
let mut seg = VadSegmenter::new(0.5, 300);
for _ in 0..5 {
assert_eq!(seg.push(&frame(0.0), 0.0), None);
}
assert_eq!(seg.push(&frame(0.6), 0.9), Some(VadEvent::SpeechStart));
let mut seg = VadSegmenter::new(0.5, 300);
assert_eq!(seg.push(&frame(0.6), 0.5), Some(VadEvent::SpeechStart));
}
#[test]
fn closes_only_after_silence_ms() {
let mut seg = VadSegmenter::new(0.5, 300);
assert_eq!(seg.push(&frame(1.0), 0.9), Some(VadEvent::SpeechStart));
assert!(push_n(&mut seg, &frame(1.0), 0.9, 8).is_empty());
for i in 1..=9 {
assert!(
seg.push(&frame(0.0), 0.0).is_none(),
"segment closed early at silent frame {i}"
);
}
match seg.push(&frame(0.0), 0.0) {
Some(VadEvent::Segment(_)) => {}
other => panic!("expected Segment on 10th silent frame, got {other:?}"),
}
}
#[test]
fn silence_boundary_uses_exact_ms_math() {
let mut seg = VadSegmenter::new(0.5, 320);
assert_eq!(seg.push(&frame(1.0), 0.9), Some(VadEvent::SpeechStart));
assert!(push_n(&mut seg, &frame(1.0), 0.9, 8).is_empty());
for i in 1..=10 {
assert!(
seg.push(&frame(0.0), 0.0).is_none(),
"closed at silent frame {i} with exactly 320 ms of silence"
);
}
match seg.push(&frame(0.0), 0.0) {
Some(VadEvent::Segment(_)) => {}
other => panic!("expected Segment on 11th silent frame, got {other:?}"),
}
}
#[test]
fn segment_includes_preroll() {
let mut seg = VadSegmenter::new(0.5, 300);
for i in 0..12 {
assert_eq!(seg.push(&frame(i as f32), 0.0), None);
}
assert_eq!(seg.push(&frame(100.0), 0.9), Some(VadEvent::SpeechStart));
assert!(push_n(&mut seg, &frame(50.0), 0.9, 8).is_empty());
let events = push_n(&mut seg, &frame(0.0), 0.0, 10);
let [VadEvent::Segment(audio)] = &events[..] else {
panic!("expected exactly one Segment, got {events:?}");
};
assert_eq!(audio.len(), (8 + 9 + 10) * FRAME_LEN);
for f in 0..8 {
assert_frame_eq(audio, f, 4.0 + f as f32);
}
assert_frame_eq(audio, 8, 100.0);
for f in 9..17 {
assert_frame_eq(audio, f, 50.0);
}
for f in 17..27 {
assert_frame_eq(audio, f, 0.0);
}
}
#[test]
fn sub_min_length_blip_rejected() {
let mut seg = VadSegmenter::new(0.5, 300);
assert_eq!(seg.push(&frame(1.0), 0.9), Some(VadEvent::SpeechStart));
assert!(push_n(&mut seg, &frame(1.0), 0.9, 2).is_empty());
for i in 0..12 {
assert!(
seg.push(&frame(0.0), 0.0).is_none(),
"blip emitted an event at frame {i}"
);
}
}
#[test]
fn min_length_boundary() {
let mut seg = VadSegmenter::new(0.5, 300);
assert_eq!(seg.push(&frame(1.0), 0.9), Some(VadEvent::SpeechStart));
assert!(push_n(&mut seg, &frame(1.0), 0.9, 6).is_empty());
assert!(push_n(&mut seg, &frame(0.0), 0.0, 10).is_empty());
let mut seg = VadSegmenter::new(0.5, 300);
assert_eq!(seg.push(&frame(1.0), 0.9), Some(VadEvent::SpeechStart));
assert!(push_n(&mut seg, &frame(1.0), 0.9, 7).is_empty());
let events = push_n(&mut seg, &frame(0.0), 0.0, 10);
let [VadEvent::Segment(audio)] = &events[..] else {
panic!("expected Segment for 256 ms burst, got {events:?}");
};
assert_eq!(audio.len(), (8 + 10) * FRAME_LEN);
}
#[test]
fn second_burst_yields_second_segment() {
let mut seg = VadSegmenter::new(0.5, 300);
assert_eq!(seg.push(&frame(1.0), 0.9), Some(VadEvent::SpeechStart));
assert!(push_n(&mut seg, &frame(1.0), 0.9, 8).is_empty());
let events = push_n(&mut seg, &frame(0.0), 0.0, 10);
let [VadEvent::Segment(_)] = &events[..] else {
panic!("expected first Segment, got {events:?}");
};
assert_eq!(seg.push(&frame(2.0), 0.9), Some(VadEvent::SpeechStart));
assert!(push_n(&mut seg, &frame(2.0), 0.9, 8).is_empty());
let events = push_n(&mut seg, &frame(0.0), 0.0, 10);
let [VadEvent::Segment(audio)] = &events[..] else {
panic!("expected second Segment, got {events:?}");
};
assert_eq!(audio.len(), (8 + 9 + 10) * FRAME_LEN);
for f in 0..8 {
assert_frame_eq(audio, f, 0.0);
}
assert_frame_eq(audio, 8, 2.0);
}
#[test]
fn preroll_partially_filled_at_stream_start() {
let mut seg = VadSegmenter::new(0.5, 300);
assert_eq!(seg.push(&frame(7.0), 0.0), None);
assert_eq!(seg.push(&frame(8.0), 0.0), None);
assert_eq!(seg.push(&frame(9.0), 0.0), None);
assert_eq!(seg.push(&frame(100.0), 0.9), Some(VadEvent::SpeechStart));
assert!(push_n(&mut seg, &frame(50.0), 0.9, 8).is_empty());
let events = push_n(&mut seg, &frame(0.0), 0.0, 10);
let [VadEvent::Segment(audio)] = &events[..] else {
panic!("expected Segment, got {events:?}");
};
assert_eq!(audio.len(), (3 + 9 + 10) * FRAME_LEN);
assert_frame_eq(audio, 0, 7.0);
assert_frame_eq(audio, 1, 8.0);
assert_frame_eq(audio, 2, 9.0);
assert_frame_eq(audio, 3, 100.0);
}