feat: installable live audio playback — [audio] extra, --device selection, friendly device errors#6
Conversation
…tion, friendly device errors `harmonics ... --play` was unusable out of the box: no audio backend shipped with the package, and even after installing one it opened the raw ALSA default device — which on a multi-sink host is often a fixed-rate sink (e.g. a 16 kHz USB audio adapter) that rejects the synth's 44.1 kHz, failing with a misleading "unexpected error, file a bug". - Add opt-in `audio` extra (sounddevice>=0.4.6 + numpy>=1.24). Core deps stay empty/offline-testable per the CLAUDE.md invariant; the extra is opt-in so importing the package still needs no sound stack. sounddevice over simpleaudio: it ships cp312 wheels bundling PortAudio. - Add --device NAME|INDEX + $HARMONICS_AUDIO_DEVICE (flag wins) on play/say/demo. With none set, prefer a resampling sound-server device (pipewire, then pulse) so playback works when the default sink is fixed-rate. - Convert live-device failures (PortAudioError) into a friendly exit-2 CliError that lists output devices and points at --device/--wav, instead of the "unexpected error, file a bug" wrapper. - Docs (README, explain catalog, CLAUDE.md), CHANGELOG, version 0.6.0 -> 0.7.0, and 17 new tests. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01VMUvDkSHhthqU3c5HrhQuj
|
/agentic_review |
PR Summary by QodoInstallable live audio playback extra with device selection and friendly errors
AI Description
Diagram
High-Level Assessment
Files changed (14)
|
Code Review by Qodo
1.
|
… (Qodo review) Addresses two Qodo review comments on PR #6: - #3544349308 (Bug/Correctness — "Device ignored with simpleaudio"): play()/_resolve_backend() tried simpleaudio FIRST and returned early, so --device / $HARMONICS_AUDIO_DEVICE and the pipewire/pulse auto-preference (sounddevice-only) were silently ignored whenever simpleaudio was installed — contradicting the --play docs. Reorder to prefer sounddevice (the only backend that honors device selection); simpleaudio is now a fallback used only when sounddevice is unavailable. New regression tests assert sounddevice wins when both backends are present and that --device is honored. - #3544349315 (Maintainability — "Private helper coupling"): demo/play.py imported underscore-prefixed helpers from audio/synth.py. Extract select_output_device / output_device_listing / device_playback_error into a new internal module harmonics/audio/_playback.py; synth.py and demo/play.py both import from there. No cross-module private coupling. Docstrings, help text, and CHANGELOG updated to the new backend order; fallback-path tests made deterministic (patch sounddevice=None) and renamed for honesty. Full suite 2357 passed; black/isort/flake8/bandit/teken clean. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01VMUvDkSHhthqU3c5HrhQuj
|



What & why
harmonics ... --playwas unusable out of the box: no audio backend shippedwith the package, and even after installing one it opened the raw ALSA
default device — which on a multi-sink host is often a fixed-rate sink (e.g. a
16 kHz USB audio adapter) that rejects the synth's 44.1 kHz, failing with a
misleading "unexpected error, file a bug".
Changes
Installable playback (opt-in extra)
pyproject.toml:[project.optional-dependencies]audio = ["sounddevice>=0.4.6", "numpy>=1.24"]. Install withuv tool install 'harmonics-cli[audio]'.(simpleaudio has none / needs a C build); numpy is required by
sounddevice.play().dependenciesstay empty — the extra is opt-in, so importing thepackage still requires no sound stack. This preserves the CLAUDE.md
"runtime deps stay empty / offline-testable" invariant and the
no-backend tests, which assume the real env has no backend.
Device selection
--device NAME|INDEX+$HARMONICS_AUDIO_DEVICE(flag overrides env) onplay/say/demo.pipewire,then
pulse) over the raw default, so--playworks when the default sinkis fixed-rate.
Friendly device errors
surfaces as an exit-2
CliErrorthat names the failure, lists availableoutput devices, and points at
--device/--wav— not "file a bug".Docs & tests
explaincatalog entries (play/say/demo),CLAUDE.md invariant note, CHANGELOG, version
0.6.0→0.7.0.device error, env/flag precedence).
Verification
pytest -n auto→ 2355 passedteken cli doctor . --strict→ rc=0pipewireandplays; forcing the 16 kHz sink yields the friendly exit-2 error with the
device listing.
🤖 Generated with Claude Code
https://claude.ai/code/session_01VMUvDkSHhthqU3c5HrhQuj