Commit fdde0924 by PLN (Algolia)

feat(surface): the column migrator, proven on bombe_dj — 5 knobs onto their own columns

PLN: "lets do the bombe dj first to confirm". One track, end to end, so the tool
and the grid are both proven before the other twelve.

    d3  ^52 C4  ->  ^31 B3     legato
    d4  ^17 A5  ->  ^32 B4     midiOff (slow 4)
    d4  ^53 C5  ->  ^52 C4     crushbus 41
    d5  ^34 B6  ->  ^33 B5     octerbus 52
    d5  ^54 C6  ->  ^53 C5     crushbus 51

It also cleaned up a collision I had introduced myself twenty minutes earlier:
0538eb97 moved bombe_dj's d9 onto ^17, but d4 was ALREADY using ^17, so between
the two commits that one knob drove both orbits. Caught by running --plan before
--apply rather than trusting the previous step. The lesson is the tool's design
rule, not a footnote: build the whole permutation, then look at it, then write.

== THREE DESIGN DECISIONS THAT ARE THE WHOLE TOOL ==

1. THE PERMUTATION IS APPLIED SIMULTANEOUSLY. Costed one move at a time, the
   setlist showed 34 "destination already taken" clashes. Every one was phantom:
   the corpus is uniformly off by one column, so each orbit's knob wants the slot
   of the orbit below it, occupied only until THAT one also moves. Build the full
   map, rewrite in a single pass, and the clashes evaporate. Applying moves
   sequentially would have corrupted the files. Real collisions across the whole
   setlist: zero.

2. THE REWRITE IS SCOPED PER ORBIT, never file-wide. Two orbits can legitimately
   share a CC today — desire.tidal had ^19 driving both d7 and d9 — and they
   migrate to DIFFERENT destinations. A file-wide search-and-replace would send
   both to one place and silently fuse two gestures into one. So the substitution
   walks orbit by orbit with only that orbit's map.

3. COMMENTS ARE NEVER REWRITTEN. A commented-out ^NN is an ALTERNATIVE PLN may
   re-enable mid-set. Renumbering it would quietly rewire that alternative to a
   different orbit's knob, and the breakage would surface weeks later with no
   trace of a cause.

Hands off by construction: faders 77-84 and knobs 13-16 (Ardour-learned — the
CC77-to-silence footgun), the 8 BootTidal helper CCs, and all buttons
(41-44/57-60/73-76/89-92 — gates and mutes are phase 2, and retraining a gate is
more disruptive than retraining a knob).

== AND A FIX TO THE VERIFICATION LENS, WHICH MATTERED MORE THAN IT LOOKS ==
After applying, surface-columns still reported "11 moves" for bombe_dj. Nothing
was wrong: it counts BUTTON refs too, and buttons are deliberately out of phase-1
scope. But a gate that reports deliberately-deferred work as a failure is a gate
you learn to ignore — and that is precisely how a real regression gets through.
Added --knobs, which narrows the grid itself so every downstream number speaks
about phase 1 only. Same discipline as feedback_parsers_over_copy: the
measurement must measure what was actually done.

== VERIFIED ==
  pvlint                        1 track, 0 errors, 0 warnings
  silent-eval --seeded          every declared orbit still emits events, cold
  surface-columns --knobs       0 renumbers remaining (was 5)

Remaining for bombe_dj, on purpose: d9's second effect (^19) still wants gSel
(FIXME already in the file from 0538eb97), and the button rows are phase 2.
parent 0538eb97
......@@ -30,17 +30,17 @@ d3 $ gF1 $ gM1
# gain 1.4
# room 0.3
# dry (slow 16 $ range 0.2 1.8 perlin)
# legato (range 0.05 2 "^52")
# legato (range 0.05 2 "^31")
# sz 0.4
d4 $ gF2 $ gM3
$ midiOn "^89" (struct "t(8,16)")
$ juxBy 0.7 (|- note 12)
$ midiOff "^17" (slow 4)
$ midiOff "^32" (slow 4)
$ midiOn "^57" (arp "pinkyup")
$ midiOff "^57" (arp "up")
$ ply 2 $ slow 2 $ note ("<[e3, gs3, b3, c4] [e3, d4, c4, b3] [e3, f4, ef4, e4] [e3, f4, ef4, e4]>" + "[0,-12]")
# "bassWarsaw"
# crushbus 41 (range 16 3.5 "^53")
# crushbus 41 (range 16 3.5 "^52")
-- # cut 9
# room 0.5 # sz 0.5
# dry 1.5
......@@ -52,8 +52,8 @@ d5 $ gF3 $ gM3 -- GUITARE DU DJ
$ slow "<1 2 ~ <2 ~>>" $ n "9"
# "diams_dj"
# cut 5 # gain 1.5
# octerbus 52 (range 0 0.95 "^34")
# crushbus 51 (range 16 4.5 "^54")
# octerbus 52 (range 0 0.95 "^33")
# crushbus 51 (range 16 4.5 "^53")
-- # room 0.3 # sz 0.6 # dry 0.4
-- # octer 0.8 # octersub 1.2
# lpf 5000
......
......@@ -189,7 +189,19 @@ def main() -> int:
ap.add_argument("--all", action="store_true", help="every .tidal under live/")
ap.add_argument("--plan", action="store_true",
help="print the exact ^NN -> ^NN moves per track")
ap.add_argument("--knobs", action="store_true",
help="KNOB rows only (A/B/C) — the phase-1 scope. Buttons "
"(gates/mutes) are phase 2 and would otherwise inflate "
"the verdict with work that was deliberately deferred.")
a = ap.parse_args()
if a.knobs:
# Narrow the grid itself, so every downstream count -- cols, aligned,
# moves -- speaks about phase 1 only. A gate that reports undone phase-2
# work as a phase-1 failure is a gate you learn to ignore, which is how
# a real regression gets through.
for cc, (col, row) in list(GRID.items()):
if row not in ("A", "B", "C"):
del GRID[cc]
paths = (sorted(ROOT.glob("live/**/*.tidal")) if a.all else resolve(a.tracks))
helpers = helper_ccs()
......
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