feat(pv-at): --track proves every control on a REAL track moves the sound (#74)
PLN's ask, verbatim: "all controls have impact on sound... even the crushes should
change noticeably". The existing suite proves the RIG works against a fixture;
this proves a TRACK is wired, which is the thing that bites at 160 BPM when a knob
turns out to do nothing.
python3 tools/at --track live/.../gimme_acid.tidal --dry-run # no audio
python3 tools/at --track live/.../gimme_acid.tidal [--json]
Per control: park at REST, take TWO baselines, probe, restore, judge. Four
outcomes, not two — MOVED / NO_IMPACT / INCONCLUSIVE / UNMEASURABLE — because a
swing that does not clearly beat the pattern's own drift is genuinely unknown, and
calling that a pass is how a green suite comes to mean nothing. NO_SIGNAL is kept
separate from NO_IMPACT: "nothing was playing" and "the control is dead" have
different fixes, and conflating them once cost a whole evening chasing SuperDirt.
THE BUG THE FIRST DRY RUN FOUND, which is the real content of this commit
The plan for gimme_acid came back "0 bipolar, no CC 49/50/51 at all" — on a track
that applies gF1/gF2/gF3 to SIX orbits. gF1/gF2/gF3 are defined in BootTidal.hs,
not in the track, so lens.py's scan of the .tidal file alone was structurally
blind to the three DJ FILTERS and to EVERY MUTE in the rig. The controls PLN
reaches for most were the ones the acceptance test could not see.
Fixed by PARSING BootTidal.hs (parsers-over-copy — it is the source of truth and
it moves: gF1 was rewired onto gDJF, midiGGlobal was retired hours ago, and a
hardcoded table would rot into a confidently wrong report). boot_helpers() now
resolves:
* `gF1 = gDJF "^49"` -> CC49, and BORROWS its lens from gDJF's body, since
"gDJF" is not a keyword any table knows and the lpf/hpf evidence sits on two
separate lines
* `gM1 = gMask . gMute1` -> composition, inheriting CC41 AND CC73
* a track's own `let gMute = ...` SHADOWS the boot one, as Tidal does
Result on gimme_acid: 18 controls -> 22, with 49/50/51 present, bipolar, resting
at 64 and probing downward. Across the OPAL set: 275 controls, 15-29 per track,
all three filters resolved in all 13, zero Ardour-owned CCs.
Two parse bugs found and fixed on the way, both by reading the output instead of
trusting it:
* `mask "f*16"` was classified as DENSITY. An all-false mask is a MUTE — same
keyword, opposite lens (rms vs onsets) — and my first all-false test asked
"does it contain a 1", which the *16 repeat count satisfies. So gMute1/2/3,
three textually IDENTICAL helpers, came back mute/density/density: rms would
have been the wrong lens on two of PLN's three mutes, and two working mutes
would have reported NO_IMPACT.
* `gM3 = gMask . gMute3` swallowed the Launchpad block that follows it and
reported CC 7 and CC 9 as mutes, because continuation lines were appended to
whatever was defined last. A continuation now has to LOOK like one.
SAFETY
CC93 is added to a new lens.NEVER_PROBE. It arms panic — gPanic gates on "^93", so
sending it high silences every gPanic'd stream. "Probe every control" plus "one
control is a kill switch" is how a self-test mutes a rehearsal and gets blamed on
the rig. It is reported and never swept. CC77-84 stay excluded by construction in
lens AND re-asserted at the probe; CC77 down is total silence, so belt and braces
is proportionate there.
Controls are restored per control, not at exit, so Ctrl-C mid-run is safe. Restore
goes to Control.rest — 64 for a DJ filter, because djf 0.05 is a ~26 Hz low-pass,
i.e. silence, and parking one at 0 to get a "baseline" measures silence and then
calls the track broken (#48).
--dry-run exists deliberately: it prints the plan with no audio and no MIDI, so
the half that can be checked tonight — which controls were found, which lens each
gets, what will be sent, what will be restored — is checkable at all. "Built but
never run" is exactly how the LED work shipped a colour ramp that was never
called.
TESTS: +79 (22 in test_lens.py, 57 in the new test_track_runner.py), suite
345 -> 424, all green. Every boot-helper regression above is pinned, including a
parametrised all-false-mask table with `f*16` in it; each lens is asserted to have
a floor (2 * max(drift, 0) is zero, so without one any tiny swing looks
significant on a quiet capture); the probe is asserted to REFUSE all of CC77-84;
and the dry-run plan is built for all 13 OPAL tracks with the DJ filters asserted
bipolar-and-resting-at-64 in each.
STATUS: the judgement half is validated. The AUDIO half is built but unrun — it
needs monitors, and the house is quiet. First thing to run with sound up.
Showing
This diff is collapsed.
Click to expand it.
tools/at/tests/test_track_runner.py
0 → 100644
Please
register
or
sign in
to comment