diff --git a/.env.example b/.env.example index e5480046..2dee01b1 100644 --- a/.env.example +++ b/.env.example @@ -54,6 +54,10 @@ LVA_PULSE_COOKIE="/run/user/${LVA_USER_ID}/pulse/cookie" ### Enable thinking sound (optional): # ENABLE_THINKING_SOUND="1" +### Start listening during wake sound (optional): +# Start listening immediately after wake word detection, without waiting for the wake sound to finish +# LISTEN_DURING_WAKE_SOUND="1" + ### Wake word directory (optional): # path for custom files in docker is for example "app/wakewords/custom" # WAKE_WORD_DIR="app/wakewords" diff --git a/README.md b/README.md index 086488a7..de290df3 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ For all other users, we have different installation methods available (Docker, s ``` sh usage: __main__.py [-h] [--name NAME] [--audio-input-device AUDIO_INPUT_DEVICE] [--list-input-devices] [--audio-input-block-size AUDIO_INPUT_BLOCK_SIZE] [--audio-output-device AUDIO_OUTPUT_DEVICE] [--list-output-devices] [--wake-word-dir WAKE_WORD_DIR] [--mic-auto-gain] [--mic-noise-suppression] [--wake-model WAKE_MODEL] [--stop-model STOP_MODEL] [--download-dir DOWNLOAD_DIR] [--refractory-seconds REFRACTORY_SECONDS] [--wakeup-sound WAKEUP_SOUND] [--timer-finished-sound TIMER_FINISHED_SOUND] [--processing-sound PROCESSING_SOUND] - [--mute-sound MUTE_SOUND] [--unmute-sound UNMUTE_SOUND] [--preferences-file PREFERENCES_FILE] [--host HOST] [--network-interface NETWORK_INTERFACE] [--port PORT] [--enable-thinking-sound] [--debug] + [--mute-sound MUTE_SOUND] [--unmute-sound UNMUTE_SOUND] [--preferences-file PREFERENCES_FILE] [--host HOST] [--network-interface NETWORK_INTERFACE] [--port PORT] [--enable-thinking-sound] [--listen-during-wake-sound] [--debug] ``` | Parameter | Description | Default | @@ -81,6 +81,7 @@ usage: __main__.py [-h] [--name NAME] [--audio-input-device AUDIO_INPUT_DEVICE] | `--network-interface` | Network interface for ESPHome server | Autodetected | | `--port` | Port for ESPHome server | 6053 | | `--enable-thinking-sound` | Enable thinking sound on startup | False | +| `--listen-during-wake-sound` | Start listening immediately after wake word detection, without waiting for the wake sound to finish | False | | `--debug` | Print DEBUG messages to console | False | | `--output-only` | Enable output only mode | False | diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 2eae2297..210eed85 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -56,6 +56,10 @@ if [ "$ENABLE_THINKING_SOUND" = "1" ]; then EXTRA_ARGS+=( "--enable-thinking-sound" ) fi +if [ "$LISTEN_DURING_WAKE_SOUND" = "1" ]; then + EXTRA_ARGS+=( "--listen-during-wake-sound" ) +fi + if [ -n "${WAKE_WORD_DIR}" ]; then EXTRA_ARGS+=( "--wake-word-dir" "$WAKE_WORD_DIR" ) fi diff --git a/docs/install_application.md b/docs/install_application.md index 90953742..84bab2f6 100644 --- a/docs/install_application.md +++ b/docs/install_application.md @@ -188,6 +188,7 @@ Environment=PREFERENCES_FILE="/home/pi/linux-voice-assistant/preferences.json" # Environment=WAKEUP_SOUND="sounds/wake_word_triggered.flac" # Environment=TIMER_FINISHED_SOUND="sounds/timer_finished.flac" # Environment=PROCESSING_SOUND="sounds/processing.wav" +# Environment=LISTEN_DURING_WAKE_SOUND="0" # Environment=MUTE_SOUND="sounds/mute_switch_on.flac" # Environment=UNMUTE_SOUND="sounds/mute_switch_off.flac" # Environment=ENABLE_OUTPUT_ONLY="1" @@ -270,6 +271,7 @@ The following variables can be configured in the `.env` or in the service file: | `WAKEUP_SOUND` | `sounds/wake_word_triggered.flac` | Sound file for wake word triggered | | `TIMER_FINISHED_SOUND` | `sounds/timer_finished.flac` | Sound file for timer finished | | `PROCESSING_SOUND` | `sounds/processing.wav` | Sound file for processing state | +| `LISTEN_DURING_WAKE_SOUND` | false | Set to "1" to start listening immediately after wake word detection, without waiting for the wake sound to finish | | `MUTE_SOUND` | `sounds/mute_switch_on.flac` | Sound file for mute on | | `UNMUTE_SOUND` | `sounds/mute_switch_off.flac` | Sound file for Configure Audio Devices | `ENABLE_OUTPUT_ONLY` | (optional) | Set to "1" to enable output-only mode | @@ -277,6 +279,17 @@ The following variables can be configured in the `.env` or in the service file: 💡 **Note:** For the systemd installation some variables set in the service need to be without `LVA_` prefix. +### Feature: Listen During Wake Sound + +By default, LVA waits until the wake sound has finished playing before it starts listening for your spoken command. This can force a short pause between saying the wake word and the rest of your request. For example: `Okay Nabu turn on the kitchen lights`. + +Enable `LISTEN_DURING_WAKE_SOUND="1"` to let LVA begin listening immediately after the wake word is detected, even while the wake sound is still playing. This allows you to say the wake word and the full command in one go. + +This feature is especially useful when you want a more natural interaction flow and do not want to wait to confirm that the wake word was detected before speaking the command. + +Keep in mind that listening while the wake sound is playing can also make the microphone pick up some of that wake sound as echo, depending on your speaker and microphone setup. This echo can interfere with speech-to-text and make commands less accurate. If you run into this, see [Enabling Acoustic Echo Cancellation (AEC)](enabling_aec.md). + + ### Use own soundfiles: If you want to use your own sounds, you can add them to the `sounds/custom` aka `/var/lib/docker/volumes/lva_sounds_custom/_data` directory and reference them in the `.env` file. diff --git a/linux_voice_assistant/__main__.py b/linux_voice_assistant/__main__.py index 6c93e924..f8babe02 100644 --- a/linux_voice_assistant/__main__.py +++ b/linux_voice_assistant/__main__.py @@ -156,6 +156,11 @@ async def main() -> None: default=900.0, # 15 minutes help="Seconds before a ringing timer auto-stops (default: 900)", ) + parser.add_argument( + "--listen-during-wake-sound", + action="store_true", + help="Start listening immediately after wake word detection, without waiting for the wake sound to finish", + ) parser.add_argument( "--debug", action="store_true", @@ -326,6 +331,7 @@ async def main() -> None: mic_auto_gain=preferences.mic_auto_gain, mic_noise_suppression=preferences.mic_noise_suppression, timer_max_ring_seconds=args.timer_max_ring_seconds, + listen_during_wake_sound=args.listen_during_wake_sound, ) if fallback_used: diff --git a/linux_voice_assistant/models.py b/linux_voice_assistant/models.py index 7319ee39..72337678 100644 --- a/linux_voice_assistant/models.py +++ b/linux_voice_assistant/models.py @@ -126,6 +126,7 @@ class ServerState: mic_noise_suppression: int = 0 mic_volume: int = 100 # 1–100, default maximum timer_max_ring_seconds: float = 900.0 + listen_during_wake_sound: bool = False def save_preferences(self) -> None: """Save preferences as JSON.""" diff --git a/linux_voice_assistant/satellite.py b/linux_voice_assistant/satellite.py index 05184d0e..637824d5 100644 --- a/linux_voice_assistant/satellite.py +++ b/linux_voice_assistant/satellite.py @@ -654,14 +654,20 @@ def _delayed_wakeup() -> None: _LOGGER.debug("Detected wake word: %s", wake_word_phrase) self._pipeline_active = True self.duck() - self.state.tts_player.play( - self.state.wakeup_sound, - done_callback=lambda: self._on_wakeup_sound_finished(wake_word_phrase), - ) - def _on_wakeup_sound_finished(self, wake_word_phrase: str) -> None: - """Callback invoked when the wakeup sound finishes playing.""" - _LOGGER.debug("Wakeup sound finished, starting audio streaming with wake word: %s", wake_word_phrase) + if self.state.listen_during_wake_sound: + _LOGGER.debug("Starting audio streaming immediately (listen_during_wake_sound enabled)") + self._start_audio_streaming(wake_word_phrase) + self.state.tts_player.play(self.state.wakeup_sound) + else: + self.state.tts_player.play( + self.state.wakeup_sound, + done_callback=lambda: self._start_audio_streaming(wake_word_phrase), + ) + + def _start_audio_streaming(self, wake_word_phrase: str) -> None: + """Start streaming audio after/during wake word detection.""" + _LOGGER.debug("Starting audio streaming with wake word: %s", wake_word_phrase) self.send_messages( [VoiceAssistantRequest(start=True, wake_word_phrase=wake_word_phrase)], )