Commit 60de8a2d by PLN (Algolia)

feat(lcxl): a standalone LED painter — the surface stops being dark, and stops being factory yellow

The problem, in PLN's words, asked three times: "why no button lights, still see
only A1 green? recover that asap i see no feedback anymore not even yellow
lifeline" — then "back from 'all yellow' to 'our coding, but touch-reactive and
persistent post touches'".

Three failures were stacked:

1. NOTHING PAINTED AT BOOT. The only LED painter lived in the Pulsar HUD
   (lib/lcxl-leds.js), so the board was lit only if Pulsar was open, had activated
   the package, and its frame pipeline worked — and #57 says its lastFrame dedupe
   latches an early empty frame, so even with Pulsar open one bad first frame means
   a permanently dark surface with no error anywhere.
2. FACTORY YELLOW WINNING — a real Midi-Through feedback loop echoing velocity 127
   back as an LED colour byte, not a device default.
3. NO PERSISTENCE. The LEDs are write-only, there is NO readback, so software must
   own the state and re-assert it. Nothing did.

Approach: tools/lcxl-leds.py, python3 stdlib only, standalone. It works with Pulsar
closed and is scriptable from gig-up. --map [TRACK] paints by ROLE derived from the
`^NN` bindings the file actually contains (dark = unbound on this track, which is
the biggest cognitive win); --map with no track paints the channel CONVENTION, so a
boot can never leave the board dark. --watch is the touch-reactive daemon: it keeps
a model of every control and repaints from that model, so persistence is the data
structure rather than a feature bolted on. --test walks every index so a dead LED
is visible. --dry-run prints hex and sends nothing.

It NEVER sends a CC. Not one, ever. SysEx out only; input is read-only via aseqdump
as a child process, which does not steal MIDI from SuperCollider. Sending CCs would
move live audio parameters, and CC 77-84 are MIDI-learned to Ardour track gains
(CC77 down = total silence).

Three things this cost, worth writing down:

* TRANSPORT. `amidi -p hw:2,0,0 -S ...` is what lit knob A1 by hand — but only
  because nothing else held the raw device. On a LIVE rig the ALSA sequencer layer
  owns the rawmidi substream and amidi dies with "cannot open port hw:2,0,0: Device
  or resource busy". The tool that works on a cold rig failed on the only rig that
  matters. Primary transport is now `aseqsend` to the LCXL's writable SEQUENCER
  port, which coexists with SuperCollider; amidi stays as fallback.
* PARSING. Splitting the .tidal on blank lines is right in principle (that IS
  Tidal's block separator) but stripping full-line `-- comments` first manufactures
  blank lines that cut a dN block in half: on vague_de_crime that put 9/9 bindings
  in the "fx" fallback because no segment head ever matched dN. Segment boundaries
  are now original blank lines PLUS every dN line, comments stripped only for the
  CC scan. Same track now reads rhythm=9, fx=6; gimme_acid 21 controls across
  rhythm/bass/fx; perfect.tidal 25 across rhythm/bass/lead/fx.
* THE SEAM. The first --watch regexes assumed bare space-separated numbers and
  would have matched NOTHING — a daemon that runs clean, logs nothing and paints
  nothing. Pulled aseqdump's real format strings out of the binary ("Control change
  %2d, controller %d, value %d") and verified the whole loop by putting a fake
  aseqdump on PATH: 9/9 synthetic events decoded, coloured and sent, plus a
  respawn-with-backoff when the child exited.

Also: the periodic re-assert runs on its own thread, not inside the read loop —
inside, it would only ever fire when an event arrived, i.e. never during the
silences when a device hiccup would actually go unnoticed.

Verified: hex reviewed byte-by-byte against the protocol, 40 index/value pairs in
one write, all bytes < 0x80; sends return rc=0 to the real device; --watch attaches
and survives; event decode unit-checked (CC13@64 -> green full, CC49@0 -> dark red
= LPF hard down, CC33@127 -> green flash, note 73 -> red flash). UNVERIFIED: what
the panel actually looks like — nobody has eyes on it. Absence of an error is not
evidence of a lit LED.
parent cfa56b69
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