The bug: midiviz resolved its ALSA source port ONCE at startup and shelled out to `aseqdump -p <port>`. Unplugging the LCXL removes the ALSA client it was subscribed to, `aseqdump`'s stdout hits EOF, the reader thread's for-loop just returns, and nothing else was keeping `app.exec()` alive — the window closed with exit 0. Because that is a CLEAN exit, `Restart=on-failure` never fired, so PLN's "always open" glyph-rain lens was gone for the rest of the session the moment he unplugged the board. Reproduced from tonight's own journal: `midiviz.service` ran 6m57s and exited status=0/SUCCESS the instant the surface came off USB — textbook rig failure mode #1, "a binding resolved once, killed by a replug, never re-resolved", except this time the binding was the whole window's reason to exist rather than just a port variable. The fix, mirroring the resolve/rebind pattern already proven in `tools/lcxl-leds.py` (`find_seq_port` + `invalidate_ports`, cached and dropped on failure): - `Reader` now tracks `.alive()` (child process poll) and marks itself `error = "closed"` when its `aseqdump` child exits on its own (vs. an intentional `.close()`), so the difference between "I quit" and "I died" is visible to the widget. - `MidiViz` gained a second QTimer (`RECONNECT_MS = 2000`, matching `midi-autoconnect.sh`'s own reconcile cadence) that re-resolves the source every tick and swaps the `Reader` in place if it moved, died, or vanished. Losing the source is never fatal any more: the window drops to its existing idle/dim-pulse paint state (already built for "no events recently") and keeps ticking, painting, and listening for the surface's return — no `sys.exit`, no fatal path added anywhere. - Liveness is gated by `_hardware_present()`, a `type=kernel` + name-match scan of `aconnect -l` reused from `midi-autoconnect.sh`'s `DIRECT_LEG_AWK` (`hw = ($0 ~ /type=kernel/ && $0 ~ /Launch Control XL|LCXL/)`). This matters because `lcxl3-driver.service` publishes a VIRTUAL port literally named 'ParVagues LCXL3' — the translated, corpus-numbered stream `resolve_watch_port()` deliberately prefers, since that is the CC numbering the grid and every `.tidal` file actually speak. That virtual client can outlive a physical unplug for a beat if the driver hasn't noticed yet, so a bare name match would report "still connected" against a ghost carrying nothing. The rebind tick distrusts a match ONLY when the matched label is itself LCXL-named and no real hardware backs it; a "Midi Through" catch-all match needs no hardware and is trusted as before. Content still comes from the preferred (possibly virtual) port — only the "is it actually there" judgement moved to hardware. - A user-pinned `-p` port keeps working, checked instead against `aseqdump -l`'s live listing (a pin surviving a client renumbering across replug is not guaranteed, same as any other resolved-by-address binding). - `tools/midiviz.service`: `Restart=on-failure` → `Restart=always` + `StartLimitIntervalSec=0` (moved to `[Unit]`, where it belongs — the first install attempt logged "Unknown key 'StartLimitIntervalSec' in section [Service], ignoring", caught before it shipped) as belt-and-braces under the in-process fix, since the unit holds no audio ports and a restart loop costs nothing real. Verified: - `--selftest`: `parsed=68 ingested=250 frames=159 paints=170 grabs=83 distinct_sampled_colours=165 platform=offscreen -> PASS`. - Reinstalled the unit (`install -m644` + `daemon-reload`); no more "Unknown key" warning in the journal on the next start. - Live restart: `ActiveState=active`, `MainPID=2728202`, `NRestarts=0`, bound to the "Midi Through" fallback (no LCXL physically plugged in tonight, confirming the hardware-gated fallback still works with zero surface present). - Port-loss simulation (couldn't unplug hardware; killed the reader's `aseqdump` child directly — the same failure shape as the source disappearing under it): child pid 2728206 -> `<defunct>`; within the next 2s rebind tick a fresh `aseqdump -p 14:0` (pid 2730846) appeared as midiviz's child. Main PID stayed 2728202 throughout, `NRestarts` stayed 0, `ActiveState` stayed `active` — recovered entirely IN-PROCESS, no systemd restart needed. CPU time kept accumulating (1.237s over 38s wall) proving the paint timer never stopped ticking.
| 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... | |
| tests | Loading commit data... | |
| README.md | Loading commit data... | |
| analyze_samples.py | Loading commit data... | |
| ardour-tidal-live.desktop | Loading commit data... | |
| audio-lens.py | Loading commit data... | |
| bank-check.py | Loading commit data... | |
| button-column-audit.py | Loading commit data... | |
| button_roles.py | Loading commit data... | |
| cheat-sheet.py | Loading commit data... | |
| check-audio-graph.sh | Loading commit data... | |
| check-boot-blocks.py | Loading commit data... | |
| check-boot.sh | Loading commit data... | |
| check-drift.sh | Loading commit data... | |
| check-mix.py | Loading commit data... | |
| check-preload.sh | Loading commit data... | |
| check-tracks.sh | Loading commit data... | |
| control-lens.py | Loading commit data... | |
| deshadow-helpers.py | Loading commit data... | |
| fader-baseline.py | Loading commit data... | |
| fan_check.sh | Loading commit data... | |
| fix-button-roles.py | Loading commit data... | |
| fix-mute-roles.py | Loading commit data... | |
| gen_tokens.py | Loading commit data... | |
| gen_ts_types.py | Loading commit data... | |
| gig-log.py | Loading commit data... | |
| gig-log.service | Loading commit data... | |
| gig-metadata.py | Loading commit data... | |
| gig-preflight.py | Loading commit data... | |
| gig-up.sh | Loading commit data... | |
| gig_record.sh | Loading commit data... | |
| install-protect.sh | Loading commit data... | |
| lcxl-init.py | Loading commit data... | |
| lcxl-leds-watch.service | Loading commit data... | |
| lcxl-leds.py | Loading commit data... | |
| lcxl-path.py | Loading commit data... | |
| lcxl3-driver.py | Loading commit data... | |
| lcxl3-driver.service | Loading commit data... | |
| lcxl3-probe-display.py | Loading commit data... | |
| lcxl3.py | Loading commit data... | |
| lcxl3_language.py | Loading commit data... | |
| lcxl_grid.json | Loading commit data... | |
| lcxl_grid.py | Loading commit data... | |
| master-out.py | Loading commit data... | |
| midi-autoconnect.service | Loading commit data... | |
| midi-autoconnect.sh | Loading commit data... | |
| midiviz.desktop | Loading commit data... | |
| midiviz.service | Loading commit data... | |
| migrate-columns.py | Loading commit data... | |
| mock-lcxl.py | Loading commit data... | |
| mute.tidal | Loading commit data... | |
| orphan-orbits.py | Loading commit data... | |
| parvagues-gear | Loading commit data... | |
| parvagues-protect.service | Loading commit data... | |
| parvagues-protect.sh | Loading commit data... | |
| parvagues-rig.target | Loading commit data... | |
| parvagues-samples.service | Loading commit data... | |
| parvagues-sc-watchdog.service | Loading commit data... | |
| probe-chain.py | Loading commit data... | |
| pvbanks.py | Loading commit data... | |
| rig-pulse.py | Loading commit data... | |
| rig_units.py | Loading commit data... | |
| sample-watcher.py | Loading commit data... | |
| sample_tfidf.py | Loading commit data... | |
| sc-watchdog.sh | Loading commit data... | |
| set-coherence.py | Loading commit data... | |
| setlist.py | Loading commit data... | |
| setlist_samples.py | Loading commit data... | |
| shipowiz.py | Loading commit data... | |
| silent-eval.py | Loading commit data... | |
| solo.tidal | Loading commit data... | |
| surface-columns.py | Loading commit data... | |
| sweep_state.py | Loading commit data... | |
| take-lens.py | Loading commit data... | |
| take-master.py | Loading commit data... | |
| take-segments.py | Loading commit data... | |
| tempo-lens.py | Loading commit data... | |
| tidal-ardour-autoroute.service | Loading commit data... | |
| tidal-ardour-autoroute.sh | Loading commit data... | |
| tidal-remote.py | Loading commit data... | |
| tools.png | Loading commit data... | |
| unmute.tidal | Loading commit data... | |
| unsolo.tidal | Loading commit data... |