PLN, watching the board for the first time on real hardware: "its slow to track if
i move all fders quickly i see the animations lagging a sec or two behind".
Not a slow device. Three compounding faults, each of which alone would have been
survivable:
1. THE BUG. The read loop repainted whenever a CC *value* changed. But colour is a
STEP function of value -- six steps in value_ramp, seven in filter_colour. Sweeping
one knob 0 -> 127 emits ~128 events and can change the board at most 6 times. We
were asking the wire for ~20x the work that could possibly be visible.
2. Every send re-resolved the port from scratch: `aconnect -o` AND `amidi -l` AND then
`aseqsend`. Three forks per LED, ~1.7 ms each, measured.
3. All of it ran INSIDE `for line in proc.stdout`, so a write in flight stopped us
reading the next MIDI event. aseqdump's pipe QUEUES rather than drops, so nothing
was lost -- everything just arrived later, and later, without bound. That
unboundedness is why it read as "a second or two" rather than a constant delay.
The fix, in payoff order. A new `Painter` thread owns the wire; the reader only touches
the model and hands it colours, never blocking. The painter dedupes on the COLOUR the
board will show, coalesces so only the last colour per index within a frame reaches the
wire, and batches every dirty index into ONE SysEx (the Launchpad dialect takes
(index, colour) pairs, so a whole-surface repaint is a single write). Port resolution
is cached for 2 s and thrown away the instant a write fails -- evidence, not a timer --
which keeps the replug-recovery property that made it uncached in the first place.
Rate limiting is a floor on the GAP between writes, not a fixed tick, so an isolated
button press still goes out immediately.
Measured, not claimed. `--bench` replays PLN's own complaint (8 knobs + 8 faders swept
together, 500 events/s) against a transport modelled at its real cost, and runs the
legacy path beside the new one:
legacy 744 wire msgs wall 11.23s overrun +8.23s
coalesced 48 wire msgs wall 3.00s overrun +0.00s
15.5x fewer messages; latency p50 7.9 ms, p99 20.2 ms
Overrun IS the visible lag -- it is how far behind his hands the board finishes. On the
real device the per-write cost also fell 7.70 ms -> 2.72 ms with ports cached, so the
total wire work is down roughly 44x.
Twelve regression tests, no hardware needed. Speed regresses silently -- nothing goes
red, it just gets slow again -- so the assertions are numeric: wire rate bounded by the
frame rate and not the input rate, reader never falls behind, p99 under 50 ms, a lone
press not delayed by the frame boundary. The last one is the one that matters: fast and
wrong beats nothing, so we decode every SysEx the coalescer emitted through the mock
surface and assert the board that LANDS is exactly what a full `build_frame`
recomputation would have produced.
Also: verbose logging now fires only when the board actually changes. Printing 400
lines a second of "CC77 = 63" was itself I/O in the hot loop, and told nobody anything.
| Name |
Last commit
|
Last update |
|---|---|---|
| .. | ||
| at | Loading commit data... | |
| bridge | Loading commit data... | |
| foundry | Loading commit data... | |
| pulsar-parvagues-hud | Loading commit data... | |
| pvlint | 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... | |
| check-tracks.sh | 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... | |
| mock-lcxl.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... | |
| tempo-lens.py | Loading commit data... | |
| tidal-remote.py | Loading commit data... | |
| tools.png | Loading commit data... | |
| unmute.tidal | Loading commit data... | |
| unsolo.tidal | Loading commit data... |