Skip to content

audio: host output device selection, enable/disable, and stereo controls#124

Merged
LinuxJedi merged 1 commit into
LinuxJedi:mainfrom
hobbo91:feature/audio-device
Jul 6, 2026
Merged

audio: host output device selection, enable/disable, and stereo controls#124
LinuxJedi merged 1 commit into
LinuxJedi:mainfrom
hobbo91:feature/audio-device

Conversation

@hobbo91

@hobbo91 hobbo91 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

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.
  • A launcher field ("Audio output", top of the A/V & Emu tab) and a runtime menu item that switch the device live -- the sink is rebuilt on the main thread (the cpal Stream is !Send on macOS) and swapped into Paula.
  • Both pickers re-read the device list on each step, on demand (no polling), so a device connected/reconnected mid-session shows up without a restart.
  • If the live device disappears, cpal's DeviceNotAvailable fires; 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 = false only silences default-on audio, while an explicit --audio still forces sound on and --noaudio/--audio-wav still win. Every sink outcome now logs a line -- cpal sink ready ... for a live device, audio: disabled (null sink); no sound for 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 only cfg(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 default route is offered (pick the output in the system mixer and Copperline follows it). True parity would need cpal's jack backend against pipewire-jack, which adds a libjack build dependency -- deliberately not done. macOS/Windows select each device directly.

Stereo/mono and separation

  • Channel mode (Stereo / Mono) and stereo separation (0-100). Separation is a mid/side width control: 100 = untouched hardware panning, 0 = mono, between = narrowed; Mono is just a forced 0.
  • Applied in Paula's push next to the master volume, so it covers live and WAV output and survives device switches (and Disabled).
  • Host preferences, not machine state: #[serde(skip)] on Paula, set from config at build and carried across save-state loads -- no STATE_VERSION change, existing saves keep loading.

Verification

  • clippy -D warnings + fmt clean, default and --no-default-features; 1318 tests, 0 failures.
  • Device selection, live switching, disable/enable, and disconnect->default verified on macOS, Windows and Linux; --noaudio and the runtime "Disabled" toggle both log the silent line. Linux list clean on PipeWire/ALSA; Windows/WASAPI clean out of the box.
  • Unit tests cover the ALSA filter, redundant-"default" logic, the 3-state Default/device/Disabled cycle, output_enabled parse/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.
  • Docs: README "Audio output" + configuration guide [audio]; --help and copperline.example.toml list the flags/keys.

Screens

Screenshot 2026-07-05 at 16 07 13 Screenshot 2026-07-05 at 16 33 44 Screenshot 2026-07-05 at 16 34 36 Screenshot 2026-07-05 at 16 18 40 Screenshot 2026-07-05 at 21 31 07 Screenshot 2026-07-06 at 06 09 39 image Screenshot 2026-07-06 at 06 39 58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread src/video/window.rs Outdated
Comment thread src/video/window.rs
Comment thread src/video/window.rs Outdated
Comment thread src/video/window.rs
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>
@hobbo91 hobbo91 force-pushed the feature/audio-device branch from 5661ef0 to fbeefd9 Compare July 6, 2026 05:59

@LinuxJedi LinuxJedi left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Excellent stuff. Many thanks!

@LinuxJedi LinuxJedi merged commit 8de8e1d into LinuxJedi:main Jul 6, 2026
8 checks passed
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.

3 participants