From 01c6cfe96ba0cb9469b7bbd760c25a38265c2c09 Mon Sep 17 00:00:00 2001 From: Myron Blair Date: Sun, 31 May 2026 16:35:44 +0000 Subject: [PATCH] fix: mute mic during ElevenLabs TTS to prevent feedback loop; wire isSpeaking --- public_html/index.html | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/public_html/index.html b/public_html/index.html index ae1bde1..f70903b 100644 --- a/public_html/index.html +++ b/public_html/index.html @@ -1734,6 +1734,7 @@ function initVoice() { recognition.lang = 'en-US'; recognition.onresult = (e) => { + if (isSpeaking) return; // ignore mic during TTS playback const result = e.results[e.results.length - 1]; if (!result.isFinal) return; const transcript = result[0].transcript.trim(); @@ -1866,8 +1867,17 @@ async function speak(text) { if (!text) return; if (_ttsAudio) { _ttsAudio.pause(); _ttsAudio = null; } synth?.cancel(); + isSpeaking = true; + // Pause recognition while JARVIS speaks to avoid mic feedback + try { recognition?.abort(); } catch(_) {} const reactor = document.getElementById('arcReactor'); reactor?.classList.add('speaking'); + const _resumeMic = () => { + isSpeaking = false; + reactor?.classList.remove('speaking'); + // Let onend restart recognition automatically (300ms buffer after audio ends) + if (isListening) setTimeout(() => { try { recognition?.start(); } catch(_) {} }, 300); + }; try { const res = await fetch('/api/tts', { method: 'POST', @@ -1878,11 +1888,11 @@ async function speak(text) { const blob = await res.blob(); const url = URL.createObjectURL(blob); _ttsAudio = new Audio(url); - _ttsAudio.onended = () => { URL.revokeObjectURL(url); _ttsAudio = null; reactor?.classList.remove('speaking'); }; - _ttsAudio.onerror = () => { reactor?.classList.remove('speaking'); _ttsAudio = null; }; + _ttsAudio.onended = () => { URL.revokeObjectURL(url); _ttsAudio = null; _resumeMic(); }; + _ttsAudio.onerror = () => { _ttsAudio = null; _resumeMic(); }; await _ttsAudio.play(); } catch(e) { - reactor?.classList.remove('speaking'); + _resumeMic(); _speakFallback(text); } } @@ -1890,12 +1900,17 @@ async function speak(text) { function _speakFallback(text) { if (!synth || !text) return; synth.cancel(); + isSpeaking = true; const utter = new SpeechSynthesisUtterance(text); if (selectedVoice) utter.voice = selectedVoice; utter.rate = 0.92; utter.pitch = 0.85; utter.volume = 1; const reactor = document.getElementById('arcReactor'); utter.onstart = () => reactor?.classList.add('speaking'); - utter.onend = () => reactor?.classList.remove('speaking'); + utter.onend = () => { + reactor?.classList.remove('speaking'); + isSpeaking = false; + if (isListening) setTimeout(() => { try { recognition?.start(); } catch(_) {} }, 300); + }; synth.speak(utter); }