Commit 6ab09eaa by PLN (Algolia)

feat(gig): gig-up --live — prove a RIG exists, not just a set (#121, #120)

gig-up proved the set COMPILES and said nothing about whether anything could make
a sound. scsynth was read only to colour a banner (:106,:115) and to skip --audio
(:202) — it never failed. This morning the audio server had been dead for two days
and the gate would still have printed GO.

--live is a MODE, not the default. Cold-by-default remains right: cold checks are
safe mid-set and must work on a train. Severity is chosen per link, deliberately:

  HARD  scsynth alive / sclang :57120 / SC -> Ardour (>=24 pw links)
        no audio server, no listener, or no path to the DAW = there is no gig.
  SOFT  Tidal :6010, LCXL -> SC, SuperDirt warmed, perf mode
        all legitimately absent ten minutes before doors; a gate that reddens for
        them is a gate PLN stops reading.

Every check reads the thing that MAKES SOUND — a process, a socket, a real pw-link —
never `systemctl is-active`. That distinction is the whole reason this exists: the
unit reported active/running for two days while the rig was mute, because MainPID is
sclang and scsynth is its child.

ALSO, and it came from PLN's ears: "preload covers set", a HARD cold check.

He heard crackles moving do_it_right -> take_5_drops. Measured in that window: 0
SuperDirt lates, 0 pipewire xruns, 17 lazy soundfile reads — all take5:*. Disk I/O,
not CPU. The preload MECHANISM was healthy and said so ("47/47 banks OK in 1.9 s");
it was warming the wrong list. preload.scd was dated Jul 28, header "10 track(s)
scanned", while the set is 13 — because setlist_samples.py reads setlist_opal2026.txt
(10 tracks) and gig-up/cheat-sheet trust set-coherence.setlist_tracks() (13). The
missing three are do_it_right, take_5_drops, electric_hammer: exactly the pair he was
transitioning between. #120 tracks which source becomes canonical — not guessed here.

Two properties made it invisible, and they are the transferable lesson: preload.scd
is GITIGNORED (correctly — generated), so no diff ever showed it drifting, and
NOTHING COMPARED IT TO THE SET. A generated artifact with no freshness check is a
stale artifact waiting for the worst possible moment; its natural debut was a cold
boot at the venue, on the first play of every track added since Jul 28. It only
surfaced at a desk because SuperDirt had been up since Jul 28 with those banks cached
from a first play weeks earlier, and a restart cleared the cache.

tools/check-preload.sh compares BANK SETS (not bytes — the generator stamps a header
and orders banks, so a byte diff would cry wolf), borrows set-coherence's parser
rather than adding a fourth definition of "the setlist", and has --fix.

Validation, all of it adversarial:
 * check-preload mutation-tested — restore the Jul-28 plan and it names the 7 banks
   that would crackle: daft fbreak120 hammer popkick realclaps superfreak take5.
   hammer and superfreak are independently confirmed: they appear as lazy loads in
   this morning's journal, before any of this was written.
 * --live on the live rig: 7/7 green after fixing MY OWN check (below).
 * THE GATE BITES: run under a PATH shim whose pgrep reports scsynth absent ->
   NO-GO, exit 1, correct fix hint, real rig never touched. That is precisely the
   state that printed GO this morning.
 * cold run unchanged: still GO, live chain not executed.

One check was wrong before it was right, in the session's recurring shape. "SuperDirt
warmed" first warned about a rig that was perfectly warm, because
`journalctl --user -u parvagues-sc` returns lines from a PREVIOUS instance on this
box — it handed back Jul 30 output while the current instance had already logged
"51/51 banks OK". Now filtered by SyslogIdentifier and keyed to the unit's own
ActiveEnterTimestamp, so it asks "did THIS boot warm?" rather than "was there ever a
PRELOAD line", which any previous boot would satisfy forever.
parent 03faf72b
#!/usr/bin/env bash
# check-preload — is preload.scd warming the set we are actually going to play?
#
# tools/check-preload.sh # verify; exit 1 if stale
# tools/check-preload.sh --fix # regenerate it from the setlist
#
# WHY (2026-08-01, found by ear). PLN heard crackles moving do_it_right ->
# take_5_drops. Measured in that window: 0 SuperDirt lates, 0 pipewire xruns, and 17
# lazy soundfile reads — every one of them `take5:*`. Not CPU, not thermal: disk I/O,
# because SuperDirt was reading that track's samples for the first time mid-transition.
#
# The preload MECHANISM was healthy and said so: === PRELOAD: 47/47 banks OK in 1.9 s ===
# It was warming the wrong list. preload.scd was dated Jul 28 and its own header said
# "10 track(s) scanned" while the set is 13 — because tools/setlist_samples.py reads
# setlist_opal2026.txt (10 tracks) while gig-up/cheat-sheet trust
# set-coherence.setlist_tracks() (13). See #120.
#
# TWO PROPERTIES MADE IT INVISIBLE, and both are the real lesson:
# * preload.scd is GITIGNORED (correctly — it is generated), so nothing in the repo
# could keep it fresh and no diff ever showed it drifting.
# * NOTHING COMPARED IT TO THE SET. A generated artifact with no freshness check is a
# stale artifact waiting for the worst possible moment. Its natural debut was a cold
# boot at the venue — the first play of every track added since Jul 28.
# It only surfaced at a desk because SuperDirt had been up since Jul 28 and had cached
# those banks on first play weeks earlier; a restart cleared the cache and exposed it.
set -uo pipefail
cd "$(dirname "$0")/.." || exit 2
FIX=0
[ "${1:-}" = "--fix" ] && FIX=1
# The canonical setlist = whatever gig-up and cheat-sheet already trust. Borrowed, not
# re-implemented: a fourth parser for "what is the setlist" is how this bug happened.
tracks=$(python3 -c "
import importlib.util
spec = importlib.util.spec_from_file_location('sc','tools/set-coherence.py')
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
print(' '.join(str(p) for p in m.setlist_tracks()))") || {
echo "check-preload: could not read the setlist from set-coherence.py" >&2; exit 2; }
# Compare BANK SETS, not file bytes: the generator stamps a header and orders banks, and
# a diff on either would cry wolf on a rebuild that changed nothing that matters.
banks() { grep -oE '\[ \\[a-z0-9_]+' "$1" 2>/dev/null | sed 's/.*\\//' | sort -u; }
new=$(mktemp); trap 'rm -f "$new"' EXIT
# shellcheck disable=SC2086
python3 tools/setlist_samples.py $tracks --emit-sc > "$new" 2>/dev/null || {
echo "check-preload: setlist_samples.py failed" >&2; exit 2; }
if [ ! -f preload.scd ]; then
echo "check-preload: preload.scd does NOT EXIST — every sample will lazy-load on first play."
missing=$(banks "$new" | wc -l); have=0
else
missing=$(comm -13 <(banks preload.scd) <(banks "$new") | wc -l)
have=$(banks preload.scd | wc -l)
fi
want=$(banks "$new" | wc -l)
if [ "$missing" -eq 0 ] && [ -f preload.scd ]; then
echo "check-preload: ok — $have bank(s) warmed, covering all $(echo "$tracks" | wc -w) setlist track(s)."
exit 0
fi
echo "check-preload: STALE — preload.scd warms $have bank(s), the set needs $want."
echo " $missing bank(s) would be read from DISK on first play (crackle, mid-transition):"
if [ -f preload.scd ]; then
comm -13 <(banks preload.scd) <(banks "$new") | head -20 | sed 's/^/ /'
fi
if (( FIX )); then
cp "$new" preload.scd
echo " fixed: preload.scd regenerated ($want banks). Takes effect at the NEXT SuperDirt boot —"
echo " systemctl --user restart parvagues-sc (~15s of silence, safe when not playing)"
exit 0
fi
echo " fix: tools/check-preload.sh --fix"
exit 1
......@@ -46,6 +46,7 @@
# -----
# tools/gig-up.sh # the cold gate — safe anywhere, ~1 min
# tools/gig-up.sh --audio # + check-tracks.sh (makes sound, ~10 min)
# tools/gig-up.sh --live # + assert a RIG exists, not just a set
# tools/gig-up.sh --quiet # only the verdict and any failures
#
# Exit 0 = GO. Non-zero = do not start; the reasons are printed first.
......@@ -54,9 +55,11 @@ cd "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" || exit 2
AUDIO=0
QUIET=0
LIVE=0
for a in "$@"; do
case "$a" in
--audio) AUDIO=1 ;;
--live) LIVE=1 ;;
--quiet|-q) QUIET=1 ;;
-h|--help) sed -n '2,58p' "${BASH_SOURCE[0]}" | sed 's/^# \?//'; exit 0 ;;
*) echo "gig-up: unknown option $a (try --help)" >&2; exit 2 ;;
......@@ -156,6 +159,16 @@ spec = importlib.util.spec_from_file_location(\"sc\", \"set-coherence.py\")
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
print(\" \".join(str(p) for p in m.setlist_tracks()))")'
# --- 5b. the preload plan covers the set -------------------------------------
# Cold, and HARD. On 2026-08-01 preload.scd was 4 days old and 3 tracks short, so
# take_5_drops' banks were read from DISK on first play and PLN heard it as crackle
# mid-transition. Every other check was green throughout: the plan is GENERATED and
# GITIGNORED, so nothing kept it fresh and nothing compared it to the set. Its natural
# debut was a cold boot at the venue.
run "preload covers set" \
"tools/check-preload.sh --fix # then restart parvagues-sc to warm it" \
tools/check-preload.sh
# --- 6. orbit ghosts across transitions --------------------------------------
# Advisory ON PURPOSE. Ghosting orbits are real (#77) and known-open; they make
# a transition messy, not the gig impossible. A gate that goes red for open work
......@@ -172,6 +185,64 @@ soft "LCXL present" \
"replug the LCXL (USB OUT endpoint stalls — LEDs dark but input alive), then tools/lcxl-init.py" \
bash -c 'aseqdump -l 2>/dev/null | grep -qi "Launch Control XL"'
# --- 7b. the live chain, opt-in (--live) -------------------------------------
# Everything above proves the SET is correct. None of it proves a RIG EXISTS.
# On 2026-08-01 scsynth had been dead for two days and this gate would still have
# printed GO: scsynth was read only to colour a banner and to skip --audio.
#
# Cold-by-default stays the right default — cold checks are safe to run mid-set, and
# a gate that demands hardware would be useless on a train. So this is a MODE.
#
# Severity is chosen per link, on purpose:
# HARD no audio server / no listener / no path to the DAW = there is no gig.
# SOFT Pulsar not open yet, desk unplugged, Ardour not launched, laptop on battery
# — all normal ten minutes before doors, and a gate that reddens for them is a
# gate PLN stops reading.
#
# Every check reads the thing that MAKES SOUND — a process, a socket, a real link —
# never `systemctl is-active`. That distinction is the whole reason this section
# exists: the unit reported active/running for two days while the rig was mute.
if (( LIVE )); then
(( QUIET )) || { echo; echo " ${D}live chain — is there a rig, not just a set?${Z}"; }
run "scsynth alive" \
"systemctl --user start parvagues-sc # the watchdog should beat you to it" \
pgrep -x scsynth
run "sclang :57120" \
"SuperDirt is not listening — systemctl --user restart parvagues-sc" \
bash -c 'ss -lunp 2>/dev/null | grep -q ":57120"'
# 12 orbits x 2 channels = 24 at an absolute minimum; a healthy rig shows ~52.
# Zero here with scsynth alive means SuperCollider is running and going NOWHERE.
run "SC -> Ardour" \
"check the autoroute: systemctl --user restart tidal-ardour-autoroute" \
bash -c 'n=$(pw-link -l 2>/dev/null | grep -ci supercollider); echo "supercollider links: $n"; [ "$n" -ge 24 ]'
soft "Tidal :6010" \
"open the set in Pulsar and boot Tidal (nothing is sending patterns yet)" \
bash -c 'ss -lunp 2>/dev/null | grep -q ":6010"'
soft "LCXL -> SC" \
"replug the LCXL, then tools/lcxl-init.py — knobs will move nothing until this is up" \
bash -c 'aconnect -l 2>/dev/null | grep -A3 "Launch Control XL" | grep -q "128:"'
# Asks whether THIS instance warmed, by keying to the unit's own start time —
# not "was there ever a PRELOAD line", which a previous boot would satisfy forever.
#
# NOTE the query. `journalctl --user -u parvagues-sc` is NOT reliable on this box:
# it returned lines from a PREVIOUS instance (Jul 30) while the current one had
# already logged "51/51 banks OK", so the first version of this check warned about a
# rig that was perfectly warm. Filter the plain journal by SyslogIdentifier instead.
soft "SuperDirt warmed" \
"no PRELOAD line since this boot — systemctl --user restart parvagues-sc to warm the set's banks" \
bash -c 't=$(systemctl --user show parvagues-sc.service -p ActiveEnterTimestamp --value); [ -n "$t" ] || exit 1; journalctl -t parvagues-sc --since "$t" --no-pager 2>/dev/null | grep -q "banks OK"'
soft "perf mode" \
"powerprofilesctl set performance # and plug in AC: battery caps turbo" \
bash -c 'powerprofilesctl get 2>/dev/null | grep -q performance'
fi
# --- verdict -----------------------------------------------------------------
# Failures print FIRST and carry their fix, because the read order in a field is
# "what is broken / what do I type", not "here is a report".
......
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