#!/usr/bin/env zsh
# check-tracks — boot every track in the setlist and PROVE each declared orbit sounds.
#
# Why this exists (2026-07-28, J-7 to OPAL)
# -----------------------------------------
# Three separate times this week "the rig is silent" turned out to be "the code
# did not compile":
#   * a `crackles` that was never defined       (vague_de_crime)
#   * a DJF helper written with nested `#`      (BootTidal.hs, took EVERY helper)
#   * `|+ note 12 . (|* gain 0.6)`              (desire.tidal, took all 9 orbits)
# In each case the file looked fine, the rig looked healthy, and nothing said
# "error" out loud. 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 and the_revolution have ZERO, so the whole file is ONE
# block, and a block either compiles entirely or not at all.
#
# There is no static typechecker for a .tidal file (it is a fragment of a GHCi
# session, not a module), so the only honest gate is EMPIRICAL: evaluate the
# track and measure whether sound comes out. A track whose every orbit reads -inf
# did not compile. That makes this both a smoke test and a compile check.
#
# Run it the day before the gig, and after ANY edit to BootTidal.hs — a broken
# helper silences the entire corpus at once, which is the worst failure this rig
# has.
#
# Usage
# -----
#     tools/check-tracks.sh                       # the whole OPAL setlist
#     tools/check-tracks.sh live/techno/foo.tidal # just these
#     KEEP=1 tools/check-tracks.sh                # keep the full probe output
#
# Exit 0 = every declared orbit of every track made sound. 1 = at least one did
# not. The per-track detail lands in a log whose path is printed at the end.
#
# TIMING IS NOT NEGOTIABLE: one track at a time, >=20s settle. A tight loop
# rebooting every ~45s lands evals on a half-loaded interpreter (BootTidal.hs
# needs its 6s) and reports false failures — burned 2026-07-28.
#
# CAVEAT, stated plainly: this measures the orbit at SuperCollider AND after
# Ardour's fader. A "SILENT AT SOURCE" verdict is Tidal's fault; an "Ardour eats
# it" verdict is a fader/mute and is NOT a track bug. Read the log, don't just
# read the exit code.
set -u
cd "${0:A:h}/.." || exit 2

# The set list is DATA, shared with tools/orphan-orbits.py (#68) so reordering the
# gig re-answers both "does every orbit sound" and "which orbits ghost" from one
# file. This used to point at a bare `setlist_opal2026.txt` in the repo ROOT, which
# has never existed — so the pre-gig gate could not run at all, and said only
# "no such file" per track if you tried.
SETLIST=${SETLIST:-armada/setlist_opal2026.txt}
LOG=$(mktemp -t check-tracks-XXXXXX.log)

# Without this, `--help` is taken as a track name and the tool answers a request for
# help with "FAIL — no such file", which reads like the rig is broken.
if [[ ${1:-} == (-h|--help) ]]; then
  sed -n '2,44p' "${0:A}" | sed 's/^# \?//'
  exit 0
fi

if [[ ! -f $SETLIST ]]; then
  print -u2 "check-tracks: FAIL — no setlist at $SETLIST"
  print -u2 "  (override with SETLIST=path, or pass tracks as arguments)"
  exit 2
fi

if (( $# )); then
  TRACKS=("$@")
else
  # Strip full-line AND trailing comments: the setlist annotates each path with its
  # codename and BPM (`live/…/wap.tidal   # WAP [133]`), and leaving that on the line
  # makes every track report "no such file".
  TRACKS=(${(f)"$(sed -e 's/#.*//' -e 's/[[:space:]]*$//' $SETLIST | grep -vE '^$')"})
fi

print "check-tracks: ${#TRACKS} track(s), one at a time — this takes ~45s each\n"
fails=0

for T in $TRACKS; do
  if [[ ! -f $T ]]; then
    print "  ${T:t:r}: FAIL — no such file"
    (( fails++ )); continue
  fi
  # Declared orbits, from the track itself. NB: zsh does NOT word-split unquoted
  # variables, so ${=ORB} is mandatory below — without it probe-chain gets ONE
  # bogus argument, exits 2 on a usage error, and a grep'd loop shows empty
  # results that read exactly like "nothing is playing" (burned 2026-07-28).
  ORB=$(grep -oE '^[[:space:]]*d[0-9]+' $T | tr -d ' d' | sort -un | tr '\n' ' ')
  print "########## $T (orbits: $ORB)" >>$LOG

  python3 tools/tidal-remote.py boot $T >>$LOG 2>&1
  sleep 20
  OUT=$(python3 tools/probe-chain.py -d ${=ORB} -s 18 -t 1.5 2>&1)
  print -r -- "$OUT" >>$LOG

  # An orbit passes if it is not silent at SOURCE. DIED/DECAYING is called out
  # separately: it means the eval did not take and what you hear is the xfade
  # tail of the OUTGOING pattern (dN == xfade N).
  n=$(print -r -- "$OUT" | grep -cE '^  d[0-9]+ ')
  silent=$(print -r -- "$OUT" | grep -c 'SILENT AT SOURCE')
  dying=$(print -r -- "$OUT" | grep -cE 'DIED|DECAYING')
  eaten=$(print -r -- "$OUT" | grep -c 'Ardour eats it')
  alive=$(( n - silent ))

  note=""
  (( dying )) && note+=" ${dying} DIED/DECAYING (eval may not have taken)"
  (( eaten )) && note+=" ${eaten} eaten by an Ardour fader (mixer, not Tidal)"

  if (( silent == n && n > 0 )); then
    print "  ${T:t:r}: FAIL — ALL $n orbits silent at source. This track almost"
    print "      certainly DID NOT COMPILE (one block, one error). Check the"
    print "      Pulsar notification for EVAL ERROR."
    (( fails++ ))
  elif (( silent )); then
    print "  ${T:t:r}: FAIL — $alive/$n orbits alive, $silent silent at source.$note"
    (( fails++ ))
  else
    print "  ${T:t:r}: ok — $n/$n orbits alive.$note"
  fi
done

python3 tools/tidal-remote.py hush >/dev/null 2>&1

print "\ncheck-tracks: detail in $LOG"
if (( fails )); then
  print "check-tracks: FAIL — $fails track(s) would not play as written." >&2
  exit 1
fi
print "check-tracks: OK — every declared orbit of every track made sound."
