-
feat(rig): measure the chain, manage the server, seed the surface — three tools… · 3f81e14d
feat(rig): measure the chain, manage the server, seed the surface — three tools that end the silent-orbit era Tonight cost an evening to "d4/d5/d9-d12 make no sound", and the reason it cost an evening is that every layer was inspected by READING and none by MEASURING. PLN's correction — "this is a great tidal-ears case, observe the audio streams" — was right, and the measurement took six seconds. ## tools/probe-chain.py — where does the signal stop? Taps the live chain at three points at once and reports peak+rms dBFS per orbit: SuperCollider:out_N/N+1 the orbit as SuperDirt renders it ardour:Tidal NN/out after Ardour's fader/mute/plugins ardour:Master/out the mix Taps are ADDITIVE PipeWire links into `pw-record --target 0` capture streams, so nothing existing is unlinked and the audio path is untouched — safe mid-set. The `--target 0` is the trick that makes it possible to tap a mid-chain node instead of only a device. Orbit->port mapping is DERIVED, not hardcoded: `~dirt.start(57120, [0,2,4,...])` means orbit k writes bus 2k, and PipeWire exposes bus b as out_(b+1)/out_(b+2). First run settled in one shot what four sessions of hypothesising could not: d1 -28.6 rms d2 -42.5 d3 -37.4 d7 -21.5 d8 -29.8 <- audible d4 d5 d6 d9 d10 d11 d12 = -inf, absolute digital silence Exact match with what PLN could hear. **Ardour fully exonerated** — faders, mutes, routing, all of it. The signal never existed. Every hypothesis that had pointed downstream was wrong, and one reading killed them all. It reports peak AND rms deliberately: a lone peak can be one stray sample or a click, and judging an orbit "working" on peak alone is how you mistake a transient for audio. ## parvagues-sc.service — SuperCollider becomes visible The reason the real error was unreadable all evening: sclang's post window was a bare /dev/pts/3. Not readable by tooling, gone when the terminal closes, gone on a crash. The process was equally fragile — owned by a terminal, and killable by a stray probe (which is exactly what happened: a probe script of mine attached to the DEFAULT server, i.e. PLN's live one, and its `0.exit` sent `/quit`. `s` in sclang is the default server; changing the SuperDirt OSC port isolates nothing). As a systemd --user unit we get restart-by-command, survival across terminal close and sandbox teardown, one place for The Bridge to show status — and the post window in the JOURNAL, where it can be PARSED into readiness state instead of squinted at. Within a minute of it landing: like_sugar (22) the_revolution (36) diams_dj (16) — the "missing" banks were registered correctly all along, plus 106 real `File reading failed` warnings on `zz._`-prefixed files that had never once been visible. That is the #45 lead, handed over by a log we could finally read. `Restart=no` on purpose: a failing boot script under Restart=always would spam the MIDI graph and hide the failure. Audio gear should fail loudly, never flap. `QT_QPA_PLATFORM=offscreen` is load-bearing, not cosmetic — a Qt-linked sclang dies with the display stack, which is how an overnight suspend used to kill the music (d8346672). ## tools/lcxl-init.py — fix the mute-bomb at its actual root Verified against tidal-1.9.5 source rather than guessed: `"^51"` is `cF_ "51"` is `_cX_ getF "51"`, and `_cX_` returns **`silence`** on a missing key. `#` cannot emit without a right-hand value, so any stream touching an unmoved control is silent — and `dN` being `xfade N` means you hear the PREVIOUS pattern's 4-cycle tail dying, which reads as a filter closing. That illusion is what cost the evening. BootTidal.hs's two mitigations are both weaker than this one. `orDef` reaches only the ~13 g* helpers — never the ~1860 inline `^NN` refs across 343 track files, nor arithmetic sites like `midiOn ("^34" - "^18")` where touched-minus-untouched is itself empty. The `setF` seed block is correct in principle (same `sStateMV` the OSC `/ctrl` handler writes — traced in Stream.hs) but depends on boot order and on that ghci having actually run it. lcxl-init instead emits real CC messages into the identical path a physical knob takes: CC -> SC's `MIDIFunc.cc` -> `/ctrl` -> Tidal's map. By construction indistinguishable from PLN sweeping the whole surface by hand — the one thing already known to work. It cannot diverge from reality because it IS the reality path, and it fixes helpers, inline refs and arithmetic sites at once, whatever state ghci is in. PLN's framing: "most things that are templated lack an init value" — so give them one, from the hardware side. Encodes the corpus convention `range <neutral> <extreme>`, so 0 is neutral for everything except the three DJ filters, whose neutral is the centre detent (64/127 -> lpf 20000 / hpf 20 = true bypass). D-row faders (77-84) are deliberately NOT seeded: they are MIDI-learned to Ardour TRACK GAINS, so blasting values would move Ardour's faders while the physical faders stay put — a surface/mix desync worse than the problem. `^77` is already covered by `orDef 0.769`. Two bugs found and fixed by verifying rather than assuming: * port resolution used `aconnect -i` (READABLE ports) and so returned SC's `out0` — a source. Sending to it goes nowhere. It must be `-o`, the writable destinations. Resolved BY NAME on every call, never cached: the LCXL has already moved 20:0 -> 24:0 across a replug, and a binding resolved once at boot then silently invalidated is this rig's most expensive bug class. * the first verification attempt tapped the destination port with aseqdump and saw 0 messages — but you cannot SUBSCRIBE to a destination. Delivery was then proven properly through Midi Through (which echoes in to out): controller 49 value 64 and controller 50 value 64 observed arriving. The send was always fine; the observation was broken. Report which one you proved.PLN (Algolia) authored3f81e14d
×