fix(cr-2026-04-22 C2): VadChunker::flush returns Vec<VadChunk> and preserves mid-flush emissions
CRITICAL from the 2026-04-22 code review: RmsVadChunker::flush() was calling consume_frame() on a zero-padded final frame via `let _ = ...`, discarding any VadChunk the call emitted. If the padded frame triggered end-of-utterance (silent tail + padding zeros push past silence_close_samples) or max_chunk_samples (buffered speech + padding push past the cap), the emitted chunk was lost; the outer state check then either returned None or an empty closing chunk. Changes the VadChunker trait flush signature from Option<VadChunk> to Vec<VadChunk> so both the mid-flush emission (from consume_frame) and the closing emission (from emit_active_chunk_and_close) can be surfaced. Updates RmsVadChunker::flush to collect from both sites and skip a zero-length closing emit when the hit_max continue variant already cleared active_chunk. Two regression tests land alongside: - flush_preserves_hit_max_chunk_from_padded_final_frame: tight max_chunk, sub-frame speech tail; pre-fix dropped the chunk, post-fix emitted samples cover the full active-speech region. - flush_preserves_end_of_utterance_chunk_from_padded_final_frame: silent tail near silence_close; padded zero frame closes the utterance inside consume_frame; pre-fix returned None. No production callers yet — the VadChunker wiring in live.rs is a deferred item from A.3. API change is clean within the repo.
This commit is contained in:
@@ -46,10 +46,17 @@ pub trait VadChunker: Send {
|
||||
/// emit as a result. An empty Vec means "still gathering".
|
||||
fn push(&mut self, samples: &[f32]) -> Vec<VadChunk>;
|
||||
|
||||
/// End-of-session: emit any in-progress speech as a chunk even
|
||||
/// though silence has not closed it. Returns `None` if there is
|
||||
/// nothing buffered (or only sub-threshold samples).
|
||||
fn flush(&mut self) -> Option<VadChunk>;
|
||||
/// End-of-session: emit any in-progress speech as chunks even
|
||||
/// though silence has not closed them. Returns an empty Vec if
|
||||
/// there is nothing buffered (or only sub-threshold samples).
|
||||
///
|
||||
/// Returns `Vec<VadChunk>` rather than `Option<VadChunk>` because
|
||||
/// the zero-padded final frame can legitimately trigger both a
|
||||
/// mid-flush emission (end-of-utterance or `max_chunk_samples`)
|
||||
/// AND a closing emission if the backend stays in-speech after
|
||||
/// the mid-flush cut. The previous `Option` signature silently
|
||||
/// dropped the mid-flush chunk.
|
||||
fn flush(&mut self) -> Vec<VadChunk>;
|
||||
|
||||
/// Drop accumulated state. Used between sessions on the same
|
||||
/// chunker instance (or after a context-window reset from the
|
||||
|
||||
Reference in New Issue
Block a user