A tune's tick is the tune's own

useTune took the period out of a file's header and then Play wrote its
own straight over it, so every tune played at a sixteenth note at 120
beats a minute whatever it asked for. A tune with a #Tick of 0d250000
lasted half as long as it said.

Nothing noticed because every fixture in the suite asked for exactly the
tick Play had written into itself. A test that agrees with the bug by
coincidence is not a test, and the way to find out is a fixture that
wants something else - so slow.tune is two.tune with twice the period and
nothing else changed, and it has to last twice as long.

The period now belongs to whoever supplied the tune: useTune sets it from
the header, useBuiltIn sets its own, and the start code writes only the
control byte - which has to come after either of them, because writing
control with the run bit set is what loads the period.

Found while reading Play to see how a splash screen would drive the
player, which is a reminder that the second reader of a piece of code is
worth more than the first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-09-06 09:37:08 -04:00
co-authored by Claude Opus 5
parent 87467a7d3a
commit 6bb1565dea
3 changed files with 47 additions and 9 deletions
+22
View File
@@ -817,6 +817,28 @@ PY2
|| result no "and a command in it loads a patch from it" "$SECOND hertz, wanted the octave"
fi
# ---- A tune's tick is the tune's ----
#
# slow.tune is two.tune with twice the period and nothing else changed, so it has to last
# twice as long. Play used to write its own tick over the one useTune had just taken out
# of the header, and every tune played at a sixteenth note at 120 whatever it asked for -
# invisible for as long as every fixture asked for exactly that.
printf 'Play slow.tune\n\n\nexit\n' > "$BUILD/slow.in"
timeout 60 "$EMU" --fast --cycles 8000000 --sound "$BUILD/slow.raw" \
--disk "$ROOT/Tests/build/disks/quiet.img" "$BUILD/cosmos.bin" \
< "$BUILD/slow.in" > "$BUILD/slow.out" 2>&1
SPAN="$(python3 - "$BUILD/slow.raw" <<'PY2'
import struct, sys
d = open(sys.argv[1], "rb").read(); n = len(d) // 2
v = struct.unpack("<%dh" % n, d)
loud = [i for i, x in enumerate(v) if abs(x) > 500]
print(loud[-1] - loud[0] if loud else 0)
PY2
)"
near "$SPAN" 96000 2 \
&& result ok "a tune's tick is the tune's own" "$SPAN samples, and 8 ticks of 250,000 cycles is 96,000" \
|| result no "a tune's tick is the tune's own" "$SPAN samples, wanted about 96,000"
# ---- The compiler and the hex editor agree ----
#
# TWO IMPLEMENTATIONS OF ONE FORMAT, which is the discipline SplitDisk and sbfs.asm are