Skip to content

config, host_audio: say which machines have audio and where it plays, and give every card one link to its host stream - #694

Open
jamesbraid wants to merge 2 commits into
audio/resamplerfrom
audio/config
Open

jamesbraid wants to merge 2 commits into
audio/resamplerfrom
audio/config

Conversation

@jamesbraid

@jamesbraid jamesbraid commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

Audio is set today by four keys that mean different things on different machines. no_audio removes HAL2 from an Indy or Indigo2, which the guest sees as missing hardware, but only silences the O2, Octane and Onyx2 cards, which are part of the board. [tezro] audio adds the Tezro's ICE1724, and with no_audio on top it prints a "plays to nowhere" warning. [audio] host and [audio] file choose the destination in two keys, prebuf_ms is read by nothing, and [audio] is the only config table that ignores a misspelt key. Behind the keys, RAD1 and the ICE1724 each carry a copy of the host-stream open and retry code, three cards write the same ring size, and four machines repeat an if !no_audio { enable_output } wrapper.

This PR separates the two questions and gives the host-stream code one home. It is the fourth of the audio cleanup stack and sits on the one-resampler change.

What changes

Commit 1, config: say which machines have audio and where it plays:

  • [machine] audio = true|false (--audio) says whether the machine has its chip. Only the Indy and Indigo2 (HAL2, default present) and the Tezro (ICE1724, default absent) take it. Every other profile refuses it in validate() with an error naming the machine; for the O2, Octane and Onyx2 it points at [audio] output = "none".
  • [audio] output = "default" | "alsa" | "pulseaudio" | "none" | { file = "out.wav" } (--audio-output) says where a present card plays, on every machine. [audio] gets deny_unknown_fields.
  • Old keys and flags still load, forever, and print one line naming their replacement. See the table below.
  • The Tezro's TezroAudio enum, its warning and the validate clause go. The machines ask cfg.audio_card().
  • iris-ci, iris-bench, the bench runner, the CPU test scripts, the example configs and iris-gui move to the new spelling. The GUI's No audio checkbox becomes a Sound card checkbox beside the profile (Indy and Indigo2 only, cleared on a profile change) and an Output list; the unused pre-buffer control goes.

Commit 2, host_audio: one link from a card to its host stream:

  • HostLink holds a card's [audio] settings, the open stream with its resampler and when a failed open happened, and opens once, when a retry is due, or when forced. RAD1 and the ICE1724 use it in place of their copies; Sink and PcmOut lose their retry fields. MACE uses it and still tries once, on the first sound. HAL2 still opens once at start; retry for HAL2 and MACE is a separate change.
  • With output = "none" the link never opens a stream and never retries, so the machines call enable_output unconditionally and the wrappers go.
  • One ring size for every card: 200 ms of the stream's rate. HAL2 had 320 ms, a 20 ms prebuffer times a multiplier of 16 from the initial import with no recorded reason. The size only matters after a host stall of 200 ms or more. The ring_samples parameter of open_output goes with it.
  • The ICE1724 no longer reads the guest rate before taking the sink lock: the resampler starts at the stream's own rate and is rebuilt when the card plays at another one, as it already was on any rate change.
  • A card with output = "none" now has its sink from the start, so IRIS_AUDIO_DUMP records on RAD1 and the ICE1724 with no host stream, as it already did on MACE and HAL2.

Non-test Rust over the two commits: commit 1 +291/-121, commit 2 +162/-158, together +444/-270 (net +174). Commit 1 is larger than it might look because the fold code and its two-way --machine handling are real code; commit 2 is about neutral, with HostLink (about 85 lines with comments) replacing two copies of the open and retry path.

Migration, old to new

Old New Guest sees
no_audio = true on indy_ip24, indigo2_ip22, indigo2_ip28 [machine] audio = false no HAL2, as before
no_audio = true on o2_ip32, octane_ip30, onyx2_ip27 [audio] output = "none" card present and silent, as before
no_audio = true on tezro_ip53 [audio] output = "none"; the card is present only if [machine] audio = true as before
no_audio = true on a machine with no card [audio] output = "none" (unused) as before
no_audio = false dropped as before
[audio] host = X [audio] output = X as before
[audio] file = P [audio] output = { file = P } as before
no_audio = true with file or host as the no_audio rows; file and host are dropped (it won before: the card never got [audio]) as before
[audio] prebuf_ms dropped (nothing read it) as before
[tezro] audio = b [machine] audio = b as before
an old key with its new key also set the new key wins and the line says so as before
--noaudio --audio false (Indy, Indigo2) or --audio-output none (others) as before
--audio-host X --audio-output X as before

A file's no_audio and [tezro] audio are folded again for the machine --machine picks, so iris --config indy.toml --machine octane_ip30 with no_audio = true still gives a silent Octane. --noaudio with --audio or --audio-output, and --audio-host with --audio-output, are parse errors. --noaudio with --audio-host means silence, as it did.

Two edges to be aware of. [audio] rejecting a misspelt key is deliberate and the only intended break. --audio-host now also takes none and file:PATH because it shares the new parser.

What to scrutinise

  • fold_audio_keys, apply_silenced and fold_tezro_audio in src/config.rs, row by row against the table, and the tests beside deprecated_o2_keys_fold_into_shared_settings.
  • HostLink replaces code: RAD1's open_sink, the ICE1724's open_sink and reopen_sink_if_due, MACE's tried_open, Sink/PcmOut retry fields. Lock order in RAD1 is unchanged (state, then sink).
  • RAD1 with output = "none" arms no audio timer, as with the old no_audio (the timer wants a stream wanted).
  • audio status now says host output: none (output = "none") where it said (no_audio).
  • HAL2 on the 200 ms ring: the chime run below.

Verification

Builds: release without LTO, base ea21404b5 against this branch, headless, systemd-run capped at 14 GiB. "Old keys on the new binary" is the claim that matters: no existing config changes what a guest sees.

PROM Command Monitor hinv and audio status, base (old binary, old keys) against headold (this branch, old keys) and head (this branch, new keys), default and silent:

Machine hinv audio status
Indy, Indigo2 IP22, Indigo2 IP28 identical in all three identical by default; silent differs only in HAL2 absent: no_audio is set becoming [machine] audio is false
O2 (PROM 4.18), Octane identical in all three identical except the host output wording, and the O2's frame counts, which follow time

Indy and Octane were run again on the final head with the same result. The default Indy chime is 92150 frames and the Octane's 72064, in both binaries.

audio status on the Onyx2 and Tezro (their PROM hinv was not driven): RAD1 (MediaIO) present by default; the Tezro has No audio device. by default and ICE1724 (DM8) with [machine] audio = true (old: [tezro] audio = true), silent with output = "none" (old: plus no_audio). Identical between old keys on the old binary, old keys here and new keys here, apart from the host output wording. The old binary's "plays to nowhere" line is gone.

IRIX hinv and hinv -c audio over the CI socket, old binary and old keys against this branch with old keys and with new keys, default and silent:

Disk Result
Indy IRIX 5.3 all identical. Default lists Iris Audio Processor: version A2 revision 4.1.0; silent has no audio line, with old and new keys.
Indy IRIX 6.5.22 all identical, but this disk's hinv does not list HAL2 in either mode, so it does not discriminate
O2 IRIX 6.5.30 all identical. Iris Audio Processor: version A3 revision 0 in default and silent.

iris-ci boot-test (serial login, then graphics login against the golden), old iris-ci writing no_audio = true against this branch's writing [machine] audio = false ([audio] output = "none" on the O2):

Disk Result
Indy 5.3 both pass. Serial login 45 s on both, A B B A over eight serial boots 55, 65, 50, 55 s base against 55, 65, 60, 55 s head. Golden: 0 pixels differ on base, 56 on this branch, within the golden's tolerance.
Indy 6.5.22 both pass, golden 44 pixels differ on both. Serial login 240 s base, 280 s head, and 277 to 317 s across the hinv sessions. These ran while I was also building and testing on a host at load average about 100, so I would not read a difference into them.
O2 6.5.30 both pass, golden 0 pixels differ on both. Serial login 125 s base, 140 s head.

HAL2 on the 200 ms ring: the Indy PROM chime into a file output with IRIS_AUDIO_DUMP, base against head and twice each. All four runs played 92150 frames with 0 underruns and 0 frames dropped by the host, and the played samples are identical across the four runs (0 of 92150 frames differ); only the burst chunking varies.

Tests, default and --features developer where it exists: cargo check --tests default, jitv2 and the whole workspace including iris-gui; cargo test -p iris --lib on config::, validate_config, host_audio, host_pcm, dev::hal2, dev::rad1, dev::mace_audio, dev::ice1724, dev::hpc3, ip53_machine, ip30_machine, ip32_machine, ip27_deskside, ip27_machine, machine::, monitor and bench_runner (491 pass); snapshot_schemas (no saved state changes, its configs moved to the new keys), monitor_ready, the iris-ci binary's tests and cargo test -p iris-gui (93 pass). New tests cover every fold row, the priority no_audio over file over host, the new-key-wins cases, the refusals through validate(), deprecated flags per profile, the clap conflicts, --machine after a fold, a deny_unknown_fields typo, a TOML round trip of every output variant (which proves toml 1.0.3 reads output = { file = "out.wav" }), HostLink's open and retry timing, RAD1 never asking the host for a stream with output = "none", and the MACE, ICE1724 and HAL2 status text.

Not verified

  • The per-card real-device tone tests (PulseAudio loopback on Linux, Windows, macOS). PulseAudio is not installed on this host and nothing here installs packages. The file-output tone tests for HAL2, MACE, RAD1 and the ICE1724 passed locally; the real-device ones run in CI.
  • The Onyx2's and Tezro's PROM hinv, and IRIX on the Octane, Onyx2, Tezro and Indigo2 (no disks): their guest-visible card presence rests on audio status and the PROM hinv for the Octane and Indigo2.
  • IRIX 6.5.22 does not list HAL2 in hinv at all here, so its default and silent comparison says nothing about audio.
  • --features developer unit run: once SIGKILLed at a 14 GiB cap while guest runs were going, and with 40 GiB two tone tests (a_tone_survives_a_pause_into_a_file on the ICE1724 and MACE, one each run) failed under load average about 100; each passed alone and a later full run passed (491). I did not characterise whether the cap kill predates this branch.
  • iris-gui was built and its tests run, but I did not click through the editor.
  • Windows and macOS builds.

Audio was set by four unrelated keys that meant different things per
machine. no_audio removed HAL2 from an Indy or Indigo2, which the guest
sees as missing hardware, but only silenced the O2, Octane and Onyx2
cards, which are part of the board. [tezro] audio added the Tezro's
ICE1724, and no_audio on top of it printed a "plays to nowhere" warning.
[audio] host and file picked the destination in two keys, prebuf_ms was
read by nothing, and [audio] was the only config table that ignored a
misspelt key.

Two keys now carry the two questions. [machine] audio says whether the
machine has its chip. Only the Indy and Indigo2 (HAL2, default present)
and the Tezro (ICE1724, default absent) take it, and every other machine
refuses it with an error naming itself. [audio] output says where a
present card plays, on every machine: default, alsa, pulseaudio, none or
a file. --audio and --audio-output set them.

No existing config changes meaning for a guest. Every old key and flag
still loads and prints one line naming its replacement: no_audio = true
becomes audio = false on the Indy and Indigo2 (HAL2 stays absent, as
before) and output = "none" elsewhere (the card stays, silent, as
before). A file's no_audio and [tezro] audio are folded again for the
machine --machine picks. A key written in the new spelling wins over an old one. prebuf_ms
is accepted and dropped. The Tezro's "plays to nowhere" warning goes,
since output = "none" with the card present is now an ordinary setting.

iris-ci, iris-bench, the bench runner, the CPU test scripts and the
in-repo configs all boot an Indy without HAL2 and now say so with
audio = false. The GUI's No audio checkbox becomes a Sound card checkbox
beside the profile (Indy and Indigo2 only) and an Output list.
RAD1 and the ICE1724 each carried a copy of the same host-stream code:
read the settings, check whether a retry is due, open, log, wrap the
stream in a resampler, record when a failed open happened, and make or
update the sink. Each also kept its own copy of the settings and of the
retry fields, so a change to the retry rule had to be made twice. The
card-private ring size was written three more times as rate * 2 / 5, the
enable_output body three times, and the machines repeated an
if-not-silenced wrapper around enable_output in four places.

HostLink holds the settings, the open stream with its resampler, and the
time of the last failed open, and opens once, on a retry that is due, or
when forced. RAD1 and the ICE1724 use it in place of their copies, and
Sink and PcmOut lose their retry fields. MACE uses it too and still tries
once, on the first sound, as before. With output = "none" the link never
opens a stream and never retries, so the machines call enable_output
unconditionally and the wrappers go. The ICE1724 no longer reads the
guest rate before taking the sink lock, because the resampler starts at
the stream's own rate and rebuilds when the card plays at another one,
which is what it already did on any rate change.

Every card now has one host ring size, 200 ms of the stream's rate. HAL2
had 320 ms, a prebuffer of 20 ms times a multiplier of 16 that came in
with the initial import and has no recorded reason. The size only
matters after a host stall of 200 ms or more.

A card with output = "none" now has its sink from the start, so
IRIS_AUDIO_DUMP records on RAD1 and the ICE1724 without a host stream, as
it already did on MACE and HAL2.

HAL2 still opens once at start and does not retry. The retry check on
HAL2 and MACE is a separate change.
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