Update audio_engine.py issue: 48
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).
This commit is contained in:
@@ -5,7 +5,7 @@ import logging
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import warnings
|
import warnings
|
||||||
from typing import List, Optional, Union
|
from typing import Dict, List, Optional, Union
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import soundcard as sc
|
import soundcard as sc
|
||||||
@@ -56,7 +56,9 @@ class AudioEngine:
|
|||||||
micro_inputs: List[np.ndarray] = []
|
micro_inputs: List[np.ndarray] = []
|
||||||
oww_inputs: List[np.ndarray] = []
|
oww_inputs: List[np.ndarray] = []
|
||||||
has_oww = False
|
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:
|
try:
|
||||||
while True:
|
while True:
|
||||||
@@ -75,7 +77,7 @@ class AudioEngine:
|
|||||||
oww_features.reset()
|
oww_features.reset()
|
||||||
|
|
||||||
# Allow immediate activation (don't enforce refractory here)
|
# Allow immediate activation (don't enforce refractory here)
|
||||||
last_active = None
|
last_active_by_id.clear()
|
||||||
|
|
||||||
# Flush hardware buffer
|
# Flush hardware buffer
|
||||||
try:
|
try:
|
||||||
@@ -173,16 +175,24 @@ class AudioEngine:
|
|||||||
wake_phrase = getattr(
|
wake_phrase = getattr(
|
||||||
wake_word, "wake_word", "wake word"
|
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(
|
_LOGGER.debug(
|
||||||
"Wake word ACTIVATED: %s (id=%s)",
|
"Wake word ACTIVATED: %s (id=%s)",
|
||||||
wake_phrase,
|
wake_phrase,
|
||||||
wake_id,
|
wake_id_attr,
|
||||||
)
|
)
|
||||||
|
|
||||||
now = time.monotonic()
|
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 (
|
if (last_active is None) or (
|
||||||
(now - last_active)
|
(now - last_active) > refractory
|
||||||
> self.state.refractory_seconds
|
|
||||||
):
|
):
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Wake word ACCEPTED (phrase=%s); "
|
"Wake word ACCEPTED (phrase=%s); "
|
||||||
@@ -191,19 +201,19 @@ class AudioEngine:
|
|||||||
0.0
|
0.0
|
||||||
if last_active is None
|
if last_active is None
|
||||||
else now - last_active,
|
else now - last_active,
|
||||||
self.state.refractory_seconds,
|
refractory,
|
||||||
)
|
)
|
||||||
|
last_active_by_id[id_key] = now
|
||||||
self.state.loop.call_soon_threadsafe(
|
self.state.loop.call_soon_threadsafe(
|
||||||
self.state.satellite.wakeup, wake_word
|
self.state.satellite.wakeup, wake_word
|
||||||
)
|
)
|
||||||
last_active = now
|
|
||||||
else:
|
else:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Wake word REJECTED by refractory period "
|
"Wake word REJECTED by refractory period "
|
||||||
"(phrase=%s, Δt=%.2f < %.2f)",
|
"(phrase=%s, Δt=%.2f < %.2f)",
|
||||||
wake_phrase,
|
wake_phrase,
|
||||||
now - last_active,
|
now - last_active,
|
||||||
self.state.refractory_seconds,
|
refractory,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Stop word detection
|
# Stop word detection
|
||||||
|
|||||||
Reference in New Issue
Block a user