All checks were successful
This integration test proves wedge #1: loud synthetic caller audio (samples = 1000, well above VAD_RMS_THRESHOLD = 500.0) sent through LocalVadReflex<Reflex<TapAudioPipe>> trips the local VAD and kills playout within one tick, without any brain advisory. Cites README:98-100 and ARCHITECTURE.md:79-81 ("local reflexes that don't need the brain"); see slice-4 spec §3.4, §6.1, and §7 done-criteria #8. Signed-off-by: Aaron D. Lee <himself@adlee.work>
62 lines
2.2 KiB
Rust
62 lines
2.2 KiB
Rust
// crates/rutster/tests/barge_in_integration.rs
|
||
//
|
||
// PRIMARY-path barge-in e2e proof (slice-4 spec §3.4, §6.1, §7 done-criteria #8).
|
||
//
|
||
// Constructs LocalVadReflex<Reflex<TapAudioPipe>> directly and proves that loud
|
||
// caller audio kills playout within one tick, with only the local VAD as the
|
||
// trigger source — no brain advisory is involved. This is wedge #1 from
|
||
// README:98-100 and ARCHITECTURE.md:79-81.
|
||
|
||
use rutster_media::{AudioSink, AudioSource, PcmFrame, VAD_DEBOUNCE_FRAMES};
|
||
use rutster_media::{LocalVadReflex, Reflex, ReflexMetrics};
|
||
use rutster_tap::{TapAudioPipe, TapMetrics};
|
||
use tokio::sync::mpsc;
|
||
|
||
#[tokio::test]
|
||
async fn primary_path_local_vad_kills_playout_without_brain() {
|
||
let (tx_pcm_in, _rx_pcm_in) = mpsc::channel(32);
|
||
let (tx_audio_out, rx_audio_out) = mpsc::channel(32);
|
||
let tap_metrics = TapMetrics::new();
|
||
let pipe = TapAudioPipe::new(tx_pcm_in, rx_audio_out, tap_metrics);
|
||
|
||
let (advisory_tx, advisory_rx) = mpsc::channel(16);
|
||
let reflex_metrics = ReflexMetrics::new();
|
||
let reflex = Reflex::new(pipe, advisory_rx, reflex_metrics.clone());
|
||
let mut stack = LocalVadReflex::new(reflex, advisory_tx);
|
||
|
||
// Pre-load a brain audio_out frame into the ring (drain via next_pcm_frame).
|
||
tx_audio_out.send(PcmFrame::zeroed()).await.unwrap();
|
||
let _ = stack.next_pcm_frame(); // drain into ring
|
||
stack.next_pcm_frame(); // pop one (brain is playing)
|
||
|
||
// Loud caller audio × N → local VAD trips.
|
||
let mut loud = PcmFrame::zeroed();
|
||
for s in loud.samples.iter_mut() {
|
||
*s = 1000;
|
||
}
|
||
for _ in 0..VAD_DEBOUNCE_FRAMES {
|
||
stack.on_pcm_frame(loud.clone());
|
||
}
|
||
|
||
// Next playout tick: kill applied, None returned (ring cleared).
|
||
let f = stack.next_pcm_frame();
|
||
assert!(
|
||
f.is_none(),
|
||
"primary-path local VAD must kill playout within 1 tick"
|
||
);
|
||
assert_eq!(
|
||
reflex_metrics
|
||
.barge_in_count
|
||
.load(std::sync::atomic::Ordering::Relaxed),
|
||
1
|
||
);
|
||
|
||
// Fresh brain audio_out → resume.
|
||
tx_audio_out.send(PcmFrame::zeroed()).await.unwrap();
|
||
let resumed = stack.next_pcm_frame();
|
||
assert!(
|
||
resumed.is_some(),
|
||
"first fresh audio_out post-barge must resume playout"
|
||
);
|
||
}
|