THE J-5 DROPOUT. Sound went glitchy-then-silent mid-rehearsal, five days before
OPAL, and every obvious suspect was healthy: sclang up, scsynth up and using
6.7% CPU, the UMC sink present and RUNNING, every link in place. Diagnosing the
audio engine would have found nothing wrong with it, because nothing was.
THE BEHRINGER IS THE CANARY, NOT THE FAULT. The internal speaker played fine the
whole time and only the UMC202HD died, which looks exactly like a broken
interface. It is not: period_size 128 on the UMC against 1024 on the SOF speaker
is an 8x tighter deadline, so the UMC is the only device in the box with a
real-time deadline tight enough to miss. USB devnum stayed at 14 across samples
(not the 2026-07-26 re-enumeration bug), mixer 127/127, links intact. Rule for
next time: when only the UMC is silent, suspect the SCHEDULER.
WHAT pw-top FOUND. Xruns per node:
sof_sdw.HiFi__Headset__source 1,421,369 nothing used it
sof_sdw.HiFi__HDMI1__sink 704,081 2.8ms BUSY/cycle, no client
UMC202HD Line sink 32,338 the victim
alsa_input.hw_U192k_0 14,338 W/Q 1.24 = waits > a full quantum
SuperCollider 1,119
ardour 422
Two nodes NOBODY USES produced 2.1 million of them. HDMI1 appeared when the
screen was mirrored for the WYSIWYG show.
THE ROOT CAUSE WAS A FIX. 52-no-suspend.conf matched ~alsa_output.* and
~alsa_input.* — every node in the graph. It was written for a real reason (a
woken device eats the first note's attack) but applied graph-wide it held six SOF
sinks and a broken mic source permanently open INSIDE the real-time graph.
Never-suspend is precisely what it asked for. The generalisable lesson, and it is
the one worth keeping: a rule scoped to one device but applied to the whole graph
stops being a fix and becomes a load. Suspension is not the enemy — suspension of
the device you are playing through is. Now narrowed to the UMC plus the internal
Speaker, the latter kept deliberately because it is the fallback that proved the
machine could still make sound during the incident.
AND THE LAPTOP MICROPHONE WAS IN THE d1 RECORDING. pw-link showed
Headset__source:capture_FL -> ardour:Tidal 01/audio_in 1. Ardour auto-connects
physical inputs, so the internal mic was summed into the input of the track that
records d1, alongside SuperCollider:out_1 — invisible in Ardour's GUI and in the
saved session file. 53-rig-graph.conf disables the source outright rather than
trusting nobody to re-arm that track. Take95 was recorded with it connected;
auditing that is #132.
RESULT, measured. Load 7.17 -> 4.80. UMC hw_ptr went from restarting once a
second — never past ~3000 frames, deltas going NEGATIVE — to a steady 48,145
frames/sec across six consecutive samples with zero restarts.
VERSIONED, NOT COPIED. The three wireplumber files now live in
config/wireplumber/ and are SYMLINKED from ~/.config. One source of truth: a copy
would have become the fourth thing in this project to drift from its generated
twin. check-audio-graph.sh also fails on a dangling symlink, because a broken one
is silent until the next wireplumber restart — the worst possible moment to find
out.
NEW GATE: tools/check-audio-graph.sh, wired into gig-up as a HARD check. It
asserts what was actually wrong, in the order it bites: no non-SuperCollider
source on any Tidal input (recording integrity first — a dropout you hear and
redo, a stem with room noise in it you ship), Master really reaching the UMC,
the interface actually clocking, and the prune still in effect. check-mix reads
the SAVED session; this reads the LIVE graph, and the saved file has been wrong
before (#62).
TWO METHODOLOGY RULES BAKED INTO IT, both learned that day:
- NEVER SCAN THE JOURNAL. `journalctl --since` on a long-uptime box reads the
whole journal and burned a full core twice WHILE PLN WAS LOSING AUDIO — the
diagnostic making the fault worse. Everything is /proc, pw-link, bounded
pw-top.
- `state: RUNNING` PROVES NOTHING. It read RUNNING while the device was dying.
Only the hardware pointer, sampled repeatedly, is honest — and take four
samples, not two: my first two-sample read gave a confidently wrong "still
stalling" verdict because a ring-buffer wrap looks identical to a stall.
Mutation-checked: the recording-integrity awk was run against the real 2026-08-03
pw-link dump WITH the mic present (it fires) and against a clean one (silent, no
false positive). A check that has never seen the bug it was written for is a
guess.
| Name |
Last commit
|
Last update |
|---|---|---|
| .. | ||
| at | Loading commit data... | |
| bridge | Loading commit data... | |
| foundry | Loading commit data... | |
| pulsar-parvagues-hud | Loading commit data... | |
| pvlint | Loading commit data... | |
| tests | Loading commit data... | |
| README.md | Loading commit data... | |
| analyze_samples.py | Loading commit data... | |
| button_roles.py | Loading commit data... | |
| cheat-sheet.py | Loading commit data... | |
| check-audio-graph.sh | Loading commit data... | |
| check-boot-blocks.py | Loading commit data... | |
| check-boot.sh | Loading commit data... | |
| check-drift.sh | Loading commit data... | |
| check-mix.py | Loading commit data... | |
| check-preload.sh | Loading commit data... | |
| check-tracks.sh | Loading commit data... | |
| control-lens.py | Loading commit data... | |
| deshadow-helpers.py | Loading commit data... | |
| fader-baseline.py | Loading commit data... | |
| fan_check.sh | Loading commit data... | |
| fix-button-roles.py | Loading commit data... | |
| fix-mute-roles.py | Loading commit data... | |
| gen_tokens.py | Loading commit data... | |
| gen_ts_types.py | Loading commit data... | |
| gig-log.py | Loading commit data... | |
| gig-up.sh | Loading commit data... | |
| gig_record.sh | Loading commit data... | |
| lcxl-init.py | Loading commit data... | |
| lcxl-leds-watch.service | Loading commit data... | |
| lcxl-leds.py | Loading commit data... | |
| lcxl_grid.json | Loading commit data... | |
| lcxl_grid.py | Loading commit data... | |
| migrate-columns.py | Loading commit data... | |
| mock-lcxl.py | Loading commit data... | |
| mute.tidal | Loading commit data... | |
| orphan-orbits.py | Loading commit data... | |
| parvagues-sc-watchdog.service | Loading commit data... | |
| probe-chain.py | Loading commit data... | |
| sample_tfidf.py | Loading commit data... | |
| sc-watchdog.sh | Loading commit data... | |
| set-coherence.py | Loading commit data... | |
| setlist.py | Loading commit data... | |
| setlist_samples.py | Loading commit data... | |
| shipowiz.py | Loading commit data... | |
| silent-eval.py | Loading commit data... | |
| solo.tidal | Loading commit data... | |
| surface-columns.py | Loading commit data... | |
| take-lens.py | Loading commit data... | |
| take-master.py | Loading commit data... | |
| take-segments.py | Loading commit data... | |
| tempo-lens.py | Loading commit data... | |
| tidal-remote.py | Loading commit data... | |
| tools.png | Loading commit data... | |
| unmute.tidal | Loading commit data... | |
| unsolo.tidal | Loading commit data... |