audio: host output device selection, enable/disable, and stereo controls#124
Merged
Merged
Conversation
571f149 to
5661ef0
Compare
There was a problem hiding this comment.
Pull request overview
This PR adds host-side audio output configuration on top of the existing cpal sink: selecting an output device (including live switching and device-loss recovery), enabling/disabling audio via a null sink, and shaping the stereo image via mono mode and stereo separation.
Changes:
- Adds a host audio output selection model (
Default/Device(name)/Disabled) plus device enumeration/filtering and live sink rebuilding. - Introduces channel-mode (stereo/mono) and stereo-separation controls applied at Paula’s host-output boundary and preserved across save-state loads.
- Extends CLI/config/docs with audio device listing/selection and the new output shaping options.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/video/window.rs | Tracks session audio output selection; adds runtime menu cycling and device-loss recovery; wires labels into UI. |
| src/video/ui.rs | Adds “Audio Out” runtime menu item and label handling; adjusts clipping and tests. |
| src/video/launcher.rs | Adds launcher fields for audio device/channel mode/separation and refreshes host device lists. |
| src/main.rs | Adds CLI flags for listing/selecting devices and shaping output; implements config-vs-CLI precedence for enabling audio. |
| src/emulator.rs | Preserves mono/separation across state loads/restores; applies shaping during machine build. |
| src/config.rs | Adds [audio] keys and parsing/validation for output device/enabled/channel mode/separation plus override plumbing and tests. |
| src/chipset/paula.rs | Implements mono + mid/side stereo width control in the mixer; skips these host prefs in save states; adds tests. |
| src/audio.rs | Adds AudioOutput abstraction, device enumeration/filtering, ALSA probe log silencing, device selection, and device-lost signaling. |
| README.md | Documents new audio output selection and shaping features at a high level. |
| docs/guide/configuration.md | Documents new [audio] keys, behavior, and Linux limitations. |
| copperline.example.toml | Adds commented example keys for new audio output configuration options. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Add host audio-output configuration on top of the existing cpal sink: choose
the output device and shape the stereo image. Pure cpal -- one code path, no
per-platform FFI -- so it enumerates and selects the same on CoreAudio, WASAPI
and ALSA. Nothing here touches the emulated audio.
Device selection: --list-audio-devices, --audio-device NAME (case-insensitive
substring) and [audio] output_device, falling back to the system default with a
warning if the name no longer matches. Also a launcher field ("Audio output",
top of the A/V & Emu tab) and a runtime-menu item that switches the device live
(rebuilt on the main thread; the cpal Stream is !Send on macOS). Both pickers
re-read the device list on each step, so a reconnected device reappears without
a restart. If the live device disappears, DeviceNotAvailable is flagged from the
stream error callback and the window reopens on the current default the next
frame, resetting the selection to Default.
Disabled option: the launcher picker and runtime menu cycle Default -> devices
-> Disabled, where Disabled swaps in a null sink for no sound -- the same effect
as --noaudio, switchable live while emulating. It persists as [audio]
output_enabled = false; the --audio/--noaudio CLI flags still override it and the
CLI is otherwise unchanged.
Linux list: cpal's ALSA enumeration lists each card many times over and
libasound prints probe chatter to stderr; the list is filtered to the clean
routes by ALSA's fixed plugin-type token (no device names hard-coded) and a
no-op ALSA error handler silences the chatter (the one cfg(linux) FFI). The GUI
also drops ALSA's "default" when it is the system default, since the picker
offers a synthetic "Default".
Stereo shaping: a channel mode (stereo/mono) and a stereo separation (0-100) --
a mid/side width control where 100 leaves the hardware panning bit-for-bit
unchanged and 0 is mono; mono mode is a forced 0. Applied in Paula's push next
to the master volume, so it covers live and WAV output and survives device
switches. Host preferences, not machine state: serde-skipped on Paula, applied
from config at build and carried across save-state loads, so no STATE_VERSION
change and existing saves keep loading.
Config/CLI/GUI throughout; docs in the configuration guide and
copperline.example.toml. Off by nothing -- a config with no [audio] keys behaves
exactly as before.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5661ef0 to
fbeefd9
Compare
LinuxJedi
approved these changes
Jul 6, 2026
LinuxJedi
left a comment
Owner
There was a problem hiding this comment.
Excellent stuff. Many thanks!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Host audio-output configuration on top of the existing cpal sink: choose the output device, turn sound on/off, and shape the stereo image. Pure cpal -- one code path, no per-platform FFI -- so it works the same on CoreAudio, WASAPI and ALSA. Nothing touches the emulated audio; it all sits at the host-output boundary (a config with no
[audio]keys behaves exactly as before).Device selection
--list-audio-devices,--audio-device NAME(case-insensitive substring) and[audio] output_device; unmatched/missing names fall back to the system default.Streamis!Sendon macOS) and swapped into Paula.DeviceNotAvailablefires; the sink reopens on the default next frame and the selection resets to "Default", so sound continues rather than dying until restart.Enable / disable
The picker's last step is Disabled, which swaps in a null sink -- no sound, the same effect as
--noaudio, switchable live while emulating. It persists as[audio] output_enabled = false; the launcher greys out Channel mode and Stereo separation while it's off. The CLI is unchanged:output_enabled = falseonly silences default-on audio, while an explicit--audiostill forces sound on and--noaudio/--audio-wavstill win. Every sink outcome now logs a line --cpal sink ready ...for a live device,audio: disabled (null sink); no soundfor the silent path.Linux device list
cpal enumerates via ALSA, which lists each card many times over, and libasound prints
ALSA lib pcm_...chatter while probing. The list is filtered to the clean routes by ALSA's fixed plugin-type token (no device names hard-coded), and a no-op ALSA error handler silences the chatter (the onlycfg(linux)FFI).Full per-device selection on Linux isn't possible through cpal/ALSA: under PipeWire/PulseAudio individual sinks aren't ALSA devices, so only the
defaultroute is offered (pick the output in the system mixer and Copperline follows it). True parity would need cpal'sjackbackend against pipewire-jack, which adds a libjack build dependency -- deliberately not done. macOS/Windows select each device directly.Stereo/mono and separation
#[serde(skip)]on Paula, set from config at build and carried across save-state loads -- noSTATE_VERSIONchange, existing saves keep loading.Verification
-D warnings+ fmt clean, default and--no-default-features; 1318 tests, 0 failures.--noaudioand the runtime "Disabled" toggle both log the silent line. Linux list clean on PipeWire/ALSA; Windows/WASAPI clean out of the box.output_enabledparse/defaults, CLI enable/disable precedence, mid/side math (100/50/0%), launcher cycling (arrow direction, greyed in mono and when disabled), and picker refresh after a config load.[audio];--helpandcopperline.example.tomllist the flags/keys.Screens