-
fix(boot): the DJF "fix" was a parse error — BootTidal.hs could not load at all · 77903244
Mid-rehearsal, PLN reported the three DJ filters still misbehaving after yesterday's #48 bidirectional rewrite. He described three symptoms: C1 centre->0 : no LPF effect on d3 (hats) C1 centre->0 : kick goes from TOTALLY high-passed to normal C1 centre->127: d3 high-passes Those fingerprint the *previous* (2026-07-25) definition exactly — `gHPF ch = (# hpf (range 20 8000 ch))`, one-directional, no lpf term: at centre hpf=4010Hz (kick gutted), at 0 hpf=20Hz (normal), at max hpf=8000Hz. Four observations, four matches. So the running ghci was not executing the new code. The question was why. Root cause: the new gDJF was written as a right section containing another operator of the same fixity — gDJF ch = (# lpf (...) # hpf (...)) -- `#` is infixl 9 which is a hard Haskell parse error, not a subtle type slip. GHC: "The operator '#' of a section must have lower precedence than that of the operand". One bad line takes down the entire enclosing `let` block, i.e. EVERY g* helper — gF1/2/3, gMask, gMute*, gPanic, the drum helpers. What made it dangerous rather than merely broken: nothing surfaced it. The live ghci still held the older definitions from before the edit, so the rig kept making sound and behaving plausibly while the file on disk was unloadable. The failure was scheduled for the next cold boot — i.e. on stage at OPAL. Same shape as #21's mute-bomb: a definition that lives only in a running process is not a verified definition. Fix: express it as two composed sections, which is also semantically cleaner (two independent unions rather than one nested): gDJF ch = (# lpf (...)) . (# hpf (...)) The filter math itself was correct and is unchanged — verified at three points: v=0 -> lpf 180 / hpf 20 (low-pass closed to subbass), v=0.5 -> lpf 20000 / hpf 20 (both wide open = true bypass at the centre detent), v=1 -> lpf 20000 / hpf 8000 (high-pass climbed to superhigh). Applied to both BootTidal.hs and the live/lib/prelude.tidal rescue sheet, which had inherited the identical broken form. Also adds tools/check-boot.sh so this class of bug cannot ship again: it extracts the helper let-block, dedents it into a real module, stubs the two stream-bound names it closes over (p, setI), and runs `ghc -fno-code`. Types only — no codegen, no stream, no port 6010 grab, safe to run mid-set. Validation, both directions: - real BootTidal.hs -> OK, exit 0 (whole 164-431 block typechecks against tidal-1.9.5 / ghc 9.4.7) - bug reintroduced -> FAIL, exit 1, pointing at the exact line That negative test earned its keep immediately: the first version of the guard used `ghc | grep -q` under `set -o pipefail`, where a failing ghc makes the pipeline non-zero regardless of grep's verdict, silently inverting the check. It reported OK on a file it had just proven broken. Now it runs ghc once and tests the captured exit code. A guard that has never seen the bug it targets is a hypothesis, not a guard.PLN (Algolia) authored77903244
| Name |
Last commit
|
Last update |
|---|---|---|
| .. | ||
| bridge | Loading commit data... | |
| foundry | Loading commit data... | |
| pulsar-parvagues-hud | Loading commit data... | |
| README.md | Loading commit data... | |
| analyze_samples.py | Loading commit data... | |
| check-boot.sh | Loading commit data... | |
| fan_check.sh | Loading commit data... | |
| gen_tokens.py | Loading commit data... | |
| gen_ts_types.py | Loading commit data... | |
| gig_record.sh | Loading commit data... | |
| mute.tidal | Loading commit data... | |
| sample_tfidf.py | Loading commit data... | |
| setlist_samples.py | Loading commit data... | |
| shipowiz.py | Loading commit data... | |
| solo.tidal | Loading commit data... | |
| tools.png | Loading commit data... | |
| unmute.tidal | Loading commit data... | |
| unsolo.tidal | Loading commit data... |