-
feat(gig-log): log WHICH TRACK is loaded — the key that decodes the whole MIDI stream · d5baca32
PLN recorded an afternoon of OPAL takes, then asked for "a master split per track loaded time". The answer should have been trivial. It was not: nothing in the rig recorded WHEN each track was loaded. The gig log had 78222 samples of thermals, xruns, gear and 7429 coalesced MIDI CC events for today alone — and no way to say what any of it was. A CC 41 is meaningless on its own, because the LCXL map is PER TRACK: on one track index 41 is a crush bus, on another it is a break gate. The performance data was all there and none of it was resolvable. Meanwhile the exact ground truth was sitting in a 33-byte file the whole time. Pulsar has published `~/.cache/parvagues/current-track` since #84 (2576e77) for the LED watcher to follow. Nobody was reading it into the log. WHY THIS MATTERS MORE THAN THE SPLIT IT WAS ASKED FOR Splitting a livecoded set by ear is genuinely hard, and we have the scars: orbit roles stay stable across tracks so level/presence detection fails, and every dN is a 4-cycle crossfade that bleeds the incoming track into the outgoing one's tail — the existing Bandcamp splits carry that bleed. A `k:"track"` line replaces all of that inference with a timestamp. Boundaries stop being clever and become read. And it retro-actively upgrades the CC stream from telemetry to an annotated performance score: track + CC + time resolves, through the per-track LCXL map we already generate, to "d4's DJ filter opened at 18:42:13". That is the input for telling practice apart from performance (hands-off vs hands-on density), for finding the moment worth clipping, and eventually for the emotion/timeline bundle. IMPLEMENTATION — deliberately the boring version - `_read_track()` never raises: absent file (publisher not up yet) or a short read both degrade to None. A missing track must degrade the log, never stop it. - Written ONLY on change, so a 3-hour session costs a handful of lines instead of 10800 repeats of one string. The header carries the initial value, so a session that never switches still has one authoritative statement of what was loaded. - Polled, not watched — for the same reason the LED watcher polls. One stat+read of 33 bytes per second is unmeasurable; an inotify watch adds a dependency and a whole class of "the watch silently died", which is the exact failure mode this data exists to prevent. - `load()` folds track records into `marks` so they render in the existing timeline for free, keeping `k:"track"` so a splitter can tell an automatic boundary from a human annotation. VALIDATED - `selftest` PASS; sampler + both readers still 0.66% of one core over 6s, well under the 3% observer budget (the observer must not perturb — we already learned that lesson when probe-chain's own capture streams caused the xruns). - END-TO-END, not just unit: ran the real recorder against a temp track file, moved it twice, and read the log back. Header carried the initial track; both changes appeared exactly once with their `from`, each within the 1s poll. Green tests on a pure function would not have proven the value reaches the file.
PLN (Algolia) authoredd5baca32
×