Commit 9f8eab2e by PLN (Algolia)

midiviz: a pip for 'this is the board, not the driver'

The window had no way to say it was reading the untranslated stream. With
lcxl3-driver down there is no ParVagues LCXL3 port, the preference falls through
to the board's own DAW port, and v3 numbering puts row C's knobs on row B's
cells and the faders on no cell at all -- a coherent, confident, wrong picture,
with the port label in the header as the only tell.

Scoped to LCXL3: an original LCXL has no translator in front of it, so its raw
port IS the right port and a pip there would be a false alarm on a desk that
works. Set on the rebind tick, which already holds the resolved label, so it
costs no extra subprocess. Drawn beside the paused and stream-broken pips, no
words, like everything else in that header.
parent bc0aee3b
...@@ -1123,6 +1123,7 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0, ...@@ -1123,6 +1123,7 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
self.cc: dict[int, list] = {} # cc -> [value, t_last, channel] self.cc: dict[int, list] = {} # cc -> [value, t_last, channel]
self.col_t = [0.0] * 9 # column 1..8 last-touch, for the glow self.col_t = [0.0] * 9 # column 1..8 last-touch, for the glow
self.stream: deque = deque(maxlen=256) # bottom ribbon, newest right self.stream: deque = deque(maxlen=256) # bottom ribbon, newest right
self.raw_port = False # reading the board, untranslated
self.drops: list[list] = [] # gutter rain self.drops: list[list] = [] # gutter rain
self._drop_t: dict[int, float] = {} # cc -> last token rained self._drop_t: dict[int, float] = {} # cc -> last token rained
self.hist: deque = deque([0] * 64, maxlen=64) # events per 100 ms self.hist: deque = deque([0] * 64, maxlen=64) # events per 100 ms
...@@ -1232,6 +1233,20 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0, ...@@ -1232,6 +1233,20 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
self.reader.close() self.reader.close()
self.reader = Reader(pid).start() self.reader = Reader(pid).start()
self.port_label = label self.port_label = label
# AM I READING THE TRANSLATED STREAM?
#
# The question nobody thought to ask on 2026-09-22, and the answer
# was no. With `lcxl3-driver` down there is no 'ParVagues LCXL3'
# port, so the preference falls through to the board's own
# 'LCXL3 1 DAW' — which speaks v3 numbering, where row C's knobs
# carry row B's v2 CCs and the faders carry CCs in no cell at all.
# The window then draws a coherent, confident, WRONG picture, and
# four separate "bugs" get reported for one missing translation.
#
# Scoped to LCXL3 deliberately: an ORIGINAL LCXL has no driver in
# front of it, so its raw port IS the right port, and a pip there
# would be a false alarm on a board that is working.
self.raw_port = ("LCXL3" in label) and ("ParVagues" not in label)
# ── palette / fonts / geometry ───────────────────────────────────── # ── palette / fonts / geometry ─────────────────────────────────────
def _build_palette(self): def _build_palette(self):
...@@ -1828,6 +1843,13 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0, ...@@ -1828,6 +1843,13 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
if self.reader is not None and self.reader.error: if self.reader is not None and self.reader.error:
p.fillRect(self.gut_x + pw * 2, top + 1, pw, hh - 1, p.fillRect(self.gut_x + pw * 2, top + 1, pw, hh - 1,
self.lut[FAM_FAMILY][13]) self.lut[FAM_FAMILY][13])
# The third pip: an LCXL3 read RAW. Every cell on screen is then in
# the wrong place, and until this existed the only thing saying so
# was the port label two inches to the left. No words, like the
# other two — but it points at the label, which is the fix.
if self.raw_port:
p.fillRect(self.gut_x + pw * 4, top + 1, pw, hh - 1,
self.lut[FAM_FX2][14])
def _paint_matrix(self, p, now, pulse): def _paint_matrix(self, p, now, pulse):
mx, my, cw, ch = self.mx, self.my, self.cw, self.ch mx, my, cw, ch = self.mx, self.my, self.cw, self.ch
......
...@@ -466,3 +466,44 @@ def test_a_token_is_never_drawn_past_the_gutter(tmp_path): ...@@ -466,3 +466,44 @@ def test_a_token_is_never_drawn_past_the_gutter(tmp_path):
x = round(w.gut_x + 2 + sub * span / 2.0) x = round(w.gut_x + 2 + sub * span / 2.0)
assert x + w.uw * len(widest) <= w.gut_x + w.gut_w + 1, ( assert x + w.uw * len(widest) <= w.gut_x + w.gut_w + 1, (
"lane %d clips %r off the right edge" % (sub, widest)) "lane %d clips %r off the right edge" % (sub, widest))
# ── the pip that says "you are reading the board, not the driver" ──────────
@pytest.mark.parametrize("label,raw", [
("128:0 · ParVagues LCXL3", False), # the translated stream: correct
("24:1 · LCXL3 1 DAW", True), # the board itself: every cell misplaced
("24:0 · LCXL3 1 MIDI", True), # standalone mode, also untranslated
("20:0 · Launch Control XL", False), # ORIGINAL board: no driver, raw IS right
("14:0 · Midi Through", False), # not a surface at all
("no MIDI source found", False),
])
def test_only_an_untranslated_lcxl3_lights_the_pip(label, raw):
"""The rule, not the drawing: a v2 board has no translator in front of it,
so flagging its raw port would be a false alarm on a working desk. Only an
LCXL3 read raw means the grid is being drawn from v3 numbering."""
assert (("LCXL3" in label) and ("ParVagues" not in label)) is raw
def test_the_pip_is_drawn_from_the_flag_the_rebind_tick_sets(tmp_path):
"""Asserted on PIXELS, and on the seam: `_rebind_tick` owns the decision
(it already has the resolved label, and `_hardware_present()` costs a
subprocess), `_paint_header` only draws it. A flag nothing sets, or a pip
nothing draws, would both pass a test written against only one of them."""
mv, w = _offscreen_widget(MV, tmp_path / "absent.json")
if mv is None:
pytest.skip("no Qt available")
w._scanlines_blit = lambda _p: None
src = open(MV.__file__).read()
assert "self.raw_port = (\"LCXL3\" in label)" in src, (
"_rebind_tick no longer sets raw_port; the pip would be dead code")
w.raw_port = False
w.repaint()
pw = max(2, round(2 * w.dens))
x, y = w.gut_x + pw * 4 + pw // 2, w.hdr_y + 2
off = w.grab().toImage().pixelColor(x, y).name()
w.raw_port = True
w.repaint()
on = w.grab().toImage().pixelColor(x, y).name()
assert off != on, "the untranslated-port pip drew nothing"
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