Commit 16429b0e by PLN (Algolia)

feat(control-lens): the noise floor comes from REPEATED baselines, not one lucky repeat

The closing A-again capture gives a single estimate of how much the signal moves on
its own, and on the first real run that estimate was wrong in the direction that
matters. Two captures of d2 at the SAME control setting read 1.6 and 2.4 onsets/s —
33% apart, which happened to equal gMask's entire measured swing — yet the closing
capture matched the opening one exactly, so the tool reported drift 0.0% and called
the control EFFECTIVE. One repeat can agree with the first reading by luck.

Now the baseline is captured --repeats times (default 2) BEFORE the control is
touched, and the spread between those captures is the floor a swing has to beat;
the closing A-again is combined with it via max(). This is why d7's ply came back
INCONCLUSIVE (swing 13.3% vs drift 10.0%) instead of EFFECTIVE — which is the right
answer: across two runs that same control read -18% and then +3.3%, i.e. noise.

The rule this encodes: on a livecoded rig the material is not stationary. Patterns
written as `"~ c . <[~ c ~ c] [<c ~ ~ c> ...]>"` play a different bar every cycle, so
a before/after difference is evidence of nothing until you know how big a difference
the music produces unaided. Measuring the control without measuring the noise is how
you end up certain about a knob that does nothing.

Also lands the #66 log (armada/tasks/018) with the full per-track and per-control
tables, and the three findings still open: gMask's swing is not clear of its own
variance, a reproducible ~15s settle lag after a control returns to rest, and
Ardour's xrun counter climbing while SuperCollider's does not.
parent 7b2e5b4d
---
log: 018
title: "Every track plays, every knob bites and the instruments that had to be fixed first"
date: 2026-07-28
task: "#66 (with #65, #67, #62)"
tags: [tooling, gig, opal2026, measurement, livecoding]
shareable: true
---
## Cap (what & why)
OPAL is J-7. PLN left the laptop running and gave one mandate: *"ensure all tracks
from gig are playable. you can send fake midi events for each, thus test all
controls have impact on sound ;) even the crushes should change noticeably what you
see/'hear'"*. No ears in the room — every claim had to be instrumental, and every
failure reported as a failure.
## Manœuvre (how)
Two passes, both empirical. **Part A:** boot each of the 10 setlist tracks one at a
time, 20 s settle, then measure every declared orbit *over time* (`probe-chain -t`).
Pass = leaves −inf **and** classifies STEADY, because a DIED/DECAYING orbit means
the eval never took and what you hear is the xfade tail of the outgoing pattern.
**Part B:** drive each control's CC through the real path a physical knob takes
(`aseqsend` → SC `MIDIFunc.cc``/ctrl``sStateMV`), and assert the *right
measurement* moved.
The measurement design was most of the work, because two of my three instruments
were wrong on their first contact with real audio.
## Prise (findings / artifacts)
**All 10 tracks now play.** 10/10 tracks, every declared orbit alive and STEADY.
Two were broken when the day started:
- **`desire.tidal`: 0 of 9 orbits.** Total silence, master bus at −inf. Cause:
`off 0.125 (|+ note 12 . (|* gain 0.6))``.` binds tighter than the section, so
it composed a function into a ValueMap. One pair of parens fixed it; 9/9 STEADY
after (`cfa56b6`).
- **`vague_de_crime.tidal`**: PLN's own `crackles` fix, saved. 9/9.
**Every control tested bites**, measured with the lens that suits it:
| control | measure | result |
|---|---|---|
| `gF1` DJ filter (^49) | centroid | 112 Hz → **3681 Hz**, band energy 72% sub → 75% above 2 k |
| `gF3` DJ filter (^51) | centroid | 384 Hz → **2765 Hz** (+529%) |
| `gMute2` (^74) | rms | −23.1 dB → **silence**, reverts clean |
| `crushbus` (^53) | centroid | 125 → **189 Hz** (+37%) |
| `ply` (^89) | onsets | 8.1 → **4.7/s**, reverts to 7.7 |
| `gMask` (^41) | onsets | **−42%** |
| `gMute1`+`gM1` | rms | silenced all four `gM1` orbits at once |
New tools: `tools/control-lens.py` (the control prover), `tools/check-tracks.sh`
(the pre-gig gate). Commits `cfa56b6`, `708c2ae`, `7b2e5b4`.
## Sel (the shareable learning)
**The instrument was wrong more often than the rig.** Three fixes, and each one is
the same mistake in a different costume — *answering a question the measurement
cannot actually answer*:
1. **rms cannot see a filter.** A DJ filter and a bit-crusher move timbre at roughly
constant loudness. Judging them in dBFS isn't a strict test, it's a meaningless
one. `gMask` read "NO CHANGE, 0.5 dB" — correct arithmetic, wrong question; on
the density lens it reads −42%.
2. **`ply` reported "+inf %, EFFECTIVE" off a division by zero.** The onset detector
returned a flat 0.0/s on a sustained bass, so any change looked infinite. The
tool invented an effect — the one thing it promises never to do. Spectral flux
fixed the detector; a floor below which the answer is INCONCLUSIVE fixed the
honesty.
3. **A decay crosses its own midpoint exactly once.** `probe-chain` called a healthy
`mask "<t f!7>"` riser "DECAYING, almost certainly an xfade tail" — twice. Two
quartile averages cannot answer a monotonicity question. Counting midpoint
crossings can: one is a fade, two or more is a cycle, and a cycle is a part.
And the discipline that makes all three survivable: **A-B-A**. On this rig the
patterns move by themselves — `d2` of `vague_de_crime` plays a different bar every
cycle — so re-measure the baseline at the *end* and treat any swing smaller than
twice the drift as inconclusive. Otherwise you cannot tell "the control did it" from
"the music did it".
## Hameçon (hook)
Three times in one week, "the rig has gone silent" turned out to mean "the code
never compiled" — and each time the damage was *total* rather than partial, because
a blank line is Tidal's block separator and these tracks have almost none.
`desire.tidal` has **zero** blank lines: nine orbits, one block, one misplaced dot,
total silence. The fix that found it wasn't a debugger. It was making the editor stop
deleting the word "error" from GHC's output.
...@@ -219,6 +219,11 @@ def main() -> int: ...@@ -219,6 +219,11 @@ def main() -> int:
ap.add_argument("-s", "--seconds", type=float, default=8.0, ap.add_argument("-s", "--seconds", type=float, default=8.0,
help="capture length per value (default 8s ~ 4 bars at 120)") help="capture length per value (default 8s ~ 4 bars at 120)")
ap.add_argument("--label", default="", help="name for the report line") ap.add_argument("--label", default="", help="name for the report line")
ap.add_argument("--repeats", type=int, default=2, metavar="N",
help="how many times to capture the BASELINE before touching the "
"control (default 2). The spread between them is the noise "
"floor a swing has to beat; 1 disables it and makes every "
"verdict weaker than it looks.")
ap.add_argument("--no-aba", dest="aba", action="store_false", ap.add_argument("--no-aba", dest="aba", action="store_false",
help="skip the closing A-again capture (faster, but then a swing " help="skip the closing A-again capture (faster, but then a swing "
"cannot be told apart from a pattern that varies per cycle)") "cannot be told apart from a pattern that varies per cycle)")
...@@ -245,6 +250,28 @@ def main() -> int: ...@@ -245,6 +250,28 @@ def main() -> int:
print(f"control-lens: DISCARD — baseline unusable: {why}", file=sys.stderr) print(f"control-lens: DISCARD — baseline unusable: {why}", file=sys.stderr)
return 2 return 2
b = measure(base) b = measure(base)
# NOISE FLOOR FROM REPEATED BASELINES, not from a single one.
#
# The closing A-again capture gives ONE estimate of self-drift, and on
# 2026-07-28 that underestimated it badly: two captures of d2 at the SAME
# control setting read 1.6 and 2.4 onsets/s — 33% apart, which happened to
# equal gMask's whole measured swing — yet the closing capture matched the
# opening one exactly, so the reported drift was 0.0%. A single repeat can
# agree with the first reading by luck. Take several and use the SPREAD.
floor_from_repeats = 0.0
for _ in range(max(0, args.repeats - 1)):
rep, why = capture(args.orbit, args.seconds)
if rep is None:
print(f" baseline repeat discarded ({why})", file=sys.stderr)
continue
dr, _, _ = delta_for(args.kind, b, measure(rep))
if not (math.isnan(dr) or math.isinf(dr)):
floor_from_repeats = max(floor_from_repeats, abs(dr))
if floor_from_repeats:
print(f" baseline repeated {args.repeats}x: it varies by "
f"{floor_from_repeats:.1f} on its own\n")
if args.kind == "density" and b["onsets"] < MIN_ONSETS: if args.kind == "density" and b["onsets"] < MIN_ONSETS:
print(f"control-lens: INCONCLUSIVE — d{args.orbit} shows only " print(f"control-lens: INCONCLUSIVE — d{args.orbit} shows only "
f"{b['onsets']:.2f} onsets/s at baseline, which is below what this " f"{b['onsets']:.2f} onsets/s at baseline, which is below what this "
...@@ -302,7 +329,7 @@ def main() -> int: ...@@ -302,7 +329,7 @@ def main() -> int:
# revert or a pattern that simply varied — indistinguishable from an A-B # revert or a pattern that simply varied — indistinguishable from an A-B
# comparison alone. So measure A again and report the drift; a swing smaller # comparison alone. So measure A again and report the drift; a swing smaller
# than the drift is not a result. # than the drift is not a result.
drift = None drift = floor_from_repeats or None
if args.aba: if args.aba:
time.sleep(1.5) time.sleep(1.5)
again, why = capture(args.orbit, args.seconds) again, why = capture(args.orbit, args.seconds)
...@@ -312,7 +339,7 @@ def main() -> int: ...@@ -312,7 +339,7 @@ def main() -> int:
a2 = measure(again) a2 = measure(again)
d2, _, unit2 = delta_for(args.kind, b, a2) d2, _, unit2 = delta_for(args.kind, b, a2)
if not (math.isnan(d2) or math.isinf(d2)): if not (math.isnan(d2) or math.isinf(d2)):
drift = abs(d2) drift = max(abs(d2), floor_from_repeats)
print(f" {'A again':>6s} {a2['rms_db']:8.1f} {a2['centroid']:8.0f}Hz " print(f" {'A again':>6s} {a2['rms_db']:8.1f} {a2['centroid']:8.0f}Hz "
f"{a2['onsets']:8.1f} {a2['bands']['<150']:5.1f} " f"{a2['onsets']:8.1f} {a2['bands']['<150']:5.1f} "
f"{a2['bands']['150-2k']:6.1f} {a2['bands']['2k+']:5.1f} " f"{a2['bands']['150-2k']:6.1f} {a2['bands']['2k+']:5.1f} "
......
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