/** * detector.ts, turning a stream of per-frame scores into wake events. * * The classifier emits a score every 80 ms. Acting on each one directly is what * makes a wake word feel unreliable in both directions: a single noisy frame * fires it, and one spoken phrase fires it three times as it crosses the * rolling window. Two rules fix that, and this module is only those two rules * plus their bookkeeping: * * - **patience**, a run of `patienceFrames` consecutive frames must all clear * the threshold before the wake is confirmed. At the default of 2 that is * about 160 ms of agreement for one extra frame of latency. * - **cooldown**, after a confirmed wake, `cooldownMs` of further detections * are dropped, so one utterance cannot fire twice. * * Kept separate from the engine and free of any I/O so the behaviour is * testable by feeding it score sequences, with time injected rather than read. */ import type { WakeDetectorTuning } from './types.js'; /** What a frame did to the detector. */ export type WakeFrameOutcome = /** Below threshold; any run in progress was broken. */ { readonly kind: 'idle'; } /** Above threshold, but the run has not yet reached `patienceFrames`. */ | { readonly kind: 'building'; readonly frames: number; readonly needed: number; } /** Above threshold and confirmed. */ | { readonly kind: 'fired'; readonly frames: number; readonly score: number; readonly peakScore: number; } /** Above threshold but suppressed because a wake fired recently. */ | { readonly kind: 'cooldown'; readonly remainingMs: number; }; /** Defaults matching the shipped `voice.wake.*` rows. */ export declare const WAKE_DETECTOR_DEFAULTS: WakeDetectorTuning; /** * Per-model patience/cooldown state machine. * * One instance per model, two models running concurrently have independent * runs and independent cooldowns, so a wake on one does not mask the other. */ export declare class WakeDetector { #private; constructor(tuning?: Partial); /** The tuning in force, after defaults were merged in. */ get tuning(): WakeDetectorTuning; /** Milliseconds of cooldown left at `now`, or 0 when not in cooldown. */ cooldownRemaining(now: number): number; /** * Clear all state. Used when the stream restarts, so a run that was building * when a process died does not resume against unrelated audio. */ reset(): void; /** * End any run in progress without scoring a frame, leaving the cooldown alone. * * What the speech gate calls when it withholds a frame: patience counts * CONSECUTIVE scored frames, so a gap of screened-out non-speech must break a * run rather than let it resume across the gap. Distinct from * {@link WakeDetector.reset}, which also clears the cooldown and would let one * utterance fire twice. */ breakRun(): void; /** * Offer one frame's score. `now` is injected rather than read from the clock * so cooldown behaviour is deterministic under test. */ push(score: number, now: number): WakeFrameOutcome; } //# sourceMappingURL=detector.d.ts.map