Commit d6e509c3 by PLN (Algolia)

docs(tasks): archive #85/#88/#89/#92 — the surface remap, and four bugs that hid in plain sight

Rich entries for the afternoon that turned the LCXL from a lookup into a channel
strip. Written for a cold reader, because these are the blog/video source material.

The four findings worth reading again months from now:

* A daemon with THREE possible lifecycles, two of which could run at once — two
  processes writing SysEx to one board, fighting over every LED, neither wrong
  enough to look broken.
* `StartLimitIntervalSec=0` in [Service] instead of [Unit]: systemd logs "Unknown
  key ... ignoring" and the default retry limit stays in force. A silent failure one
  section heading away from working.
* 34 "destination already taken" clashes that were entirely PHANTOM — an artefact of
  costing a permutation one move at a time when the corpus is uniformly off by one.
* A measurement that overruled my own design: I proposed eight per-column DJ
  filters; measuring which orbits actually receive gF1/gF2/gF3 showed they are
  per-FAMILY (drums / bass / leads) and consistent across all 13 tracks. Building
  the "better" design would have destroyed a working abstraction.

Also: the physical LCXL faders sat at 0 while the Ardour faders had been raised by
mouse — silent divergence, and the next brush of a fader would have killed the orbit
mid-set. Found in a log, not by ear.
parent a5c61c0b
...@@ -933,3 +933,107 @@ The "factory yellow" was never the device's default: it was a real Midi-Through ...@@ -933,3 +933,107 @@ The "factory yellow" was never the device's default: it was a real Midi-Through
feedback loop echoing note velocity 127 back as an LED colour byte feedback loop echoing note velocity 127 back as an LED colour byte
(`reference_lcxl_midi_feedback_loop`). It regenerates on a Tidal reboot, so the (`reference_lcxl_midi_feedback_loop`). It regenerates on a Tidal reboot, so the
symptom returning is not a regression in the LED code. symptom returning is not a regression in the LED code.
---
## #85 — LED watcher had three lifecycles and two could run at once
**Description.** The board's colours only persist because a daemon holds a model of
every control and repaints from it. That daemon had no home.
**Done.** `tools/lcxl-leds-watch.service`, symlinked into `~/.config/systemd/user/`
the way `parvagues-bridge.service` already is, enabled, `WantedBy=default.target`
so it starts at boot with linger. `gig-up.sh` no longer spawns anything — it
`systemctl --user restart`s the unit. Commit `901b43a`.
**Learnings.**
- Three ways to start it existed: by hand (dies with the terminal), `gig-up.sh`'s
own `setsid` copy, and a *transient* `systemd-run` unit. The second and third
could run **simultaneously**, two processes writing SysEx to one LCXL, fighting
over every LED — and neither wrong enough to look broken, which is the worst kind
of bug this rig produces. `restart` (not `start`) guarantees one owner even after
a crash.
- **`StartLimitIntervalSec=0` belongs in `[Unit]`, not `[Service]`.** In `[Service]`
systemd logs "Unknown key … ignoring" — a warning nobody reads — and the default
5-restarts-in-10s limit stays in force. With a hot-pluggable LCXL usually absent
at boot, the unit would burn its retries and sit in `failed`, board dark for the
session. A silent failure one section heading away from working.
- Cost, from the transient unit's own accounting: **2.140 s CPU over 1 h 53 m =
0.03% of a core**, 14.5 M RSS. The fork-per-LED is a throughput problem, not a
load problem — so never a suspect when hunting xruns.
- `pgrep -cf 'lcxl-leds.py --watch'` reported **2** with one process running: it
counts the shell running the pgrep pipeline. Read the process list, not the count.
**Deps.** Unblocked the LED work that followed (#78 tail, #94).
---
## #88 / #89 — the Ardour re-learn: faders 1-8 = d1-d8, knobs A1-A4 = d9-d12
**Description.** Make the surface legible: fader N levels orbit N.
**Done.** PLN re-learned all 12 bindings by hand; verified from the session file —
`CC 77..84` → Tidal 01..08, `CC 13..16` → Tidal 09..12, **nothing stray**, and
`CC 20`/`CC 31` released. `check-mix` then read **OK for the first time**: all 12
orbits reach master, Tidal 01-08 within +0.0..+1.1 dB. The two track-side conflicts
(#88) were fixed in `0538eb9`.
**Learnings.**
- **The first "saved" wasn't saved.** The session file's mtime hadn't moved and the
bindings were byte-identical to before. Verifying by *reading the artifact* rather
than trusting the report caught it in seconds; the fix was Session → Save from the
menu rather than ctrl+S with focus elsewhere.
- **The physical LCXL faders were all at 0** while PLN had raised the Ardour faders
with the mouse. The LCXL has no motors, so the two diverge silently and the *next
brush of a fader* snaps the orbit to the hardware position — i.e. silence. A whole
class of mystery silence, found in `gig-log`'s surface view, not by ear.
- **CC 77 was bound to nothing**, despite the standing belief that it was a global
gain whose fall killed everything. Beliefs about bindings must be re-read from the
session, never remembered.
- `^14` was simultaneously Ardour's Tidal-10 gain and `piment_bresilien`'s d10
crushbus — one knob, two unrelated jobs, the "rig is haunted" signature.
**Deps.** #46 → #92 → #94.
---
## #92 — column-align the OPAL set (phase 1: knobs)
**Description.** PLN spotted the half of #46 I had not measured: *"we need the
coverage of the effects moving in the tracks, e.g. bass from 81 to 80 means effects
on 53 now move to 52"*.
**Done.** `tools/surface-columns.py` (price it) + `tools/migrate-columns.py` (perform
it). **59 knob substitutions across 13 tracks, 5 gSel FIXMEs, 0 collisions.** Commits
`d8fb13b`, `d8fd702`, `0538eb9`, `fdde092`, `346c652`.
**Learnings.**
- My earlier "two track edits" costing answered the **wrong question** — it looked for
CC *collisions*, but the property that makes a surface readable is **column
coherence**. 19/84 orbits were aligned; the corpus was **uniformly off by one**
(d1→col2 … d6→col7, d7/d8 already right), because columns 1-3's C-knob and buttons
were already the family filters and mutes, so per-orbit controls had been pushed
right to dodge them.
- **Because it's uniform, there is no fader mapping that makes today's tracks
coherent.** Either the tracks move or the surface stays a lookup. PLN's instinct
that the re-learn was only half the job was exactly right.
- **34 "destination taken" clashes were phantom** — an artefact of costing moves one
at a time. Each orbit wants the slot of the orbit below it, free *once that one
also moves*. Build the whole permutation, then write once. Sequential application
would have corrupted the files; `desire` needs two genuine **swaps**.
- **Scope the rewrite per orbit, never file-wide.** `desire`'s `^19` drove both d7
and d9 and they migrate apart; a global replace would have fused two gestures.
- **Never rewrite comments.** A commented `^NN` is an alternative PLN re-enables
mid-set; renumbering it breaks weeks later with no trace.
- **A measurement overruled my design.** I proposed `gF1..gF8`, one filter per
column. Measuring which orbits receive each showed gF1→d1/d2/d3/d8, gF2→d4,
gF3→d5/d7 — **per-family, corpus-wide, deliberate**. Per-orbit filters would have
destroyed a working abstraction. C1/2/3 stay put, and C4-C8 stay free, which also
removed the "one effect per orbit" constraint I'd wrongly imposed.
- **My own tool had a parser bug that closed doors**: the helper-CC scan counted CCs
appearing only in `--` comments, inflating the "unavailable" set by three. A wrong
number that *closes* options is worse than one that opens them, because nobody goes
looking. Fixed by stripping comments, as pvlint and silent-eval already do.
- **The verification lens had to be narrowed to match the work done**: after applying,
`surface-columns` still reported 11 moves for `bombe_dj` — all buttons, deliberately
out of scope. A gate that reports deferred work as failure is a gate you learn to
ignore. Hence `--knobs`.
**Deps.** #46; blocks #94 (buttons, 166 refs, now unblocked by gMask's retirement).
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