From 111fbafdb283880255b5c0cede930805b1e8063c Mon Sep 17 00:00:00 2001 From: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz> Date: Thu, 22 Oct 2026 09:10:12 -0400 Subject: [PATCH 1/2] berd-call: replay VAD gates and compare offline profiles Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz> --- src-tauri/crates/berd-call/VAD_REPLAY.md | 15 + src-tauri/crates/berd-call/src/cli_help.rs | 2 + src-tauri/crates/berd-call/src/lib.rs | 1 + src-tauri/crates/berd-call/src/main.rs | 8 + src-tauri/crates/berd-call/src/vad_replay.rs | 488 ++++++++++++++++++ .../crates/berd-call/src/vad_replay_cli.rs | 57 ++ .../crates/berd-call/tests/vad_replay_cli.rs | 69 +++ 7 files changed, 640 insertions(+) create mode 100644 src-tauri/crates/berd-call/VAD_REPLAY.md create mode 100644 src-tauri/crates/berd-call/src/vad_replay.rs create mode 100644 src-tauri/crates/berd-call/src/vad_replay_cli.rs create mode 100644 src-tauri/crates/berd-call/tests/vad_replay_cli.rs diff --git a/src-tauri/crates/berd-call/VAD_REPLAY.md b/src-tauri/crates/berd-call/VAD_REPLAY.md new file mode 100644 index 000000000..b3d5047ca --- /dev/null +++ b/src-tauri/crates/berd-call/VAD_REPLAY.md @@ -0,0 +1,15 @@ +# Offline VAD replay + +Proposed integration, October 22, 2026. This command evaluates continuous-recognition activity gates. Capture-directory ingestion is being coordinated with the recording implementation; the current fixture entry point accepts normalized JSON. + +Run `berd-call vad-replay INPUT.json --candidates CANDIDATES.json`. It emits JSONL, one report for the recorded settings followed by each named candidate, without opening a microphone or reading/writing live profiles. Candidate settings must be static. Recorded settings may include dynamic values because each frame records the values actually applied; replay does not simulate recognizer-driven learning. + +Input has `schema_version: 1`, `session_id`, `held_out`, `frames`, `labels` and `reply_waiting`. Frames contain `end_ms`, `epoch`, `confidence`, `mode` (listening/interruption), and `settings` using the existing DetectionSettings schema. Each frame represents 16 ms. Epoch changes are explicit live gate resets; missing frame intervals leave state unknown until a full onset/release run determines it again. + +Labels contain `start_ms`, `end_ms`, `speech` and `provenance` (human_checked/inferred). Label intervals must be ordered and nonoverlapping. Silence must be labeled explicitly; unlabelled audio is not assumed silent. Reply-waiting intervals contain start_ms/end_ms, also ordered and nonoverlapping. All times share the fixture's audio timeline. Candidate files contain an array of `{name, settings}`; names must be unique. + +Scores report human-checked speech missed and false activity durations at 16 ms resolution, silent activity overlapping a waiting reply, onset-delay samples, missed/excluded utterances and fragmentation. Inferred labels are counted but excluded from correctness scores. Full onset samples require uninterrupted known detector coverage over the annotation. Interruption detection samples use the recorded playback mode at speech onset. They measure gate detection, not audible agent stopping. Raw samples allow tail analysis without hiding misses in an average. + +These reports do not estimate a changed conversation's playback schedule. In particular, original reply-ready intervals and modes are fixed evidence. Replaying a stronger gate might change later playback in reality. Compare candidates on held-out sessions, inspect missed speech alongside latency, and trial improvements live before promoting them. No candidate is promoted by this command. + +Each input file is capped at 128 MiB and a comparison at 64 candidates. Split long recordings into fixtures with preserved context and epoch boundaries. Raw recordings and fixtures stay local unless separately shared. diff --git a/src-tauri/crates/berd-call/src/cli_help.rs b/src-tauri/crates/berd-call/src/cli_help.rs index a3772424a..89b04b37f 100644 --- a/src-tauri/crates/berd-call/src/cli_help.rs +++ b/src-tauri/crates/berd-call/src/cli_help.rs @@ -22,6 +22,7 @@ Speech and models: models Inspect or install speech models Diagnostics: + vad-replay Compare offline continuous-recognition VAD fixtures benchmark tts Benchmark a TTS backend benchmark stt Benchmark an STT backend @@ -275,6 +276,7 @@ fn help_for(topic: &[&str]) -> Option<&'static str> { | ["models", "openai", "voices"] | ["models", "pocket", "status" | "install" | "voices"] | ["models", "parakeet", "status" | "install"] => Some(MODELS_HELP), + ["vad-replay"] => Some("Usage: berd-call vad-replay INPUT.json [--candidates CANDIDATES.json]\n\nReplays continuous-recognition VAD fixtures without changing live profiles.\nWrites one JSON report per recorded/candidate profile. See VAD_REPLAY.md for the fixture schema and scoring limits."), ["benchmark"] => Some(BENCHMARK_HELP), ["benchmark", "tts"] => Some(BENCHMARK_TTS_HELP), ["benchmark", "stt"] => Some(BENCHMARK_STT_HELP), diff --git a/src-tauri/crates/berd-call/src/lib.rs b/src-tauri/crates/berd-call/src/lib.rs index ecdc19644..a482f9262 100644 --- a/src-tauri/crates/berd-call/src/lib.rs +++ b/src-tauri/crates/berd-call/src/lib.rs @@ -42,6 +42,7 @@ mod synthesis; mod tts; #[cfg(target_os = "macos")] pub mod update_guard; +pub mod vad_replay; pub use audio_output::{wait_until_drained, PcmAudioOutput}; pub use configured_tts::{ diff --git a/src-tauri/crates/berd-call/src/main.rs b/src-tauri/crates/berd-call/src/main.rs index fb0ab5404..2a20f8250 100644 --- a/src-tauri/crates/berd-call/src/main.rs +++ b/src-tauri/crates/berd-call/src/main.rs @@ -82,6 +82,7 @@ mod session_audio; mod session_framing; #[cfg(target_os = "macos")] mod system_input_mute; +mod vad_replay_cli; use session_audio::{ AudioHostAck, AudioOutputControlRequest, AudioPipeTransport, RemotePcmAudioOutput, @@ -550,6 +551,13 @@ fn main() { None => {} } match args.get(1).map(String::as_str) { + Some("vad-replay") => { + if let Err(error) = vad_replay_cli::run(&args[2..]) { + eprintln!("berd-call vad-replay: {error}"); + std::process::exit(2); + } + } + #[cfg(target_os = "macos")] Some("detection-profile") => { let result = (|| -> Result { diff --git a/src-tauri/crates/berd-call/src/vad_replay.rs b/src-tauri/crates/berd-call/src/vad_replay.rs new file mode 100644 index 000000000..7f0222045 --- /dev/null +++ b/src-tauri/crates/berd-call/src/vad_replay.rs @@ -0,0 +1,488 @@ +//! Deterministic replay of the continuous-recognition activity gate. +//! This predicts gate decisions, never counterfactual audible playback. +use crate::detection::{DetectionSettings, SpeechGate}; +use serde::{Deserialize, Serialize}; + +pub const FRAME_MS: u64 = 16; + +#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum Mode { + Listening, + Interruption, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Frame { + /// End of a complete 16 ms detector frame on the recording timeline. + pub end_ms: u64, + pub epoch: u64, + pub confidence: f32, + pub mode: Mode, + pub settings: DetectionSettings, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum Provenance { + HumanChecked, + Inferred, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Label { + pub start_ms: u64, + pub end_ms: u64, + pub speech: bool, + pub provenance: Provenance, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Interval { + pub start_ms: u64, + pub end_ms: u64, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ReplayInput { + pub schema_version: u32, + pub session_id: String, + pub held_out: bool, + pub frames: Vec, + pub labels: Vec