Commit 9647e446 by PLN (Algolia)

feat(boot): kill the global gain that lived on a fader — and free fader 1 for d1

PLN, 2026-07-29: "midiGlobal can be killed imo. so yea please do the refactor of
boottidal so we can free gain track1".

`midiGGlobal = orDef 0.769 "^77" * 1.3` read the LIVE fader, and that was wrong twice
over. First as SAFETY: it put a global gain on ONE physical fader, so brushing past
fader 1 in the dark attenuated every midiG-using stream at once — the whole set, quietly,
with nothing on screen to explain it. A control that can silence everything should not be
reachable by accident. Second as ERGONOMICS: CC77 being read by Tidal is what kept the
surface one lane off. Ardour has learned CC 78-84, CC77 is free, and with Tidal no longer
reading it the eight faders can finally line up fader N -> dN instead of fader N -> d(N-1)
(#46). Any track that reached for a fader was reaching one to the right of the orbit it
was thinking about.

Now a fixed pre-set: `midiGGlobal = 1.0`. Deliberately inaudible — the old untouched
default evaluated to 0.769 * 1.3 = 0.9997, so every existing track moves by 0.003 dB.
Global headroom is still adjustable, but it is one number in one place rather than
something a hip can knock.

NOT retired: the midiG family itself. PLN thought he no longer used it ("i dont use
midiGs anymore iirc since ardour faders"), and behaviourally he is right -- with the
global term gone, `midiG' ch l h` reduces to plain `gain (range l h ch)`. But the NAME is
called in 167 files (`midiG'`) plus 12 (`midiG`), and deleting a definition the corpus
references is a Haskell compile error, which takes the whole `let` block down and
silences the entire track. That is the exact failure mode of the last two debugging
evenings, and five days from OPAL is not when to re-open it. Retiring the usage is a
post-gig corpus migration, sibling to #64.

Verified with tools/check-boot.sh: the helper block typechecks against tidal-1.9.5 under
`ghc -fno-code`, the #55 seed block typechecks, and all 13 g* helpers (including
midiGdef, which is the one this edit could plausibly have broken) still yield events with
an UNTOUCHED controller. Not "no error appeared" -- the helpers were run against an empty
control map and their event counts checked.

Also lands tools/at/lens.py: infers WHICH MEASUREMENT can see a given control from the
track's own source, so a per-track acceptance test can assert "this knob measurably moves
the orbit it is wired to" without using a lens that is blind to the effect. rms cannot see
a filter or a bitcrusher -- they rearrange the spectrum and leave the level alone -- so an
rms-based "did anything change?" reports a confident NO on an effect that works perfectly.
Classifies 96.7% of the corpus's 5046 control bindings (33% unknown -> 3.3% once the
scanner looked at a 3-line window instead of one line, because Tidal expressions wrap and
an unclassified control is one the suite silently SKIPS -- precisely the controls most
likely to be broken). 41 unit tests, every positive case a real corpus line, with a
coverage guard so a future edit cannot quietly regress the scan.
parent 1c9df100
...@@ -318,10 +318,28 @@ let -- DPV specific parameters ...@@ -318,10 +318,28 @@ let -- DPV specific parameters
midiDJF ch lMin lMax hMin hMax = (_LPF lMin lMax ch) . (_HPF hMin hMax ch) midiDJF ch lMin lMax hMin hMax = (_LPF lMin lMax ch) . (_HPF hMin hMax ch)
-- FIXME: Seems to cut some lows when lMin != 0 -- FIXME: Seems to cut some lows when lMin != 0
-- Midi gain control (faders) -- Midi gain control (faders)
-- Untouched fader 77 defaults to 0.769 so the product is unity (1.0) — -- ------------------------------------------------------------------
-- NOT 0, which would silence every midiG-using stream on a fresh boot. -- midiGGlobal used to read the LIVE fader: orDef 0.769 "^77" * 1.3
-- (#53 wants this fixed pre-set rather than live-MIDI at all.) -- Retired 2026-07-29 (#53), on PLN's call ("midiGlobal can be killed imo"),
midiGGlobal = orDef 0.769 "^77" * 1.3 -- for two independent reasons:
--
-- 1. SAFETY. It put a global gain on ONE physical fader, so a single
-- accidental nudge of fader 1 attenuated every midiG-using stream at
-- once — the whole set, silently, with nothing on screen to say why.
-- A control that can silence everything should not be reachable by
-- brushing past it in the dark.
-- 2. IT COST US FADER 1. CC77 is also MIDI-learned in Ardour (or was free,
-- pending #46). With Tidal reading it too, moving fader 1 double-dipped:
-- Ardour's track gain AND Tidal's global gain, multiplied. Freeing ^77
-- is what lets the surface become fader N -> dN, so the eight faders
-- line up with d1-d8 instead of sitting one lane off (#46).
--
-- Now a FIXED pre-set. 1.0 = unity, exactly what the old untouched default
-- (0.769 * 1.3) evaluated to, so every existing track's level is unchanged
-- by this edit — the refactor is deliberately inaudible. Set it here if you
-- ever want global headroom; it is one number in one place, not a fader.
midiGGlobal :: Pattern Double
midiGGlobal = 1.0
_gainG ch = (gain (midiGGlobal * ch)) -- gain Global _gainG ch = (gain (midiGGlobal * ch)) -- gain Global
midiG' ch l h = _gainG (range l h ch) -- midiGain control midiG' ch l h = _gainG (range l h ch) -- midiGain control
midiGdef = midiG' 1 0 1 -- midiGain default midiGdef = midiG' 1 0 1 -- midiGain default
......
"""Unit tests for lens inference — which measurement can SEE which control.
Every positive case below is a REAL line from live/ (694 tracks, 5046 control bindings),
because a classifier tuned on invented examples classifies invented code. The cases that
matter most are the ones asserting a control is NOT measured by rms: rms is blind to a
filter and to a bitcrusher, so an rms-based "did anything change?" reports a confident NO
on an effect that is working perfectly. That single mistake would make the whole
per-track acceptance test worthless while looking green.
"""
from __future__ import annotations
import sys
from pathlib import Path
import pytest
TOOLS = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(TOOLS))
from at import lens # noqa: E402
from pvlint.core import Track # noqa: E402
def ctl(src: str, cc: int):
"""The Control for `cc` in a one-orbit source snippet."""
found = {c.cc: c for c in lens.controls(Track(path="<test>", text=src))}
return found.get(cc)
# --------------------------------------------------------- the right lens
@pytest.mark.parametrize("src,cc,lens_name", [
# spectrum — rms CANNOT see any of these
('d1 $ s "bd" # crushbus 44 (range 16 1.6 "^53")', 53, "centroid"),
('d1 $ s "bd" # djfbus 3 (range 0.05 0.95 "^51")', 51, "centroid"),
('d1 $ s "bd" # lpf (range 200 8000 "^13")', 13, "centroid"),
('d1 $ s "bd" # squizbus 7 (range 1 4 "^30")', 30, "centroid"),
# the sub-octave family ADDS weight underneath; brightness is the wrong question
('d1 $ s "bd" # octersubbus 21 (range 0 1.2 "^32")', 32, "low_ratio"),
('d1 $ s "bd" # octerbus 6 (range 0 1 "^31")', 31, "high_ratio"),
# level
('d1 $ gMute3 $ s "bd"\n $ midiOn "^75" (const silence)', 75, "rms"),
('d1 $ s "bd" # gain (range 0 1.2 "^29")', 29, "rms"),
# density — a ply that fires changes the onset rate and almost nothing else
('d1 $ midiOn "^59" (ply 4) $ s "bd*4"', 59, "onsets"),
('d1 $ midiOn "^89" (slice 4 "0 1 2 3") $ s "break"', 89, "onsets"),
('d1 $ s "bd" # chop (range 1 16 "^44")', 44, "onsets"),
# time axis — a reverb extends ACTIVITY, which an aggregate cannot show
('d1 $ s "bd" # roombus 9 (range 0 0.6 "^33")', 33, "duty"),
('d1 $ s "bd" # lratebus 43 (range 0.6 6.7 "^33")', 33, "duty"),
('d1 $ s "bd" # legato (range 0.2 2 "^34")', 34, "duty"),
])
def test_the_lens_matches_the_effect_kind(src, cc, lens_name):
c = ctl(src, cc)
assert c is not None, f"CC{cc} not detected at all"
assert c.lens == lens_name, f"CC{cc} classified {c.kind}/{c.lens}"
@pytest.mark.parametrize("src,cc", [
('d1 $ s "bd" # crushbus 44 (range 16 1.6 "^53")', 53),
('d1 $ s "bd" # djfbus 3 (range 0.05 0.95 "^51")', 51),
('d1 $ s "bd" # coarse (range 1 24 "^54")', 54),
])
def test_spectral_effects_are_NEVER_measured_by_level(src, cc):
"""The single most expensive possible mistake in this module.
A crusher or a filter rearranges the spectrum and leaves the level alone. Measure it
with rms and the suite reports "no impact" on a working effect — which is how a
green test suite comes to mean nothing.
"""
assert ctl(src, cc).lens != "rms"
# ------------------------------------------------------ multi-line reality
def test_a_wrapped_expression_is_still_classified():
"""PLN wraps his lines, and a line-by-line scan left a THIRD of the corpus
unclassified. An unclassified control is one the AT silently skips — so the
controls most likely to be broken would be the ones excluded from the test."""
src = ('d1 $ midiOn "^57" (\n'
' (# crushbus 41 (range 16 4 "^54"))\n'
' ) $ s "bd*4"\n')
assert ctl(src, 57).lens == "centroid"
def test_the_keyword_NEAREST_the_cc_wins_not_the_first_in_the_table():
"""`midiOn "^59" (ply 4) # crushbus ...` is about the ply; the crush has its own CC.
Resolving by table order rather than position handed such lines to whichever effect
happened to sit higher in the list."""
src = 'd1 $ midiOn "^59" (ply 4) $ s "bd" # crushbus 1 (range 16 4 "^60")'
assert ctl(src, 59).lens == "onsets"
def test_a_gated_pattern_swap_is_measured_on_onsets():
"""`midiOn "^41" (<| "k k k <k k*2>")` names no effect at all — it replaces the
pattern. What changes is WHEN events happen."""
src = 'd1 $ midiOn "^41" (<| "k k k <k [<~ k> k]>") $ s "bd"'
c = ctl(src, 41)
assert c.kind == "pattern" and c.lens == "onsets"
def test_a_bare_single_value_string_is_not_mistaken_for_a_rhythm():
src = 'd1 $ s "bd" # n (range 0 8 "^15")'
assert ctl(src, 15).kind != "pattern"
# ------------------------------------------------- helper -> orbit attribution
def test_a_let_helper_attributes_its_cc_to_every_orbit_that_applies_it():
"""you_my_sunshine's real shape: ONE gF3 helper owned FOUR orbits, which is why one
knob left d5/d7/d9/d11 all inaudible. Attributing the CC only to the `let` line
would have reported "CC51 affects no orbits" — exactly backwards."""
src = ('let gF3 = (# djfbus 3 (range 0.05 0.95 "^51"))\n'
'd5 $ gF3 $ s "voice"\n'
'd7 $ gF3 $ s "hh"\n'
'd9 $ gF3 $ s "pad"\n'
'd11 $ s "chop"\n')
c = ctl(src, 51)
assert c.orbits == [5, 7, 9]
assert 11 not in c.orbits
def test_a_helper_no_orbit_applies_is_still_reported_as_dead_wiring():
src = ('let gF9 = (# djfbus 9 (range 0.05 0.95 "^55"))\n'
'd1 $ s "bd"\n')
c = ctl(src, 55)
assert c is not None and c.orbits == []
assert c not in lens.measurable(Track(path="t", text=src))
# ------------------------------------------------------------------ safety
@pytest.mark.parametrize("cc", list(range(77, 85)))
def test_the_ardour_fader_bank_can_never_be_returned(cc):
"""CC 77-84 are MIDI-learned to Ardour's track gains and CC77 is the GLOBAL gain --
down is total silence. Excluded by construction, not by convention, so no future
caller can opt back in by accident."""
src = f'd1 $ s "bd" # gain "^{cc}"'
assert ctl(src, cc) is None
assert cc in lens.FORBIDDEN_CC
def test_no_measurable_control_is_ever_in_the_forbidden_bank():
src = 'd1 $ s "bd" # gain "^77" # crushbus 1 (range 16 4 "^54")'
ccs = {c.cc for c in lens.measurable(Track(path="t", text=src))}
assert ccs == {54}
# ----------------------------------------------- rest values and probes
@pytest.mark.parametrize("cc", [49, 50, 51])
def test_a_dj_filter_rests_at_CENTRE_not_at_zero(cc):
"""djf 0.5 is bypass; djf 0.05 is a ~26 Hz low-pass, i.e. SILENCE. Parking a filter
at 0 to establish a baseline would measure silence and call the track broken. This
exact confusion cost an evening (#48) and left four orbits dark on stage."""
c = ctl(f'd1 $ s "bd" # djfbus 1 (range 0.05 0.95 "^{cc}")', cc)
assert c.rest == 64
assert c.bipolar
def test_a_dj_filter_probes_BOTH_directions():
"""It is bipolar — one side closes a low-pass, the other opens a high-pass. Probing
one side only is how you get a confident wrong answer about a working filter."""
c = ctl('d1 $ s "bd" # djfbus 1 (range 0.05 0.95 "^49")', 49)
assert min(c.probes) < 64 < max(c.probes)
def test_an_ordinary_control_rests_at_zero():
c = ctl('d1 $ s "bd" # crushbus 1 (range 16 4 "^54")', 54)
assert c.rest == 0 and not c.bipolar
# ---------------------------------------------------------- unmeasurable
def test_pan_is_reported_UNMEASURABLE_not_broken():
"""The capture sums to mono, so no available lens can see pan. Reporting it as "no
impact" would be a false failure on a working control — and a suite that cries wolf
is a suite you learn to ignore."""
c = ctl('d1 $ s "bd" # pan (range 0 1 "^35")', 35)
assert c.kind == "pan" and not c.measurable
def test_lens_value_reads_every_declared_lens():
"""Guards against a lens name in the table with no reader behind it — which would
raise KeyError mid-run, on the rig, during a soundcheck."""
from at.harness import Shape
sh = Shape(orbit=1, peak_db=-6.0, rms_db=-20.0, centroid_hz=800.0,
low_ratio=0.5, high_ratio=0.2, onsets_per_s=4.0, duty=0.7)
for _kw, _kind, name in lens.EFFECT_LENS:
if name:
assert isinstance(lens.lens_value(sh, name), float)
assert name in lens.LENS_UNITS
# ------------------------------------------------------- corpus coverage
def test_the_classifier_covers_the_real_corpus():
"""A coverage GUARD, not a vanity metric: it fails if a future edit regresses the
multi-line scan that took unknown from 33% to 3.3%. Numbers measured 2026-07-29
over 694 tracks / 5046 bindings."""
import glob
from pvlint.core import load
total = unknown = 0
for f in sorted(glob.glob(str(TOOLS.parent / "live/**/*.tidal"), recursive=True)):
for c in lens.controls(load(f)):
total += 1
unknown += c.kind == "unknown"
assert total > 4000, f"corpus shrank unexpectedly ({total} bindings)"
assert unknown / total < 0.08, f"{unknown}/{total} unclassified — the scan regressed"
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