/** * Media-echo classification for mic transcripts. * * When device audio plays over the speakers (a video, a call, music), the mic * hears it too and Soniox happily transcribes it — so media dialogue shows up * as mic speech attributed to the wearer, and wake-words spoken IN the media * ("koi") fire the proactive pipeline. This module classifies a mic_transcript * segment as MEDIA ECHO when its text substantially matches recent system * audio_transcript (ScreenCaptureKit device-audio) text. * * Exact matching cannot work: Soniox segments the two streams on different * boundaries and decodes them independently, so the same sentence arrives as * differently-shaped segments with small word-level diffs. Instead we use * token containment: a rolling window of recent system-audio tokens, and a * mic segment is echo when enough of its significant tokens appear in that * window. If the system stream is silent (headphones, nothing playing) the * window is empty and nothing is ever classified as echo. * * Used by BOTH capture paths — context-block rendering (PATH A) and proactive * triggers (PATH B). Each consumer owns its own tracker instance because each * tails the audio JSONL at its own offset/cadence. */ /** * How long system-audio tokens stay matchable. Soniox can finalize a long mic * utterance tens of seconds after the media line it echoes (the two streams * flush on different boundaries), so the window must comfortably cover one * long utterance either way — 60s. */ export declare const SYSTEM_AUDIO_WINDOW_MS = 60000; /** * Mic segments with fewer significant tokens than this are never classified * as echo — one or two shared words ("yeah", "okay cool") is not evidence, * and misclassifying real user speech is worse than letting an echo through. */ export declare const MIN_SIGNIFICANT_TOKENS = 3; /** * Containment ratio at/above which a mic segment counts as echo. ~70%: the * two streams' independent decodes disagree on a few words per sentence, so * requiring 100% would never fire; much lower and ordinary topical overlap * (the user talking ABOUT the video they're watching) starts false-positiving. */ export declare const ECHO_CONTAINMENT_THRESHOLD = 0.7; /** Lowercase, strip punctuation, split into word tokens. */ export declare function normalizeTokens(text: string): string[]; /** The tokens that count toward echo evidence (see MIN_TOKEN_LENGTH). */ export declare function significantTokens(text: string): string[]; /** * Fraction of `tokens` (deduped) present in `window`. Deduping keeps one * repeated common word from dominating the ratio. */ export declare function containmentRatio(tokens: string[], window: ReadonlySet): number; /** * Stateful rolling window of recent system-audio tokens + echo classification * for mic segments. Feed every system audio_transcript line via * `noteSystemAudio`; ask `isEcho` for each mic_transcript line. Within one * batch of JSONL lines, feed ALL system lines before classifying mic lines — * a mic echo can land in the file before its system-audio source segment. */ export declare class EchoTracker { private entries; noteSystemAudio(text: string, ts?: number): void; /** True when the mic segment substantially matches recent system audio. */ isEcho(text: string, ts?: number): boolean; private prune; } /** * How long a mic segment votes for speaker dominance. Long enough that the * wearer stays "you" through someone else's interjections; short enough that * handing the device to someone else re-baselines within minutes. */ export declare const SPEAKER_DOMINANCE_WINDOW_MS: number; /** * Rolling dominant-speaker vote over NON-echo mic segments. The wearer talks * into their own mic far more than anyone else, so the most frequent Soniox * `speaker` tag over the recent window is "you"; every other tag is another * voice in the room. */ export declare class DominantSpeakerTracker { private entries; note(speaker: string, ts?: number): void; /** The most frequent recent speaker tag, or null when nothing is tallied. */ dominant(now?: number): string | null; private prune; }