Always-on wake-word listener (local mic → Talk).
Backends:
- phrase_gate — energy/VAD + tiny Whisper STT + regex phrases (default)
- openwakeword — ML scores on PCM (pre-trained hey_jarvis or custom ONNX)
- auto — openWakeWord if installed, else phrase_gate
Default action: emit talk_open on local_trigger (open Talk orb).
Classes
WakeWordListener
WakeWordListener(
on_wake: Callable[[str], None],
*,
cfg: WakeListenConfig | None = None,
once: bool = False,
device: int | None = None,
debug: bool = False,
transcribe: TranscribeFn | None = None,
)
Mic loop: openWakeWord ML scores and/or STT phrase gate → callback.
Source code in src/diapason/speech/wakeword.py
| def __init__(
self,
on_wake: Callable[[str], None],
*,
cfg: WakeListenConfig | None = None,
once: bool = False,
device: int | None = None,
debug: bool = False,
transcribe: TranscribeFn | None = None,
) -> None:
self._on_wake = on_wake
self._cfg = cfg or WakeListenConfig()
self._once = once
self._device = device
self._debug = debug
self._transcribe = transcribe or default_transcribe_pcm
self._pattern = build_wake_pattern(self._cfg.phrases)
self._stop = threading.Event()
self._thread: Optional[threading.Thread] = None
self._fired = False
self._last_fire = 0.0
self._resolved_backend = "phrase_gate"
|
Methods:
check_text
check_text(text: str) -> bool
Unit-test / manual path: return True and invoke callback if wake.
Source code in src/diapason/speech/wakeword.py
| def check_text(self, text: str) -> bool:
"""Unit-test / manual path: return True and invoke callback if wake."""
if not has_wake_word(text, pattern=self._pattern):
return False
return self._fire(text)
|
Functions:
default_transcribe_pcm
default_transcribe_pcm(pcm: bytes, sample_rate: int) -> str
Best-effort local STT (faster-whisper tiny). Returns '' if unavailable.
Source code in src/diapason/speech/wakeword.py
| def default_transcribe_pcm(pcm: bytes, sample_rate: int) -> str:
"""Best-effort local STT (faster-whisper tiny). Returns '' if unavailable."""
if not pcm:
return ""
try:
import io
import wave
from faster_whisper import WhisperModel
buf = io.BytesIO()
with wave.open(buf, "wb") as wf:
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(sample_rate)
wf.writeframes(pcm)
buf.seek(0)
model = getattr(default_transcribe_pcm, "_model", None)
if model is None:
model = WhisperModel("tiny", device="cpu", compute_type="int8")
setattr(default_transcribe_pcm, "_model", model)
segments, _info = model.transcribe(buf, language=None, beam_size=1)
return " ".join(s.text for s in segments).strip()
except Exception:
logger.debug("wake STT unavailable", exc_info=True)
return ""
|
wake_config_from_toml
wake_config_from_toml(
*,
cooldown: float | None = None,
min_rms: float | None = None,
phrases: str | None = None,
backend: str | None = None,
) -> WakeListenConfig
Build WakeListenConfig merging CLI overrides with [speech.wakeword].
Source code in src/diapason/speech/wakeword.py
| def wake_config_from_toml(
*,
cooldown: float | None = None,
min_rms: float | None = None,
phrases: str | None = None,
backend: str | None = None,
) -> WakeListenConfig:
"""Build WakeListenConfig merging CLI overrides with [speech.wakeword]."""
cfg = WakeListenConfig()
try:
from diapason.core.config import load_config
w = load_config().speech.wakeword
cfg.cooldown_s = float(w.cooldown_s)
cfg.phrases = phrases_from_config(w.phrases)
cfg.action = (w.action or "talk").strip() or "talk"
cfg.backend = (w.backend or "phrase_gate").strip() or "phrase_gate"
cfg.sensitivity = float(w.sensitivity)
cfg.model_path = (w.model_path or "").strip()
except Exception:
logger.debug("wake config from toml unavailable", exc_info=True)
if cooldown is not None:
cfg.cooldown_s = float(cooldown)
if min_rms is not None:
cfg.min_rms = float(min_rms)
if phrases:
cfg.phrases = phrases_from_config(phrases)
if backend:
cfg.backend = backend.strip()
return cfg
|