-
feat(lcxl): a standalone LED painter — the surface stops being dark, and stops being factory yellow · 60de8a2d
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.PLN (Algolia) authored60de8a2d
| Name |
Last commit
|
Last update |
|---|---|---|
| .. | ||
| bridge | Loading commit data... | |
| foundry | Loading commit data... | |
| pulsar-parvagues-hud | Loading commit data... | |
| README.md | Loading commit data... | |
| analyze_samples.py | Loading commit data... | |
| check-boot.sh | Loading commit data... | |
| check-mix.py | Loading commit data... | |
| control-lens.py | Loading commit data... | |
| fan_check.sh | Loading commit data... | |
| gen_tokens.py | Loading commit data... | |
| gen_ts_types.py | Loading commit data... | |
| gig_record.sh | Loading commit data... | |
| lcxl-init.py | Loading commit data... | |
| lcxl-leds.py | Loading commit data... | |
| mute.tidal | Loading commit data... | |
| probe-chain.py | Loading commit data... | |
| sample_tfidf.py | Loading commit data... | |
| setlist_samples.py | Loading commit data... | |
| shipowiz.py | Loading commit data... | |
| solo.tidal | Loading commit data... | |
| tidal-remote.py | Loading commit data... | |
| tools.png | Loading commit data... | |
| unmute.tidal | Loading commit data... | |
| unsolo.tidal | Loading commit data... |