Skip to content

audio: [audio] host picks the host audio system; PulseAudio on request (needs testing) - #220

Merged
techomancer merged 1 commit into
techomancer:mainfrom
atomchild411:linux-audio
Oct 13, 2026
Merged

techomancer merged 1 commit into
techomancer:mainfrom
atomchild411:linux-audio

Conversation

@atomchild411

Copy link
Copy Markdown
Collaborator

Needs testing on more Linux setups. Tested only on Debian 12 arm64 in a Parallels VM (PipeWire). Nothing changes unless you ask for it: the default host is still ALSA on Linux, and every other platform is untouched.

What changes

  • [audio] host (and --audio-host): "default" (the platform's usual host: CoreAudio, WASAPI, and ALSA on Linux, as before), "alsa", or "pulseaudio", a new opt-in on Linux. It plays through cpal's own PulseAudio client, which PipeWire answers too through pipewire-pulse, and falls back to ALSA when no server answers. That client is cpal's pulseaudio feature: pure Rust over the server's socket, so the build links nothing new (checked: the Linux binary links only libasound). iris-gui gets a matching "Host audio" choice in the Audio section, shown on Linux only.
  • With the feature compiled in, cpal's own default_host() would prefer PulseAudio whenever its socket exists, so HAL2 now picks the host by id (output_host in src/dev/hal2.rs). That's what keeps ALSA the default.
  • [audio] cpal_buffer_frames now works. It was parsed and shown in iris-gui but never reached HAL2 (HPC3 dropped the config). It now sets the host buffer size, and a size the device refuses falls back to the host's default. When it's unset, PulseAudio gets a 512-frame period (cpal targets two, ~21 ms at 48 kHz). PulseAudio's own default target is about two seconds, which made IRIX's sound arrive that late. prebuf_ms is still unused, and the docs now say so.
  • Docs: FEATURES.md, HELP.md, iris.toml, docs/hal2.md, CHANGELOG, and rules/testing/cpal-alsa-busy-poll.md.

Why

On this VM, cpal's ALSA backend spins a whole core: its worker returns from poll() about five million times a second, because the descriptor stays ready while avail < avail_min. That happens through PipeWire's ALSA plugin and on the card's own device alike, while aplay with the same sizes sleeps. The starved emulator underruns constantly, and an IP28 once stayed in its power-on diagnostics with the red LED lit. Through PulseAudio the same stream used 0.01 s of CPU in 5 s.

I have not reproduced the ALSA spin on bare metal, so ALSA stays the default. If it turns up elsewhere (and once it's reported to cpal), ALSA could become the fallback only.

Measured

Debian 12 arm64 VM (4 vCPUs), IP28 + Max IMPACT, IRIX 6.5.22; a demo's synthesized score played for 30 s, HAL2 output captured:

ALSA (default) host = "pulseaudio"
cpal's audio thread cpal_alsa_out 101% of a core under 1%
xrun / stream-error lines 107 0
score dropouts 6.5/s (69 ms lost/s) 7.8/s (82 ms lost/s)

PulseAudio removes the spinning thread and the stream errors, but on a 4-vCPU VM the dropouts stay. They aren't the host audio this time: HAL2's ring runs dry because the emulator itself is starved, mostly by hptimer, which spins a core on every machine iris emulates (HAL2 paces each channel at 4 kHz and the 8254 runs at 1 kHz). A separate, not yet submitted change that lets hptimer sleep took the same run to 1.5 dropouts/s (26 ms lost/s) together with this one. On a host with spare cores this PR's part matters less, and on one short of cores both are needed.

CI-equivalent run (Ubuntu 24.04 container with CI's packages): workspace builds, cargo test --workspace passes (1031 lib tests, including a new [audio] host config test), and the lightning,gpujit,jitv2 build works. The Mac builds unchanged, iris-gui included.

Wanted

Testing on other Linux machines, especially bare metal with a plain PulseAudio server and with PipeWire. For each, it'd help to know: does host = "pulseaudio" play cleanly, and does the default (ALSA) show a cpal_alsa_out thread near 100% in top -H?

🤖 Generated with Claude Code

`[audio] host` (`--audio-host`): "default" keeps each platform's usual host
(CoreAudio, WASAPI, and ALSA on Linux, as before); "alsa"; "pulseaudio"
plays through cpal's own PulseAudio client, which PipeWire answers too
through pipewire-pulse, and falls back to ALSA when no server answers. The
client is cpal's `pulseaudio` feature, pure Rust over the server's socket:
the build links nothing new.

Why: on some Linux setups cpal's ALSA backend spins a whole core. In a
Debian 12 arm64 VM its worker returned from poll() about five million
times a second (the descriptor stays ready while avail < avail_min),
through PipeWire's ALSA plugin and the card's own device alike, while
aplay with the same sizes slept. The starved emulator underran
constantly, and an IP28 once sat in its power-on diagnostics. The
PulseAudio client played the same stream on 0.01 s of CPU in 5 s. The
default stays ALSA until that is confirmed on real hardware.

The feature makes cpal's own default_host() prefer PulseAudio whenever its
socket exists, so HAL2 now picks the host by id (output_host).

`[audio] cpal_buffer_frames` was parsed but never reached HAL2 (HPC3
dropped the config); it now sets the host buffer size, falling back to the
host's default if the device refuses it. Unset, PulseAudio gets a 512-frame
period (cpal targets two, ~21 ms at 48 kHz) instead of its own default
target of about two seconds, which made IRIX's sound arrive that late.
`prebuf_ms` is still not used.

iris-gui: a Host audio choice in the Audio section, on Linux. Docs:
FEATURES.md, HELP.md, iris.toml, docs/hal2.md, CHANGELOG,
rules/testing/cpal-alsa-busy-poll.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@techomancer
techomancer merged commit 5da263f into techomancer:main Oct 13, 2026
5 of 6 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.

2 participants