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
......
"""lens — which measurement can SEE a given control, and on which orbits.
Why this module exists
----------------------
PLN's ask was "test all controls have impact on sound... even the crushes should change
noticeably". The trap is that the obvious measurement answers most of those questions
WRONG. rms is completely blind to 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 is working perfectly. Equally, a spectral centroid is
blind to a mute, and an aggregate of any kind is blind to the difference between a fade
and a sparse part.
So a general per-track acceptance test cannot use one measurement. It has to pick the
lens from what the track's own code does with that CC, which is exactly what this module
does -- and it does it as a PURE function of the source text, so it is unit-testable
without a rig, without audio, and without waiting eight seconds per assertion.
What it deliberately does NOT do
--------------------------------
It does not predict the DIRECTION of most effects. "crush 4 raises the centroid" is true
until someone crushes something that was already noise. The honest, general invariant is
`this control measurably moves the orbit it is wired to, on a lens that can see its
kind`, judged against the pattern's own cycle-to-cycle drift. Direction is asserted only
where the semantics are unambiguous and were paid for in blood: `djf` is bipolar with
bypass at the CENTRE (0.05 is a ~26 Hz low-pass, i.e. silence -- the #48 footgun), and a
mute must actually silence.
Controls whose effect no available lens can see -- `pan`, on a capture that sums to mono
-- are reported UNMEASURABLE, not "no impact". A test suite that reports a false failure
on a working control teaches you to ignore it, which is worse than not testing it.
"""
from __future__ import annotations
import re
import sys
from dataclasses import dataclass, field
from pathlib import Path
TOOLS = Path(__file__).resolve().parent.parent
if str(TOOLS) not in sys.path:
sys.path.insert(0, str(TOOLS))
from pvlint.core import Track, strip_comment # noqa: E402
# CC 77-84 are MIDI-learned to Ardour's track gains and CC77 is the global gain --
# turning it down is total silence. They are excluded BY CONSTRUCTION here, not merely
# avoided by convention, so no future caller can opt back in by accident.
FORBIDDEN_CC = frozenset(range(77, 85))
CC_RE = re.compile(r'"\^(\d+)"')
LET_RE = re.compile(r'^\s*let\s+([a-zA-Z][a-zA-Z0-9_\']*)\s*=')
# effect keyword -> (kind, lens). Ordered: the FIRST match in a line wins, so the more
# specific names must precede the substrings they contain (octersub before octer, and
# every *bus before its bare parameter).
EFFECT_LENS: list[tuple[str, str, str]] = [
# -- spectrum: the only lens that sees a filter or a distortion --
("midiDJF", "filter", "centroid"),
("djfbus", "filter", "centroid"),
("djf", "filter", "centroid"),
("hpfbus", "filter", "centroid"),
("crushbus", "crush", "centroid"),
("crush", "crush", "centroid"),
("distortbus", "drive", "centroid"),
("squizbus", "squiz", "centroid"),
("squiz", "squiz", "centroid"),
("triodebus", "drive", "centroid"),
("ringbus", "ring", "centroid"),
("hpf", "filter", "centroid"),
("hcutoff", "filter", "centroid"),
("lpf", "filter", "centroid"),
("cutoff", "filter", "centroid"),
("resonance", "filter", "centroid"),
("lpq", "filter", "centroid"),
("coarse", "crush", "centroid"),
("modIndex", "fm", "centroid"),
("fmmod", "fm", "centroid"),
("accelerate", "pitch", "centroid"),
("speed", "pitch", "centroid"),
# -- the sub-octave family ADDS energy underneath; brightness is the wrong lens --
("octersubsubbus", "sub", "low_ratio"),
("octersubbus", "sub", "low_ratio"),
("octerbus", "octave", "high_ratio"),
# -- level. `# silence` / `const silence` is the corpus's real mute idiom --
("silence", "mute", "rms"),
("gMute", "mute", "rms"),
("gain", "level", "rms"),
("amp", "level", "rms"),
# -- density: a `ply` that fires changes the ONSET RATE and almost nothing else --
("striate", "density", "onsets"),
("chop", "density", "onsets"),
("ply", "density", "onsets"),
("gMask", "density", "onsets"),
("mask", "density", "onsets"),
("degradeBy", "density", "onsets"),
("segment", "density", "onsets"),
("stut", "density", "onsets"),
("euclid", "density", "onsets"),
("note", "pitch", "centroid"),
("up", "pitch", "centroid"),
# A gated `# n 95` swaps which SAMPLE plays -- a different sound entirely, so its
# brightness is what moves. (For a synth `n` is pitch; either way, centroid.)
("n ", "sample", "centroid"),
("slice", "density", "onsets"),
("juxBy", "density", "onsets"),
# -- time: a reverb or delay extends ACTIVITY, which only the time axis shows --
("roombus", "space", "duty"),
("lesliebus", "space", "duty"),
("lratebus", "space", "duty"),
("lsizebus", "space", "duty"),
("leslie", "space", "duty"),
("lrate", "space", "duty"),
("lsize", "space", "duty"),
("delaybus", "space", "duty"),
("delayfb", "space", "duty"),
("delayt", "space", "duty"),
("delay", "space", "duty"),
("room", "space", "duty"),
("legato", "space", "duty"),
("sustain", "space", "duty"),
("release", "space", "duty"),
("loopAt", "space", "duty"),
# -- rearrangement: same material, different event times --
("scramble", "density", "onsets"),
("shuffle", "density", "onsets"),
("hurry", "density", "onsets"),
("fast", "density", "onsets"),
("slow", "density", "onsets"),
("jux", "density", "onsets"),
("rot", "density", "onsets"),
# -- unmeasurable on a mono-summed capture. Named explicitly so it reports
# UNMEASURABLE rather than a false NO_IMPACT. --
("pan", "pan", ""),
("orbit", "routing", ""),
]
# Anything that merely GATES another function ("apply f while the button is down")
# tells us nothing about the lens -- the wrapped function does. So these are skipped
# when scanning for an effect keyword, and only used to decide the probe values.
GATES = ("midiOn", "midiOff", "midiG", "novaOn", "novaOff", "range",
"sometimesBy", "someCyclesBy", "whenmod", "every", "off", "superimpose")
DJ_FILTERS = frozenset({49, 50, 51})
@dataclass
class Control:
"""One CC as this track actually wires it."""
cc: int
orbits: list[int] = field(default_factory=list)
kind: str = "unknown"
lens: str = ""
evidence: str = ""
@property
def bipolar(self) -> bool:
"""True when the control's neutral is its CENTRE, not its bottom."""
return self.cc in DJ_FILTERS or self.kind == "filter" and self.cc in DJ_FILTERS
@property
def rest(self) -> int:
"""The value that means "not doing anything".
For a DJ filter that is 64, and getting this wrong is not a cosmetic error:
djf 0.05 is a ~26 Hz low-pass, so parking a filter at 0 to establish a
"baseline" would measure silence and call the track broken.
"""
return 64 if self.cc in DJ_FILTERS else 0
@property
def probes(self) -> list[int]:
"""Values to try, most-likely-to-show-an-effect first."""
if self.cc in DJ_FILTERS:
return [10, 118] # closing low-pass, then climbing high-pass
return [127, 96]
@property
def measurable(self) -> bool:
return bool(self.lens)
def __str__(self) -> str:
orb = ",".join(f"d{o}" for o in self.orbits) or "?"
return (f"CC{self.cc:<3d} {self.kind:<8s} -> {self.lens or 'UNMEASURABLE':<10s} "
f"on {orb}")
def _effect_in(text: str) -> tuple[str, str, str] | None:
"""First effect keyword in a line, as (keyword, kind, lens).
Scans by POSITION rather than by table order so that in
`midiOn "^59" (ply 4) # crushbus 1 "^60"` the keyword nearest the CC wins -- table
order alone would have handed every such line to whichever effect happened to sit
higher in the list.
"""
best = None
for kw, kind, lens in EFFECT_LENS:
for m in re.finditer(rf"\b{re.escape(kw)}\b", text):
if best is None or m.start() < best[0]:
best = (m.start(), kw, kind, lens)
return (best[1], best[2], best[3]) if best else None
# A gated mini-notation swap -- `midiOn "^41" (<| "k k k <k k*2>")` -- replaces the
# pattern rather than applying an effect. There is no effect keyword to find, but the
# thing it changes is WHEN events happen, so onsets is the lens. Requires a multi-token
# string so `# n "3"` isn't mistaken for a rhythm.
PATTERN_SWAP = re.compile(r'"[^"]*[a-z~\]][ ][a-z~\[<][^"]*"')
LOOKAHEAD = 2
def _scan(text: str) -> dict[int, tuple[str, str, str]]:
"""CC -> (kind, lens, evidence) for every CC referenced in `text`.
Scans a small WINDOW, not a single line. Tidal expressions wrap, and PLN wraps them
often -- `midiOn "^57" (` with the `# crushbus` on the following line is ordinary
style. A strictly line-by-line scan left a third of the corpus unclassified, and an
unclassified control is one the acceptance test silently skips: precisely the
controls most likely to be broken, quietly excluded from the test that would notice.
"""
lines = [strip_comment(ln) for ln in text.splitlines()]
out: dict[int, tuple[str, str, str]] = {}
for i, code in enumerate(lines):
ccs = [int(c) for c in CC_RE.findall(code)]
if not ccs:
continue
window = "\n".join(lines[i:i + 1 + LOOKAHEAD])
hit = _effect_in(code) or _effect_in(window)
if hit is None and PATTERN_SWAP.search(window):
hit = ("<|", "pattern", "onsets")
for cc in ccs:
if cc in FORBIDDEN_CC:
continue
if hit and (cc not in out or out[cc][0] == "unknown"):
out[cc] = (hit[1], hit[2], code.strip()[:90])
elif cc not in out:
out[cc] = ("unknown", "", code.strip()[:90])
return out
def controls(track: Track) -> list[Control]:
"""Every CC this track binds, with the lens that can see it and the orbits it hits.
Two passes, because ParVagues tracks bind CCs in two places: directly inside a `dN`
block, and inside a `let gF3 = ...` helper that several orbits then apply. Attributing
a helper's CC only to the line that defines it would say "CC51 affects no orbits",
which is exactly backwards -- in you_my_sunshine that one helper owned FOUR.
"""
helpers: dict[str, dict[int, tuple[str, str, str]]] = {}
for line in track.lines:
code = strip_comment(line)
m = LET_RE.match(code)
if m and CC_RE.search(code):
helpers[m.group(1)] = _scan(code)
found: dict[int, Control] = {}
def record(cc: int, kind: str, lens: str, ev: str, orbit: int | None) -> None:
c = found.get(cc)
if c is None:
c = found[cc] = Control(cc=cc, kind=kind, lens=lens, evidence=ev)
elif c.kind == "unknown" and kind != "unknown":
c.kind, c.lens, c.evidence = kind, lens, ev
if orbit is not None and orbit not in c.orbits:
c.orbits.append(orbit)
for orb in track.orbits():
code = orb.code()
for cc, (kind, lens, ev) in _scan(code).items():
record(cc, kind, lens, ev, orb.number)
for name, ccs in helpers.items():
if re.search(rf"\b{re.escape(name)}\b", code):
for cc, (kind, lens, ev) in ccs.items():
record(cc, kind, lens, ev, orb.number)
# A CC defined in a helper that no orbit applies is still a binding worth reporting
# -- it is dead wiring, and dead wiring on a control surface is a live trap.
for ccs in helpers.values():
for cc, (kind, lens, ev) in ccs.items():
record(cc, kind, lens, ev, None)
for c in found.values():
c.orbits.sort()
return sorted(found.values(), key=lambda c: c.cc)
def measurable(track: Track) -> list[Control]:
"""Only the controls an audio test can honestly judge, and that are safe to move."""
return [c for c in controls(track)
if c.measurable and c.orbits and c.cc not in FORBIDDEN_CC]
def lens_value(shape, lens: str) -> float:
"""Read one lens off a Shape. Keeps the AT from growing a getattr soup."""
return {
"centroid": lambda s: s.centroid_hz,
"rms": lambda s: s.rms_db,
"low_ratio": lambda s: s.low_ratio * 100.0,
"high_ratio": lambda s: s.high_ratio * 100.0,
"onsets": lambda s: s.onsets_per_s,
"duty": lambda s: s.duty * 100.0,
}[lens](shape)
LENS_UNITS = {"centroid": "Hz", "rms": "dB", "low_ratio": "%",
"high_ratio": "%", "onsets": "/s", "duty": "%"}
"""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