From 8d135dc51b36a566d4c0139e1c5e21f0cfb68867 Mon Sep 17 00:00:00 2001 From: imonlinux <39863321+imonlinux@users.noreply.github.com> Date: Sat, 6 Dec 2025 19:32:29 -0800 Subject: [PATCH] Update audio_engine.py issue: 48 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Behavioral changes: Each wake word has its own timer. Rapid repeats of the same phrase within the refractory window still get rejected (which is desirable). A misfire of “Hey Jarvis” no longer locks out “Okay Nabu” immediately afterward. If a user really wants to disable the gating, they can set: "wake_word": { "refractory_seconds": 0 } and this code will treat it as “no refractory” (max(..., 0.0) above). --- linux_voice_assistant/audio_engine.py | 30 ++++++++++++++++++--------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/linux_voice_assistant/audio_engine.py b/linux_voice_assistant/audio_engine.py index 897831e..8b23095 100644 --- a/linux_voice_assistant/audio_engine.py +++ b/linux_voice_assistant/audio_engine.py @@ -5,7 +5,7 @@ import logging import threading import time import warnings -from typing import List, Optional, Union +from typing import Dict, List, Optional, Union import numpy as np import soundcard as sc @@ -56,7 +56,9 @@ class AudioEngine: micro_inputs: List[np.ndarray] = [] oww_inputs: List[np.ndarray] = [] has_oww = False - last_active: Optional[float] = None + + # Track last activation time per wake word (by id or phrase) + last_active_by_id: Dict[str, float] = {} try: while True: @@ -75,7 +77,7 @@ class AudioEngine: oww_features.reset() # Allow immediate activation (don't enforce refractory here) - last_active = None + last_active_by_id.clear() # Flush hardware buffer try: @@ -173,16 +175,24 @@ class AudioEngine: wake_phrase = getattr( wake_word, "wake_word", "wake word" ) - wake_id = getattr(wake_word, "id", None) + wake_id_attr = getattr(wake_word, "id", None) + # Use id if available, otherwise phrase string as key + id_key = wake_id_attr or wake_phrase + _LOGGER.debug( "Wake word ACTIVATED: %s (id=%s)", wake_phrase, - wake_id, + wake_id_attr, ) + now = time.monotonic() + last_active = last_active_by_id.get(id_key) + + # If refractory_seconds <= 0, disable gating + refractory = max(self.state.refractory_seconds, 0.0) + if (last_active is None) or ( - (now - last_active) - > self.state.refractory_seconds + (now - last_active) > refractory ): _LOGGER.debug( "Wake word ACCEPTED (phrase=%s); " @@ -191,19 +201,19 @@ class AudioEngine: 0.0 if last_active is None else now - last_active, - self.state.refractory_seconds, + refractory, ) + last_active_by_id[id_key] = now self.state.loop.call_soon_threadsafe( self.state.satellite.wakeup, wake_word ) - last_active = now else: _LOGGER.debug( "Wake word REJECTED by refractory period " "(phrase=%s, Δt=%.2f < %.2f)", wake_phrase, now - last_active, - self.state.refractory_seconds, + refractory, ) # Stop word detection