diff --git a/linux_voice_assistant/__main__.py b/linux_voice_assistant/__main__.py index 2eed265..764e2be 100644 --- a/linux_voice_assistant/__main__.py +++ b/linux_voice_assistant/__main__.py @@ -134,7 +134,7 @@ async def main() -> None: finally: # --- 10. Cleanup --- _LOGGER.debug("Shutting down...") - state.mic_muted = True # Signal audio thread to stop + state.shutdown = True # Signal audio thread to stop audio_thread.join() if hasattr(state, "mqtt_controller") and state.mqtt_controller: @@ -404,69 +404,95 @@ def _process_audio(state: ServerState, mic, block_size: int): last_active: Optional[float] = None try: - _LOGGER.debug("Opening audio input device: %s", mic.name) - with mic.recorder(samplerate=16000, channels=1, blocksize=block_size) as mic_in: - while True: - if state.mic_muted: - break - - audio_chunk_array = mic_in.record(block_size).reshape(-1) - audio_chunk = ( - (np.clip(audio_chunk_array, -1.0, 1.0) * 32767.0) - .astype(" 0.5 for oi in oww_inputs for p in wake_word.process_streaming(oi)): activated = True + if (not wake_words) or (state.wake_words_changed): + state.wake_words_changed = False + wake_words = [ + ww for ww in state.wake_words.values() + if ww.id in state.active_wake_words + ] + has_oww = any(isinstance(ww, OpenWakeWord) for ww in wake_words) - if activated: - now = time.monotonic() - if (last_active is None) or ((now - last_active) > state.refractory_seconds): - state.loop.call_soon_threadsafe(state.satellite.wakeup, wake_word) - last_active = now + if has_oww and (oww_features is None): + _LOGGER.debug("Initializing OpenWakeWord features...") + oww_features = OpenWakeWordFeatures.from_builtin() - stopped = False - for micro_input in micro_inputs: - if state.stop_word.process_streaming(micro_input): - stopped = True - - if stopped and (state.stop_word.id in state.active_wake_words): - state.loop.call_soon_threadsafe(state.satellite.stop) + try: + micro_inputs.clear(); micro_inputs.extend(micro_features.process_streaming(audio_chunk)) - state.loop.call_soon_threadsafe(state.satellite.handle_audio, audio_chunk) + if has_oww: + assert oww_features is not None + oww_inputs.clear(); oww_inputs.extend(oww_features.process_streaming(audio_chunk)) + + for wake_word in wake_words: + activated = False + if isinstance(wake_word, MicroWakeWord): + if any(wake_word.process_streaming(mi) for mi in micro_inputs): activated = True + elif isinstance(wake_word, OpenWakeWord): + if any(p > 0.5 for oi in oww_inputs for p in wake_word.process_streaming(oi)): activated = True + + if activated: + now = time.monotonic() + if (last_active is None) or ((now - last_active) > state.refractory_seconds): + state.loop.call_soon_threadsafe(state.satellite.wakeup, wake_word) + last_active = now + + stopped = False + for micro_input in micro_inputs: + if state.stop_word.process_streaming(micro_input): + stopped = True - except Exception: - _LOGGER.exception("Unexpected error handling audio") + if stopped and (state.stop_word.id in state.active_wake_words): + state.loop.call_soon_threadsafe(state.satellite.stop) + + state.loop.call_soon_threadsafe(state.satellite.handle_audio, audio_chunk) + + except Exception: + _LOGGER.exception("Unexpected error handling audio") except Exception as e: _LOGGER.critical("A soundcard error occurred: %s", e)