Commit b503502f by PLN (Algolia)

rig: absent is not broken, and a deep queue is just latency

Two gate/monitor fixes found while clearing gig-up blockers the night before a
gig.

ABSENT IS NOT BROKEN. check-audio-graph hard-FAILED the moment Ardour's Master
was on the internal codec, which made the whole pre-gig gate NO-GO while PLN was
rehearsing on the laptop jack with the UMC in a bag. PLN: "no umc atm playing on
the jack now. we need to be tolerant to various setups." He is right, and the
repo already knows the rule — section 3 of this same script downgrades to warn
when the interface is absent, and rig.py opens with "REPORT ABSENT AS ABSENT".
Section 2 just never got the memo.

Severity now depends on whether the UMC EXISTS. No UMC on the bus: warn, say
plainly that the last hop cannot be proven until the interface is plugged in,
and tell him to re-run at the venue. UMC present and Master still on the codec:
that is the real regression this check was written for — Ardour restores its own
saved ports and does NOT follow the PipeWire default sink, so it will happily
ignore an interface sitting right there. Result: OK (3 warnings) instead of a
NO-GO on a legitimate setup.

A DEEP QUEUE IS NOT A BUFFER, IT IS A DELAY. PLN: "when i move faders up/down
quickly, [it] lags by 500ms+, almost 1s, behind ahah".

Chased this to the wrong end twice, worth recording. First guess was fork+exec
per LED write — measured it: 2.8 ms p50, 8 ms p99, and --bench shows the
coalescer at p99 18 ms with ZERO overrun, so the LED path was never the problem.
Faders have no LEDs at all (row D), so a fader sweep does no LED writes; and the
Pulsar HUD has no MIDI tap, so it cannot lag on MIDI either. Second guess was
aseqdump block-buffering its stdout into a pipe (the exact bug this repo
documents for Python) — disproved with an isolated aseqdump client fed by
aseqsend: median 40 ms inter-arrival at a 25/s send rate, last event landing
BEFORE the last send. It flushes per event.

The real seam is the Bridge's SSE fan-out: `queue.Queue(maxsize=512)`. A fader
sweep is ~400 CC/s, so 512 deep is 1.3 s of backlog — and once it fills it STAYS
full, because drop-oldest holds the buffer AT capacity. Every event the browser
renders is then 512 events stale, permanently. The drop policy was already right;
the DEPTH was the latency. 512 was picked as "generous" and is really a latency
budget nobody priced.

Now 64 (~160 ms at that rate) — jitter absorption rather than a queue. Paired
with the browser-side coalescing from 72937cc7, the consumer drains far faster
than it fills, so it should rarely be reached.

Verified: 79/79 bridge tests, check-audio-graph OK on the jack.
parent 4e0d63ae
......@@ -82,8 +82,21 @@ class MidiStream:
return []
return parse_ports(r.stdout)
# A live monitor wants the PRESENT, not a complete history. 512 was chosen as
# "generous"; on a real surface it is a LATENCY BUDGET. PLN, 2026-08-21: "when i
# move faders up/down quickly, lags by 500ms+, almost 1s, behind ahah" — a fader
# sweep is ~400 CC/s, so a 512-deep queue is 1.3 s of backlog, and once it fills
# it STAYS full: drop-oldest keeps the buffer at capacity, so every event the
# browser renders is 512 events stale, forever. Deep buffers do not smooth a
# stream the consumer cannot keep up with; they just add a fixed delay to it.
#
# 64 bounds the backlog to ~160 ms at that rate, which is jitter absorption
# rather than a queue. The browser coalesces consecutive same-control events, so
# it now drains far faster than it fills and this should rarely be reached at all.
DEPTH = 64
def subscribe(self, port=None) -> queue.Queue:
q: queue.Queue = queue.Queue(maxsize=512)
q: queue.Queue = queue.Queue(maxsize=self.DEPTH)
with self._lock:
self._subs.add(q)
restart = self._proc is None or (port and port != self._port)
......
......@@ -93,10 +93,29 @@ echo "2. monitor path — ardour Master reaches the UMC"
MASTER=$(pw-link -l 2>/dev/null | awk '/^ardour:Master\/audio_out/ {f=1; next} /^[^[:space:]]/ {f=0} f')
UMC_CH=$(printf '%s\n' "$MASTER" | grep -c 'UMC202HD' || true)
SOF_CH=$(printf '%s\n' "$MASTER" | grep -c 'sof_sdw' || true)
# TOLERANT TO THE SETUP IN FRONT OF US (2026-08-21). This used to hard-FAIL the
# moment Master was on the internal codec, which made the whole gate NO-GO while
# PLN was rehearsing on the laptop jack with the UMC in a bag — a legitimate
# setup, reported as a broken rig. Absent is not broken; it is absent. Same law
# section 3 below already follows, and the same one rig.py opens with.
#
# So the severity now depends on whether the interface EXISTS. With no UMC on the
# bus this cannot prove the last hop either way, and says so. With the UMC present
# and Ardour still on the codec, that is the real regression this check was
# written for — Ardour restores its own saved ports and does NOT follow the
# PipeWire default sink, so it will happily ignore an interface that is right there.
UMC_PRESENT=no
[ -d /proc/asound/U192k ] && UMC_PRESENT=yes
grep -qiE 'UMC|U192k' /proc/asound/cards 2>/dev/null && UMC_PRESENT=yes
if [ "$UMC_CH" -ge 2 ]; then
ok "both Master channels on the UMC202HD"
elif [ "$UMC_PRESENT" = no ]; then
warn "no UMC on the bus — Master is on the internal codec. Fine for rehearsal
on the jack; at the venue plug the UMC in and RE-RUN, because this check
cannot prove the last hop until the interface exists"
elif [ "$SOF_CH" -gt 0 ]; then
bad "Master is on the INTERNAL codec (sof_sdw), not the UMC — the thing the UMC exists to avoid"
bad "UMC IS on the bus but Master is on the INTERNAL codec (sof_sdw) — the thing the UMC exists to avoid"
else
bad "Master reaches the UMC on $UMC_CH/2 channels"
fi
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment