midiviz: state as well as events — the DJFs know where they are resting
"as it maintains a state, it should be 'not all same default state' when you
look at it between two movements? like, the DJFs are at zero, in the Low, or in
the High, and this doesnt really grasp from viewing the midimon"
Correct, and it was not a theming problem. This module draws EVENTS, and makes
recency a visual axis on purpose: an event's identity is its position, its value
is a bar, its recency is brightness. So between two movements every cell decays
to the same floor tint, and a filter resting at zero is the same picture as one
parked at the top. No palette fixes that — an event stream carries changes, and
rest is the absence of a change.
A viewer cannot derive it either. The LCXL3 has no readback, and rows B/C run
relative (the surface sends v - 64), so the hardware holds no absolute position.
Exactly one process integrates those deltas and therefore owns the value, and it
is not the viewer: lcxl3-driver's `self.values` is not a cache of something else,
it is the only copy.
So: two channels, because two physics. Events stay edge-timed off the wire; state
is a level, published last-value-wins.
driver `~/.cache/parvagues/surface-state.json`, temp file + os.replace, on
the existing loop at 10 Hz, written ONLY when the snapshot changed.
Every failure swallowed: this is a courtesy to viewers, and a
read-only /home must never stop the faders reaching Ardour.
midiviz one stat() per frame; an unchanged `seq` means zero parsing and zero
repainting, which is the "pay once" property asked for. Absent,
unreadable or wrong-shaped file all mean "no state", and the window
draws exactly what it drew before this layer existed.
Deliberately not a socket or a broker: ~32 integers, idempotent, no history
worth queueing. A socket buys a connection, backpressure and a dead-consumer
failure mode in exchange for nothing. `cat` is the debugger.
First slice is the three family filters, drawn in the cells they physically
occupy (C1/C2/C3) rather than a strip of its own — the layout does not move, and
this module's founding idea is that identity IS position, so state belongs where
the events about it are, which is where his hands are.
ZERO IS THE CENTRE DETENT, 64, not 0: gDJF is an lpf and an hpf section both
wide open at ch=0.5, so centre is bypass and both ends filter. The event bar is
left-anchored, which is exactly the lie — a filter at rest draws a half-width
bar that reads as "half of something". The state strip sits above it,
centre-anchored, with the detent drawn: a bar growing left of centre is LOW, right
is HIGH, bare detent is ZERO. Static at rest, no decay, no pulse — being legible
when nothing has moved for a minute is the entire point.
Found by looking at it rather than by reasoning: the first version drew into the
bottom of the cell, where the event bar already lives, and C1-C3 rendered as
mud. Checked at 900x560 and 1600x1000, and 3 units tall beat 2 in sunlight.
16 tests, the load-bearing one being a real round trip: the real state_write and
the real SurfaceState.poll over one temp path, so a change to either side that
breaks the other fails here and not at a gig. That round trip caught a live bug —
`null` is valid JSON of the wrong shape, and the AttributeError from the first
.get escaped the handler and would have taken the window down.
Suites 526 passed, 2 failed (both pre-existing); --selftest PASS across three
themes and four shapes. Takes effect on the next driver restart; the running
driver was not touched.
Showing
This diff is collapsed.
Click to expand it.
tools/bridge/tests/test_surface_state.py
0 → 100644
Please
register
or
sign in
to comment