Skip to content

Refactor: audio stack and enhance testing infrastructure - #9

Merged
chcavignx merged 22 commits into
mainfrom
test
Jun 13, 2026
Merged

Refactor: audio stack and enhance testing infrastructure#9
chcavignx merged 22 commits into
mainfrom
test

Conversation

@chcavignx

Copy link
Copy Markdown
Owner

No description provided.

chcavignx and others added 22 commits April 30, 2026 07:31
Refactor Audio Stack, Implement Offline Voice Agent and Implement Comprehensive Audio Testing and Diagnostics
LiveReview Pre-Commit Check: vouched (iter:12, coverage:69%)
…loading

- Updated `test_e2e_mic_asr.py` to replace pyaudio stream handling with AudioRecorder for improved audio capture.
- Simplified audio frame reading logic and ensured proper cleanup of resources.
- Removed unnecessary calls to load TTS engine in `test_tts_queue.py` to enhance test efficiency.
- Set audio backend to "pyaudio" in `test_vad_wakeword_units.py` to ensure consistent configuration for wake word detection tests.

LiveReview Pre-Commit Check: skipped
LiveReview Pre-Commit Check: vouched (iter:1, coverage:0%)
LiveReview Pre-Commit Check: vouched (iter:1, coverage:0%)
LiveReview Pre-Commit Check: vouched (iter:1, coverage:0%)
LiveReview Pre-Commit Check: vouched (iter:1, coverage:0%)
LiveReview Pre-Commit Check: vouched (iter:1, coverage:0%)
LiveReview Pre-Commit Check: vouched (iter:2, coverage:0%)
LiveReview Pre-Commit Check: vouched (iter:1, coverage:0%)
LiveReview Pre-Commit Check: vouched (iter:1, coverage:0%)
LiveReview Pre-Commit Check: ran (iter:4, coverage:100%)
LiveReview Pre-Commit Check: ran (iter:3, coverage:100%)
LiveReview Pre-Commit Check: ran (iter:3, coverage:99%)
LiveReview Pre-Commit Check: ran (iter:1, coverage:0%)
LiveReview Pre-Commit Check: ran (iter:2, coverage:77%)
LiveReview Pre-Commit Check: ran (iter:3, coverage:59%)
LiveReview Pre-Commit Check: ran (iter:2, coverage:90%)
LiveReview Pre-Commit Check: ran (iter:1, coverage:0%)
LiveReview Pre-Commit Check: ran (iter:1, coverage:0%)
LiveReview Pre-Commit Check: ran (iter:1, coverage:0%)
@qodo-code-review

qodo-code-review Bot commented Jun 13, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0)

Grey Divider


Action required

1. TTS interrupt hangs wait 🐞 Bug ☼ Reliability
Description
TTSEngine.interrupt() drains _tts_queue without calling task_done(), so wait() (Queue.join) and
speak(blocking=True) can block indefinitely. It also never stops in-progress sounddevice playback,
so “interrupt” does not actually interrupt current audio output.
Code

src/audio/tts.py[R160-166]

        """Clear queue and stop current playback."""
        while not self._tts_queue.empty():
            try:
-                self._tts_queue.get_nowait()
+                _item = self._tts_queue.get_nowait()
            except queue.Empty:
                break
        logger.debug("TTS interrupted")
Evidence
interrupt() consumes items from the queue but never calls task_done(), while wait() relies on
join(). The playback path uses AudioPlayer.play_wav_bytes(block=True) and there is no stop call in
interrupt(), so current playback continues despite clearing the queue.

src/audio/tts.py[154-170]
src/audio/tts.py[181-203]
src/audio/tts.py[243-246]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`TTSEngine.interrupt()` removes queued items using `Queue.get_nowait()` but does not call `Queue.task_done()` for those items. Since `wait()` uses `Queue.join()`, this can leave `unfinished_tasks` > 0 forever and hang `wait()` / `speak(blocking=True)`.

Additionally, `interrupt()` claims to stop current playback but does not stop the active sounddevice playback.

## Issue Context
The playback loop correctly calls `task_done()` for items it consumes, but `interrupt()` is another consumer of the same queue and must also decrement the unfinished task counter for every removed item.

## Fix Focus Areas
- src/audio/tts.py[159-170]
- src/audio/tts.py[181-203]

### Implementation notes
- In `interrupt()`, for every successfully dequeued item, call `self._tts_queue.task_done()`.
- If the dequeued item is the `None` sentinel, consider re-enqueueing it (or avoid consuming it) so shutdown semantics remain intact.
- To actually stop current playback, add a backend stop call (e.g., `sounddevice.stop()`) via `AudioPlayer` (preferred) or directly in `TTSEngine.interrupt()`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Import-time logging config 🐞 Bug ☼ Reliability
Description
src.utils.config applies logging.config.dictConfig() at import time, so importing the module now
depends on ROOT_DIR/log.json and a writable log directory and can raise during import. This also
globally overrides process logging before callers/tests can install their own logging configuration.
Code

src/utils/config.py[R348-360]

+def setup_config_logging() -> None:
+    """Set up the logging configuration module."""
+    log_file = ROOT_DIR / "log.json"
+
+    log_path = Path(config.paths.tmp + "/filename.log")
+    log_path.parent.mkdir(parents=True, exist_ok=True)
+
+    with Path(log_file).open("r", encoding="utf-8") as f:
+        logging.config.dictConfig(json.load(f))  # pyright: ignore[reportAny]
+
+
+setup_python_path()
+setup_config_logging()
Evidence
The config module unconditionally executes setup_config_logging() at import time, which reads
log.json and applies dictConfig. log.json includes a TimedRotatingFileHandler writing to a relative
.tmp path, making import dependent on filesystem state/permissions.

src/utils/config.py[331-360]
log.json[31-37]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`src/utils/config.py` calls `setup_config_logging()` at module import time. That function reads `ROOT_DIR/log.json` and applies `logging.config.dictConfig(...)`, which can fail during import (missing/invalid `log.json`, handler initialization failures, unwritable working directory).

Import-time global logging configuration is also a strong side effect that makes the module harder to reuse in libraries and tests.

## Issue Context
`log.json` configures a file handler writing to `.tmp/ai-assistant.log`, so `dictConfig` will attempt to open that file during import.

## Fix Focus Areas
- src/utils/config.py[331-360]
- log.json[31-37]

### Implementation notes
- Remove the unconditional `setup_config_logging()` call at import time.
- Expose an explicit init function (e.g., `init_app()` or `setup_logging()`) and call it from the CLI entrypoint (`src/main.py`).
- Add a safe fallback: if `log.json` is missing/invalid, log a warning (or no-op) rather than raising at import time.
- Consider honoring an env var (e.g., `DISABLE_CONFIG_LOGGING=1`) to skip logging setup in tests/embedded usage.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Device name ignored 🐞 Bug ≡ Correctness
Description
AudioConfig documents input_device_name/output_device_name as overriding device indexes, and
config.yaml sets them, but stream-opening code only uses device_index so the name settings have no
effect. This can cause the wrong device to be selected even when a device name is configured.
Code

src/utils/config.py[R165-166]

+    input_device_index: int | None = None  # None = default input device
+    input_device_name: str | None = None  # Optional name of input device to select (overrides index if found)
Evidence
The configuration schema and default YAML include device_name fields with override semantics, but
the stream-opening API only accepts device_index and constructs candidates from indexes; there is no
name-based lookup path shown.

src/utils/config.py[159-172]
config.yaml[60-71]
src/audio/audio_utils.py[139-175]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`AudioConfig` exposes `input_device_name` / `output_device_name` and documents that they override the numeric index, and the default `config.yaml` now sets these fields. However, the audio backend selection APIs only accept a numeric `device_index` and never consult the configured device names.

## Issue Context
This is user-facing: configuration indicates that device names should be honored, but they are silently ignored.

## Fix Focus Areas
- src/utils/config.py[159-172]
- config.yaml[60-71]
- src/audio/audio_utils.py[139-175]

### Implementation notes
- Add a helper in `audio_utils.py` to resolve a device index from a configured name (exact or substring match) using `list_audio_devices()`.
- Thread the resolved index into `open_input_stream_with_fallback(...)` / output selection.
- Alternatively, if name-based selection is not intended, remove these config fields (and YAML entries/comments) to avoid misleading users.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@chcavignx
chcavignx merged commit b3dbd97 into main Jun 13, 2026
6 checks passed
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Refactor: Migrate audio stack to sounddevice-exclusive backend and expand test coverage
✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

Walkthroughs

Description
• Replaces PyAudio with sounddevice as the exclusive audio backend across ASR, TTS, and wake-word.
• Centralizes device/stream management via new AudioRecorder/AudioPlayer utilities in
  audio_utils.
• Expands test + example infrastructure to validate audio capture, playback, and offline pipelines.
Diagram
graph TD
    subgraph src_audio["src/audio"]
        AU["audio_utils.py\n(Recorder/Player/Streams)"] --> SD[("sounddevice")]
        ASR["asr.py\n(ASREngine)"] --> AU
        TTS["tts.py\n(TTSEngine)"] --> AU
        WW["wake_word.py\n(WakeWordDetector)"] --> AU
        VAD["vad.py\n(VADEngine)"] --> AU
    end

    LOG["log.json"] --> CFG["config.py\n(Config + logging)"] --> AU

    subgraph tests["tests"]
        CONF["conftest.py"]
        TAU["test_audio_utils.py"]
        TENG["test_audio_engine_units.py"]
        TBK["test_audio_backends.py"]
        TWW["test_wake_word_coverage.py"]
    end

    AU --> TAU --> CONF
    ASR --> TENG
    TTS --> TENG
    AU --> TBK
    WW --> TWW

    subgraph Legend
      direction LR
      _mod["Module"] ~~~ _ext[("External")] ~~~ _file["Config/File"]
    end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Maintain dual backend (sounddevice + PyAudio fallback)
  • ➕ Less risky for environments where PortAudio/sounddevice is problematic
  • ➕ Eases transition for existing users
  • ➖ Perpetuates complex branching paths
  • ➖ Higher long-term maintenance/testing burden
2. Split into smaller PRs (backend refactor / tests+examples / CI+deps)
  • ➕ Much easier to review and revert
  • ➕ Improves bisectability if regressions appear
  • ➖ More coordination overhead
  • ➖ Intermediate PRs may temporarily reduce green CI

Recommendation: The sounddevice-exclusive consolidation is strategically sound and simplifies long-term maintenance. The main improvement would be splitting the change-set (core backend refactor vs testing/examples vs CI/deps) to reduce review risk; also watch for side-effectful logging setup in config.py being executed at import time.

Grey Divider

File Changes

Enhancement (4)
list_audio_devices.py Add audio device diagnostics script +167/-0

Add audio device diagnostics script

• New script enumerating input/output devices and capabilities to help debug setup on different machines.

scripts/tests/list_audio_devices.py


main.py Initialize config-driven logging at startup +5/-0

Initialize config-driven logging at startup

• Calls 'setup_config_logging()' so runtime uses the JSON logging configuration early.

src/main.py


config.py Add config logging setup, new fields, and stronger safety check +39/-14

Add config logging setup, new fields, and stronger safety check

• Adds tmp path, audio device-name selection, ASR audio storage fields, logging config bootstrapping, and fixes unsafe-config-path raise placement.

src/utils/config.py


log_filters.py Add logging filter utilities +16/-0

Add logging filter utilities

• New helper module to support structured logging and filtering for the project.

src/utils/log_filters.py


Bug fix (1)
vad.py Fix imports and Raspberry Pi tuning calls in VAD +6/-8

Fix imports and Raspberry Pi tuning calls in VAD

• Cleans duplicate imports, fixes import paths to 'src.utils.sysutils', and ignores returned values from CPU limiting helpers.

src/audio/vad.py


Refactor (18)
text2speech_piper.py Simplify Piper example to use AudioPlayer +14/-37

Simplify Piper example to use AudioPlayer

• Refactors the Piper TTS example to rely on the new audio playback utilities instead of manual stream handling.

examples/TTS/text2speech_piper.py


voice_agent_offline.py Update offline voice agent example to new audio abstractions +22/-7

Update offline voice agent example to new audio abstractions

• Moves capture/playback responsibilities to the shared audio utilities to match the new backend architecture.

examples/VAD/voice_agent_offline.py


test_hardware_detection.py Refactor hardware detection example +19/-42

Refactor hardware detection example

• Simplifies device enumeration/detection based on new audio utilities.

examples/test_hardware_detection.py


test_playback.py Refactor playback example for new AudioPlayer APIs +131/-150

Refactor playback example for new AudioPlayer APIs

• Routes playback through 'AudioPlayer' and adds file-based playback validation.

examples/test_playback.py


test_recording.py Simplify recording example using AudioRecorder +60/-210

Simplify recording example using AudioRecorder

• Replaces manual stream logic with 'AudioRecorder' to reduce complexity and standardize capture.

examples/test_recording.py


test_stream_open_close.py Simplify stream lifecycle example +66/-145

Simplify stream lifecycle example

• Updates stream open/close checks to align with new stream abstractions in 'audio_utils'.

examples/test_stream_open_close.py


fast_whisper_objects.py Minor faster-whisper objects cleanup +1/-2

Minor faster-whisper objects cleanup

• Small tweaks to object definitions/usage to align with refactor.

scripts/models/audio/fast_whisper_objects.py


load_huggingface_objects.py Adjust HuggingFace loader utilities +6/-10

Adjust HuggingFace loader utilities

• Cleans up loading helpers and aligns interfaces to new configuration expectations.

scripts/models/audio/load_huggingface_objects.py


piper_models.py Minor Piper models tweak +1/-1

Minor Piper models tweak

• Small update to Piper model list/handling.

scripts/models/audio/piper_models.py


vosk_models.py Minor Vosk models tweak +1/-2

Minor Vosk models tweak

• Small update to Vosk model list/handling.

scripts/models/audio/vosk_models.py


whisper_objects.py Clean up Whisper model objects/utilities +3/-6

Clean up Whisper model objects/utilities

• Minor refactor/cleanup to keep Whisper object creation consistent with updated imports/deps.

scripts/models/audio/whisper_objects.py


__init__.py Remove stray package init content +0/-1

Remove stray package init content

• Removes an unused line to keep package init minimal/clean.

src/init.py


__init__.py Relax lazy imports for whisper/faster_whisper +7/-10

Relax lazy imports for whisper/faster_whisper

• Switches lazy loading to not error on import for optional model packages and adds explicit '__all__'.

src/audio/init.py


asr.py Migrate ASR capture to sounddevice abstractions +165/-233

Migrate ASR capture to sounddevice abstractions

• Reworks ASR input capture to use 'AudioInputStream' + fallback probing and consolidates resampling support; updates logging naming.

src/audio/asr.py


audio_utils.py Introduce comprehensive sounddevice-based audio utilities +1241/-140

Introduce comprehensive sounddevice-based audio utilities

• Major rewrite adding unified backend selection, device management, stream abstractions, 'AudioPlayer', 'AudioRecorder', and fallback open logic; becomes the central audio I/O layer.

src/audio/audio_utils.py


tts.py Route TTS playback through AudioPlayer (sounddevice-only) +40/-165

Route TTS playback through AudioPlayer (sounddevice-only)

• Removes PyAudio protocols/logic and uses 'AudioPlayer' for playback; adds clearer attribute typing and library-level logging.

src/audio/tts.py


wake_word.py Migrate wake-word capture to sounddevice abstractions +81/-179

Migrate wake-word capture to sounddevice abstractions

• Removes PyAudio protocols and uses 'AudioInputStream' + fallback open logic; updates typing/logging structure.

src/audio/wake_word.py


sysutils.py Simplify sysutils typing and standardize logger naming +7/-8

Simplify sysutils typing and standardize logger naming

• Refactors logger naming to library-level and simplifies psutil memory handling without TYPE_CHECKING casts.

src/utils/sysutils.py


Documentation (9)
DEV_PROCESS.md Refresh development process documentation +4/-4

Refresh development process documentation

• Minor updates reflecting new workflows/tooling expectations.

docs/DEV_PROCESS.md


STS_VAD_models.md Small documentation corrections for STS/VAD models +2/-3

Small documentation corrections for STS/VAD models

• Clarifies wording/links around model usage (minor edits).

docs/STS_VAD_models.md


STT_offline.md Minor offline STT documentation update +1/-1

Minor offline STT documentation update

• Small edits to keep offline STT guidance consistent with refactor.

docs/STT_offline.md


TTS_offline.md Update offline TTS docs for sounddevice-only playback +3/-13

Update offline TTS docs for sounddevice-only playback

• Removes/updates PyAudio references and documents sounddevice-only behavior.

docs/TTS_offline.md


AUDIO_TESTS_README.md Document expanded standalone audio test suite +144/-1

Document expanded standalone audio test suite

• Adds detailed descriptions and run instructions for new recorder/ASR/VAD/wake-word/TTS lifecycle standalone tests.

examples/AUDIO_TESTS_README.md


QUICK_START_AUDIO_TESTS.sh Update quick-start script for new audio tests +21/-1

Update quick-start script for new audio tests

• Adds/updates commands to run the expanded set of example audio tests.

examples/QUICK_START_AUDIO_TESTS.sh


vosk_test_simple.py Minor Vosk example adjustments +1/-2

Minor Vosk example adjustments

• Small cleanup to keep Vosk example aligned with updated stack.

examples/STT/vosk/vosk_test_simple.py


test_whisper.py Update Whisper example usage +8/-6

Update Whisper example usage

• Minor adjustments to Whisper example for new dependency/loading patterns.

examples/STT/whisper/test_whisper.py


test_whisper_w_huggingface.py Refine Whisper+HF example flow +43/-36

Refine Whisper+HF example flow

• Updates the HuggingFace-based Whisper example to reflect model-loading and configuration changes.

examples/STT/whisper/test_whisper_w_huggingface.py


Other (29)
action.yml Adjust uv setup action behavior +1/-1

Adjust uv setup action behavior

• Tweaks the composite action used for uv-based setup (minor config change).

.github/actions/python-uv-setup/action.yml


run_tests.yml Update CI to support sounddevice/system deps and caching +4/-0

Update CI to support sounddevice/system deps and caching

• Enables uv cache and installs system packages for lint/test jobs to support Linux x86_64 execution and native audio dependencies.

.github/workflows/run_tests.yml


.pre-commit-config.yaml Minor pre-commit configuration tweak +1/-1

Minor pre-commit configuration tweak

• Small adjustment to pre-commit settings to align with updated tooling.

.pre-commit-config.yaml


config.yaml Retune audio/VAD/ASR/wake defaults and add platform section +36/-16

Retune audio/VAD/ASR/wake defaults and add platform section

• Updates audio sample rates/devices, adjusts VAD thresholds/timeouts, enables ASR audio storage, changes wake model defaults, and adds platform CPU core hints.

config.yaml


INTEGRATION_TEST_SUMMARY.py Add integration test summary runner +126/-0

Add integration test summary runner

• New script that summarizes execution/results across the example audio tests.

examples/INTEGRATION_TEST_SUMMARY.py


run_all_audio_tests.py Update example test orchestrator +11/-24

Update example test orchestrator

• Refreshes the runner to include/fit the new set of standalone example audio tests.

examples/run_all_audio_tests.py


test_asr_recording_validation.py Add mic recording + ASR validation example +127/-0

Add mic recording + ASR validation example

• New standalone script validating microphone capture and transcription in a realistic pipeline.

examples/test_asr_recording_validation.py


test_asr_with_tts.py Add TTS→ASR loopback integration example +123/-0

Add TTS→ASR loopback integration example

• New script synthesizing speech via TTS and feeding WAV bytes to ASR for transcription verification.

examples/test_asr_with_tts.py


test_recorder_standalone.py Add standalone AudioRecorder validation script +95/-0

Add standalone AudioRecorder validation script

• New script that validates 'AudioRecorder' start/stop and chunk formatting on real hardware.

examples/test_recorder_standalone.py


test_tts_lifecycle_and_utils.py Add TTS lifecycle + utilities validation script +163/-0

Add TTS lifecycle + utilities validation script

• New script exercising queue-based TTS lifecycle, interruptions, and audio utility conversions.

examples/test_tts_lifecycle_and_utils.py


test_vad_standalone.py Add standalone VAD validation script +101/-0

Add standalone VAD validation script

• New script validating Silero VAD segmentation/resampling behavior.

examples/test_vad_standalone.py


test_wake_word_standalone.py Add standalone wake-word validation script +63/-0

Add standalone wake-word validation script

• New script that loads wake-word models and validates capture/callback behavior.

examples/test_wake_word_standalone.py


log.json Add JSON logging configuration +47/-0

Add JSON logging configuration

• Adds 'logging.config.dictConfig'-compatible JSON used by config/main startup.

log.json


pyproject.toml Bump Python version, refactor deps, and tune coverage/test config +105/-18

Bump Python version, refactor deps, and tune coverage/test config

• Raises Python requirement to 3.11, promotes sounddevice/openwakeword/piper-tts/faster-whisper, removes pyaudio, adds test deps, and configures coverage/run/report behavior.

pyproject.toml


requirements.txt Refresh pinned dependencies +2914/-150

Refresh pinned dependencies

• Large lock/pin update reflecting new dependency set (sounddevice-first stack).

requirements.txt


_helpers.py Update audio test helpers for new capture layer +6/-13

Update audio test helpers for new capture layer

• Adjusts helper utilities to align with sounddevice-based device detection/capture paths.

tests/audio/_helpers.py


test_asr_vad_logic.py Minor ASR/VAD logic test adjustments +4/-2

Minor ASR/VAD logic test adjustments

• Small updates to expected behavior after refactor.

tests/audio/test_asr_vad_logic.py


test_audio_backends.py Add sounddevice exclusivity test +33/-0

Add sounddevice exclusivity test

• New tests asserting 'sounddevice' is the only backend and engines default to it.

tests/audio/test_audio_backends.py


test_audio_engine_units.py Expand unit tests for ASR/TTS and mocking behavior +462/-231

Expand unit tests for ASR/TTS and mocking behavior

• Adds broader mocking (including optional torch/whisper), marks tests as 'basic', and adjusts expectations around runtime config/setup.

tests/audio/test_audio_engine_units.py


test_audio_utils.py Massively expand audio_utils unit tests with sounddevice mocks +852/-141

Massively expand audio_utils unit tests with sounddevice mocks

• Adds extensive mocked sounddevice module and validation for new device/stream/recorder/player utilities; marks suite as 'basic'.

tests/audio/test_audio_utils.py


test_e2e_mic_asr.py Use AudioRecorder for mic capture in E2E ASR test +21/-31

Use AudioRecorder for mic capture in E2E ASR test

• Replaces manual PyAudio stream handling with 'AudioRecorder', improves cleanup, and uses 'asr.load()' for setup.

tests/audio/test_e2e_mic_asr.py


test_e2e_sample_audio_transcription.py Minor E2E transcription test adjustments +2/-4

Minor E2E transcription test adjustments

• Updates paths/expectations to remain compatible with refactor.

tests/audio/test_e2e_sample_audio_transcription.py


test_e2e_tts_asr.py Minor TTS→ASR E2E adjustments +3/-2

Minor TTS→ASR E2E adjustments

• Small updates aligning integration behavior with new TTS/audio utilities.

tests/audio/test_e2e_tts_asr.py


test_tts_queue.py Streamline TTS queue tests to avoid unnecessary engine loads +6/-6

Streamline TTS queue tests to avoid unnecessary engine loads

• Removes redundant model loading to keep queue tests fast and isolated.

tests/audio/test_tts_queue.py


test_vad_wakeword_units.py Tighten backend configuration for VAD/wake-word tests +14/-10

Tighten backend configuration for VAD/wake-word tests

• Ensures consistent backend configuration during wake-word/VAD unit tests after backend refactor.

tests/audio/test_vad_wakeword_units.py


test_wake_word_coverage.py Add comprehensive wake-word detector coverage tests +340/-0

Add comprehensive wake-word detector coverage tests

• New high-coverage tests for WakeWordDetector lifecycle, threads, cooldown, and error handling using mocks.

tests/audio/test_wake_word_coverage.py


conftest.py Add pytest session hooks for subset coverage gating +62/-0

Add pytest session hooks for subset coverage gating

• Adds repo-root import path setup and relaxes coverage fail-under for audio-only / 'basic' marker runs.

tests/conftest.py


test_config.py Update config tests for new config fields and safety checks +37/-33

Update config tests for new config fields and safety checks

• Extends coverage for new config options and validates corrected config-path safety enforcement.

tests/utils/test_config.py


test_sysutils.py Adjust sysutils tests for typing/logger refactor +26/-19

Adjust sysutils tests for typing/logger refactor

• Updates expectations for logger naming and psutil memory access changes.

tests/utils/test_sysutils.py


Grey Divider

Qodo Logo

Comment thread src/audio/tts.py
Comment on lines 160 to 166
"""Clear queue and stop current playback."""
while not self._tts_queue.empty():
try:
self._tts_queue.get_nowait()
_item = self._tts_queue.get_nowait()
except queue.Empty:
break
logger.debug("TTS interrupted")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Tts interrupt hangs wait 🐞 Bug ☼ Reliability

TTSEngine.interrupt() drains _tts_queue without calling task_done(), so wait() (Queue.join) and
speak(blocking=True) can block indefinitely. It also never stops in-progress sounddevice playback,
so “interrupt” does not actually interrupt current audio output.
Agent Prompt
## Issue description
`TTSEngine.interrupt()` removes queued items using `Queue.get_nowait()` but does not call `Queue.task_done()` for those items. Since `wait()` uses `Queue.join()`, this can leave `unfinished_tasks` > 0 forever and hang `wait()` / `speak(blocking=True)`.

Additionally, `interrupt()` claims to stop current playback but does not stop the active sounddevice playback.

## Issue Context
The playback loop correctly calls `task_done()` for items it consumes, but `interrupt()` is another consumer of the same queue and must also decrement the unfinished task counter for every removed item.

## Fix Focus Areas
- src/audio/tts.py[159-170]
- src/audio/tts.py[181-203]

### Implementation notes
- In `interrupt()`, for every successfully dequeued item, call `self._tts_queue.task_done()`.
- If the dequeued item is the `None` sentinel, consider re-enqueueing it (or avoid consuming it) so shutdown semantics remain intact.
- To actually stop current playback, add a backend stop call (e.g., `sounddevice.stop()`) via `AudioPlayer` (preferred) or directly in `TTSEngine.interrupt()`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant