Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -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 |

Expand Down
4 changes: 4 additions & 0 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions docs/install_application.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -270,13 +271,25 @@ 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 |


💡 **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 <pause until wake sound finishes> 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.
Expand Down
6 changes: 6 additions & 0 deletions linux_voice_assistant/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions linux_voice_assistant/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
20 changes: 13 additions & 7 deletions linux_voice_assistant/satellite.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)],
)
Expand Down
Loading