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:
co-authored by
Claude Opus 5
parent
87467a7d3a
commit
6bb1565dea
@@ -133,15 +133,15 @@ playReady:
|
|||||||
|
|
||||||
; ---- The beat ----
|
; ---- The beat ----
|
||||||
;
|
;
|
||||||
; 125,000 cycles is a sixteenth note at 120 beats a minute, which is the smallest thing
|
; ONLY THE CONTROL BYTE. The period was set by whoever supplied the tune, because a tick
|
||||||
; this piece divides a beat into. The period goes in before the control byte, because
|
; belongs to the tune and not to this program: a file says what it wants in its header.
|
||||||
; writing control with the run bit set is what loads it.
|
;
|
||||||
INIA 0x01
|
; It used to be written here as well, AFTER useTune had taken it out of the file - so every
|
||||||
OUTA 0x52
|
; tune played at a sixteenth note at 120 beats a minute whatever its header said. The only
|
||||||
INIA 0xE8
|
; reason nothing noticed is that the one fixture in the suite asks for exactly that.
|
||||||
OUTA 0x53
|
;
|
||||||
INIA 0x48
|
; Writing control with the run bit set is what loads the period, which is why this comes
|
||||||
OUTA 0x54
|
; after whoever set it and not before.
|
||||||
INIA 0x07
|
INIA 0x07
|
||||||
OUTA 0x51 ; Run, repeat, interrupt.
|
OUTA 0x51 ; Run, repeat, interrupt.
|
||||||
SIF
|
SIF
|
||||||
@@ -220,6 +220,15 @@ playNotATune:
|
|||||||
; three tables the player asks for, and an order list for each voice. Nothing is relocated,
|
; three tables the player asks for, and an order list for each voice. Nothing is relocated,
|
||||||
; because the assembler already wrote the addresses.
|
; because the assembler already wrote the addresses.
|
||||||
useBuiltIn:
|
useBuiltIn:
|
||||||
|
; The tick this tune wants. For a file it comes out of the header; for this one it is here,
|
||||||
|
; so that both hand the player the same things.
|
||||||
|
INIA 0x01
|
||||||
|
OUTA 0x52
|
||||||
|
INIA 0xE8
|
||||||
|
OUTA 0x53
|
||||||
|
INIA 0x48
|
||||||
|
OUTA 0x54 ; 0x01E848 is 125,000: a sixteenth note at 120.
|
||||||
|
|
||||||
SETD.0 BuiltInPatches
|
SETD.0 BuiltInPatches
|
||||||
SETD.1 PatchTable
|
SETD.1 PatchTable
|
||||||
STD.0.1
|
STD.0.1
|
||||||
|
|||||||
@@ -736,6 +736,13 @@ open(sys.argv[1], "wb").write(blob(128))
|
|||||||
open(sys.argv[2], "wb").write(blob(129))
|
open(sys.argv[2], "wb").write(blob(129))
|
||||||
FIXTURE
|
FIXTURE
|
||||||
"$ROOT/TuneC" -I "$WORK" "$ROOT/Programs/Tunes/two.tune.txt" "$WORK/compiled.tune" >/dev/null
|
"$ROOT/TuneC" -I "$WORK" "$ROOT/Programs/Tunes/two.tune.txt" "$WORK/compiled.tune" >/dev/null
|
||||||
|
|
||||||
|
# The same tune at half the speed. A TICK BELONGS TO THE TUNE, and the only reason nothing
|
||||||
|
# noticed that Play was ignoring it was that every fixture asked for the tick Play happened to
|
||||||
|
# have written into itself. One that asks for something else is the whole check.
|
||||||
|
sed 's/#Tick 0d125000/#Tick 0d250000/' "$ROOT/Programs/Tunes/two.tune.txt" > "$WORK/slow.tune.txt"
|
||||||
|
"$ROOT/TuneC" -I "$WORK" "$WORK/slow.tune.txt" "$WORK/slow.tune" >/dev/null
|
||||||
|
"$TOOL" put "$DISKS/quiet.img" "$WORK/slow.tune" slow.tune >/dev/null
|
||||||
"$ROOT/Assembler" -I "$ROOT/Programs/Sounds" -I "$ROOT/Programs/Libraries" \
|
"$ROOT/Assembler" -I "$ROOT/Programs/Sounds" -I "$ROOT/Programs/Libraries" \
|
||||||
-I "$ROOT/Programs/CosmOS/Source" \
|
-I "$ROOT/Programs/CosmOS/Source" \
|
||||||
"$ROOT/Programs/CosmOS/Apps/Play.asm" -o "$WORK/Play.sbx" >/dev/null
|
"$ROOT/Programs/CosmOS/Apps/Play.asm" -o "$WORK/Play.sbx" >/dev/null
|
||||||
|
|||||||
@@ -817,6 +817,28 @@ PY2
|
|||||||
|| result no "and a command in it loads a patch from it" "$SECOND hertz, wanted the octave"
|
|| result no "and a command in it loads a patch from it" "$SECOND hertz, wanted the octave"
|
||||||
fi
|
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 ----
|
# ---- The compiler and the hex editor agree ----
|
||||||
#
|
#
|
||||||
# TWO IMPLEMENTATIONS OF ONE FORMAT, which is the discipline SplitDisk and sbfs.asm are
|
# TWO IMPLEMENTATIONS OF ONE FORMAT, which is the discipline SplitDisk and sbfs.asm are
|
||||||
|
|||||||
Reference in New Issue
Block a user