-
feat(rig): one supervised surface tap, and an output that follows a preference list · 560e0e95
Two pieces of the cockpit (#155, #41), both written the same way: resolve by IDENTITY every time, never cache an address. tools/bridge/surface.py — THE control-surface tap ------------------------------------------------- Four tools each spawned their own aseqdump. This morning three of them were bound to ALSA client 24 (an Arturia KeyStep) instead of 20 (the Launch Control XL) because a replug had renumbered the clients, and each resolves its port once, at start. I "fixed" them by moving them to 20:0. Then PLN replugged again this evening and the numbers SWAPPED BACK: client 20: 'Arturia KeyStep 32' <- was the LCXL client 24: 'Launch Control XL' <- was the KeyStep So the two I fixed are wrong again and the one I called broken is now right. An address is a lease, not an identity. This module resolves "Launch Control XL" by name on EVERY respawn, so a replug costs one backoff interval instead of a silent session. First run against the live rig, with zero configuration: starting port=None ok port=24:0 It also keeps the last value of every CC, and — the part that matters — records `seen: false` distinctly from `value: 0`. An untouched control is what silences a Tidal stream (a bare "^NN" with no value yields NO EVENTS), so a cockpit that rendered untouched-as-zero would hide the exact failure it exists to show. And it does not litter. Three smoke runs of this file each orphaned an aseqdump to pid 1 — precisely the mess PLN complained about this morning ("i see 4 aseqdumps... you had one job"). try/finally is not enough, because SIGKILL, OOM and `timeout` all skip it. PR_SET_PDEATHSIG puts the guarantee in the kernel: the tap dies with its parent however the parent dies. tools/master-out.py — Ardour Master follows a preference list ------------------------------------------------------------- PLN: "can we control this tedious routing... ideally theres a tie breaking list of choice, from uhc to headphones to speakers". Ardour's master does not follow the PipeWire default sink, so every rig move means re-patching in qjackctl. Not "route to X" but "route to the best X actually present": UMC -> Headphones -> Speakers, matched as substrings of the node name so serial numbers and profile suffixes do not matter. Two properties make it safe to run mid-set: * IDEMPOTENT — computes wanted links, diffs against existing, applies only the delta. Already correct = zero pw-link calls = cannot glitch the audio. The naive unlink-then-relink is a guaranteed dropout even when nothing changed. * EXCLUSIVE BUT NARROW — removes Master links to other sinks (or plugging the UMC back in gives you doubled output), and touches nothing else. The limit is a predicate, `_is_ours`, not a good intention: apply() refuses to unlink any source that is not a Master output. Verified against KNOWN-BAD states, not just against agreement with reality — the whole lesson of this session. Ten checks: preference order at each of the three tiers, the "master stranded on headphones when the UMC appears" case (2 adds + 2 removes), idempotency (0 and 0), and the guard refusing both a foreign link in plan() and a foreign source handed straight to apply(). All pass. Live dry-run reports "already correct — 0 changes".PLN (Algolia) authored560e0e95
×