Files
VoiceStudio/docs
Palash DebnathandClaude Opus 4.8 e8705a106d feat(dictation): opt-in NLMS AEC for dictate-over-playback (Wave 8b) (#399)
* feat(dictation): opt-in NLMS AEC for dictate-over-playback (Wave 8b)

Dictating while OmniVoice plays audio (TTS preview, dub, video) leaks the
loudspeaker signal into the mic, and the streaming ASR transcribes that
bleed. Browser echoCancellation varies per platform/webview — it can't be a
cross-platform default — so this adds a server-side canceller that behaves
identically everywhere.

services/aec.py ports Patter's NlmsEchoCanceller (MIT): a time-domain NLMS
adaptive filter with a Geigel double-talk detector, warm-up step ramp, and
far-end staleness pass-through. /ws/transcribe gains an opt-in '?aec=1[&sr=]'
mode: frames are raw int16 mono PCM tagged with a 1-byte prefix (0x00 mic,
0x01 playback reference); the mic is cleaned against the reference before
buffering, and the cleaned PCM is muxed via stdlib wave (not ffmpeg). Without
the param the protocol and behaviour are byte-for-byte unchanged.

Backend ships dark (no new deps — numpy already pinned); frontend far-end
streaming is a follow-up. Tests cover echo attenuation, double-talk
preservation, cold/stale pass-through, param validation, and the framing
helpers — all pure-numpy/stdlib so they skip the torch ASR stack.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(capture_ws): stubs accept the new pcm_sr kwarg

_transcribe_buffer/_transcribe_buffer_full gained an optional pcm_sr kwarg
for the AEC PCM path; the protocol-test stubs had fixed signatures and
raised TypeError on it, so the handler sent 'error' instead of 'final'.
Accept **kw in the stubs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 23:08:45 +05:30
..