Commit 2cd4108a by PLN (Algolia)

feat(pvlint): PV009 — gain MULTIPLIED by a control that seeds to zero, i.e. a…

feat(pvlint): PV009 — gain MULTIPLIED by a control that seeds to zero, i.e. a layer that never arrives

PLN, mid-run, working it out himself: "d9 on wap didnt take effects on 17 and 19?
ok no its the 17 superimpose that melts too !! does our xfade screw the
superimposes? 💡"

The xfade is innocent. wap.tidal:91 is

    $ superimpose ( (# "acidOto3091") . (|- note 12) . (|* gain "^17") . (# cut 91) )

and ^17 seeds to 0 under BootTidal's policy ("A/B knobs -> 0, effect amount off").
Gain MULTIPLIED by 0 is silence, so the superimposed layer is not quiet — it does
not exist. The 4-cycle dN crossfade then eases from the previously-audible state
into that silence, which is what reads as a melt and is exactly why the xfade
looked guilty. It was the messenger.

The distinction the rule encodes: `#` ASSIGNS and survives a 0 seed
(`# crushbus 51 (range 16 4.5 "^53")` at seed 0 is simply crush 0), while `|*`
and `|/` ANNIHILATE. Only the multiplicative form can delete a pattern, which is
why flagging `#` sites would bury the real finding under hundreds of false ones.

SEVERITY IS `info`, DELIBERATELY. This idiom is usually correct — it is a bring-in
fader for a layer, and PLN uses it that way. The rule does not exist to call it
wrong; it exists so nobody hunts it BY EAR. Making it a warning would fire on a
normal, intended idiom and get the whole gate ignored, the mistake already made
once in check-drift's first draft. It shows under `pvlint --info`.

WHY IT MATTERS MOST RIGHT NOW: a remap moves the CC, but it does not move the
KNOB. Physical positions survive across a surface change, so a knob left at 0
silently acquires a new victim. That is the single root cause behind everything
PLN heard this session — d9's layer gone on wap, and the same class of staleness
behind the d4/d5/d8 symptoms (latched buttons whose orbit changed under them). It
is the identical failure to the very first question he asked today: Ardour's fader
raised by mouse while the physical fader sat at 0. There are no motors, so the
surface never follows the map.

SCOPE, measured (both forms — `|* p (range 0 hi "^NN")` and the bare
`|* p "^NN"`, which my first scan missed and which is the worse of the two):
    33 sites across 25 files corpus-wide
     2 in the OPAL setlist:  wap.tidal:91 (bare), desire.tidal:75 (range 0 1.5)
Notable elsewhere: scratchomatic multiplies gain by "^84" — an ARDOUR-learned
fader — and computer_riddim/computer_riddub/anniv multiply by "^50", the gF2 DJ
filter knob.

6 new tests. The negatives carry the weight, as always: `range 1 1.5` (the
prescribed neutral-low-end fix) must NOT flag, `#` assignment must not flag, and
C1-C3 must not flag because they seed to 0.5 (centre), not 0. 496 pass.
parent 2778f1cd
......@@ -482,6 +482,66 @@ def pv008_button_drives_two_orbits(track: Track) -> Iterable[Finding]:
)
# --------------------------------------------------------------------------
# PV009 — gain MULTIPLIED by a control that seeds to zero: silent until touched
# --------------------------------------------------------------------------
MUL_RANGED = re.compile(r'\|([*/])\s*(\w+)\s*\(\s*range\s+([\d.]+)\s+[\d.]+\s+"\^(\d+)"')
MUL_BARE = re.compile(r'\|([*/])\s*(\w+)\s+"\^(\d+)"')
# BootTidal's seed policy: A/B knobs and C4-C8 -> 0 ("effect amount off"),
# C1-C3 -> 0.5 (DJF centre), faders -> 1, buttons -> 0. So a control that seeds
# to 0 is any knob outside C1-C3, and any button.
SEEDS_TO_ZERO = ((set(range(13, 21)) | set(range(29, 37)) | set(range(52, 57)))
| set(range(41, 45)) | set(range(57, 61))
| set(range(73, 77)) | set(range(89, 93)))
@rule
def pv009_gain_multiplied_by_zero_seed(track: Track) -> Iterable[Finding]:
"""`|* gain "^17"` — the layer is SILENT until that knob is raised.
Found 2026-07-29 by PLN's ear, after the remap: "its the 17 superimpose that
melts too !! does our xfade screw the superimposes?" It does not. `^17` seeds
to 0 (BootTidal's "effect amount off"), and gain times zero is silence, so the
superimposed layer never arrives. The 4-cycle `dN` crossfade then eases from
the previously-audible state into that silence, which is what reads as a melt
and is why the xfade looked guilty.
This is USUALLY DELIBERATE — a bring-in fader for a layer — which is exactly
why it is `info` and not a warning. The problem it solves is knowing WHICH
knob is holding a layer at zero, without discovering it mid-set. It matters
most right after a remap: CCs move, physical knobs do not, so a control left
at 0 silently acquires a new victim.
`#` params are unaffected (`# crush (range 16 3.5 "^53")` at seed 0 just sets
a value); only multiplicative application can annihilate a pattern.
"""
for i, code in iter_code_lines(track):
for m, kind, cc_i, lo_i in ((MUL_RANGED, "ranged from 0", 4, 3),
(MUL_BARE, "no range at all", 3, None)):
for hit in m.finditer(code):
cc = int(hit.group(cc_i))
if cc not in SEEDS_TO_ZERO:
continue
if lo_i is not None and float(hit.group(lo_i)) != 0.0:
continue
op, param = hit.group(1), hit.group(2)
yield Finding(
rule="PV009",
severity="info",
line=i,
message=f'|{op} {param} "^{cc}" is silent at the seeded value '
f'({kind})',
detail=f"^{cc} seeds to 0, and {param} multiplied by 0 removes "
f"the layer entirely — it will not be heard until that "
f"control is raised. Usually intended as a bring-in "
f"fader; listed so no one hunts it by ear. A remap moves "
f"the CC but not the knob, so check this one after any "
f"surface change.",
)
def check(track: Track, enabled: set[str] | None = None) -> list[Finding]:
out: list[Finding] = []
for fn in RULES:
......
......@@ -268,3 +268,45 @@ def test_pv008_ignores_commented_out_gestures():
' -- $ midiOn "^42" (<| "k k")\n\n'
'd2 $ midiOn "^42" (mask "t f")\n')
assert lint(src, "PV008") == []
# ------------------------------------------------------------------- PV009
def test_pv009_flags_bare_multiplied_gain():
"""wap.tidal:91 — `|* gain "^17"` with ^17 seeded to 0 = layer never arrives."""
src = 'd9 $ superimpose ((|* gain "^17") . (# cut 91))\n $ n "0"\n'
f = lint(src, "PV009")
assert len(f) == 1
assert f[0].severity == "info" # usually deliberate; must not cry wolf
assert '"^17"' in f[0].message and "no range at all" in f[0].message
def test_pv009_flags_range_starting_at_zero():
"""desire.tidal:75 — `|* gain (range 0 1.5 "^17")`."""
src = 'd9 $ superimpose ((|* gain (range 0 1.5 "^17")) . (# n 5))\n'
f = lint(src, "PV009")
assert len(f) == 1 and "ranged from 0" in f[0].message
def test_pv009_ignores_a_neutral_low_end():
"""`range 1 1.5` is unity at seed — the prescribed fix, must not be flagged."""
assert lint('d9 $ (|* gain (range 1 1.5 "^17")) $ n "0"\n', "PV009") == []
def test_pv009_ignores_hash_assignment():
"""`#` sets a value; only MULTIPLICATION can annihilate the pattern.
`# crush (range 16 3.5 "^53")` at seed 0 is simply crush 0, not silence.
Flagging these would bury the real finding under hundreds of false ones.
"""
assert lint('d5 $ n "0" # crushbus 51 (range 16 4.5 "^53")\n', "PV009") == []
assert lint('d5 $ n "0" # gain (range 0 1.5 "^33")\n', "PV009") == []
def test_pv009_ignores_the_djf_knobs_which_seed_to_centre():
"""C1-C3 seed to 0.5, not 0, so multiplying by them is not annihilation."""
assert lint('d1 $ (|* gain "^49") $ n "0"\n', "PV009") == []
def test_pv009_ignores_commented_lines():
assert lint('d9 $ n "0"\n -- $ (|* gain "^17")\n', "PV009") == []
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