TuneC: a written tune becomes the bytes the player reads
The compiler, and the last thing the ladder was waiting for. A tune names
its instruments, writes sequences of notes and durations, and gives each
voice an order list of sequence names - which is where repetition comes
from, since a phrase played four times is written once and named four
times.
#Tick 0d125000
#Patch Oboe oboe.patch
#Voice 0d0 Oboe
#Sequence Verse
0d64 0d4 0d67 0d4 0d72 0d8
#Order 0d0
Verse Verse Ending
"#" is a directive and ";" is a comment, exactly as in SplitBit assembly
and in the shell's scripts, and numbers are written the way the assembler
writes them. One rule across the machine rather than a third dialect -
and the rule earned itself immediately: the first tune I wrote said
"#Voice 0" and was refused, correctly, for a bare number.
WHAT IT REFUSES IS EVERYTHING THE PLAYER CANNOT NOTICE. The machine has
no names, so it cannot say a sequence does not exist. It has no lengths,
so it cannot say the voices will come apart four bars after the mistake.
A duration of nought is counted down to 255 and held, which sounds like a
hang rather than an error. And by the time a tune is loaded, "no starting
instrument" and "instrument nought" are the same byte - so the user's
ruling, that a voice with a part and no instrument is an error, can only
be kept here.
SoundPatch gains --blob, writing the same table as raw bytes. It stays
the only thing that reads soundThing's JSON: a second program parsing
that format is a second opinion about what a patch means, and the seam
between two opinions is where the LFO bug lived for a fortnight. Patches
are found beside the tune and then on a -I path, the way an include is.
THE TEST IS THAT TWO IMPLEMENTATIONS AGREE. maketune.py lays the fixture
out by hand and TuneC compiles a written source, and the suite checks
they match byte for byte - the discipline SplitDisk and sbfs.asm are held
to, for the same reason: either alone is only self-consistent. The
fixture predates the compiler, so this is also TuneC checked against
something written before it existed. Four more checks cover the four
refusals.
Also: the SoundPatch binary was tracked, alone among the six tools, and
.gitignore lists every other one. Untracked, and TuneC added beside it.
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
bfc46d982e
commit
2808688fa1
@@ -817,6 +817,55 @@ PY2
|
||||
|| result no "and a command in it loads a patch from it" "$SECOND hertz, wanted the octave"
|
||||
fi
|
||||
|
||||
# ---- The compiler and the hex editor agree ----
|
||||
#
|
||||
# TWO IMPLEMENTATIONS OF ONE FORMAT, which is the discipline SplitDisk and sbfs.asm are
|
||||
# held to and for the same reason: either alone is only self-consistent. maketune.py lays
|
||||
# the bytes out by hand and TuneC compiles a written source, and what the player reads is
|
||||
# right only if both agree about what it should say.
|
||||
#
|
||||
# The fixture is what Play was tested against before TuneC existed, so this is also the
|
||||
# compiler being checked against something that predates it.
|
||||
if cmp -s "$ROOT/Tests/build/.diskwork/compiled.tune" \
|
||||
"$ROOT/Tests/build/.diskwork/two.tune"; then
|
||||
result ok "TuneC writes what the hand-laid fixture does" "byte for byte"
|
||||
else
|
||||
result no "TuneC writes what the hand-laid fixture does" \
|
||||
"$(cmp "$ROOT/Tests/build/.diskwork/compiled.tune" "$ROOT/Tests/build/.diskwork/two.tune" 2>&1 | head -1)"
|
||||
fi
|
||||
|
||||
# ---- And it refuses what the player could not notice ----
|
||||
#
|
||||
# Each of these is something a tune can get wrong that the machine cannot tell you about.
|
||||
# The player has no names, so it cannot say a sequence does not exist; it has no lengths,
|
||||
# so it cannot say the voices will come apart four bars later; and a duration of nought is
|
||||
# counted down to 255 and held, which sounds like a hang rather than a mistake.
|
||||
refuses() {
|
||||
# refuses <what it should say> <line>...
|
||||
local want="$1"; shift
|
||||
printf '%s\n' "$@" > "$BUILD/bad.tune.txt"
|
||||
local said
|
||||
said="$("$ROOT/TuneC" -I "$ROOT/Tests/build/.diskwork" "$BUILD/bad.tune.txt" \
|
||||
"$BUILD/bad.tune" 2>&1)"
|
||||
if [ -f "$BUILD/bad.tune" ]; then rm -f "$BUILD/bad.tune"; fi
|
||||
case "$said" in
|
||||
*"$want"*) result ok "TuneC refuses $want" "and says which line" ;;
|
||||
*) result no "TuneC refuses $want" "$(echo "$said" | head -1)" ;;
|
||||
esac
|
||||
}
|
||||
refuses "would come apart" \
|
||||
"#Tick 0d125000" "#Patch A low.patch" "#Voice 0d0 A" "#Voice 0d1 A" \
|
||||
"#Sequence Four" "0d60 0d4" "#Sequence Eight" "0d60 0d8" \
|
||||
"#Order 0d0" "Four" "#Order 0d1" "Eight"
|
||||
refuses "a part and no instrument" \
|
||||
"#Tick 0d125000" "#Patch A low.patch" "#Sequence S" "0d60 0d4" "#Order 0d0" "S"
|
||||
refuses "a duration is from" \
|
||||
"#Tick 0d125000" "#Patch A low.patch" "#Voice 0d0 A" "#Sequence S" "0d60 0d0" \
|
||||
"#Order 0d0" "S"
|
||||
refuses "no sequence of that name" \
|
||||
"#Tick 0d125000" "#Patch A low.patch" "#Voice 0d0 A" "#Sequence S" "0d60 0d4" \
|
||||
"#Order 0d0" "Nope"
|
||||
|
||||
# ---- And a file that is not a tune is refused ----
|
||||
#
|
||||
# The loader reads offsets out of a file and follows what they name, so a file that is not
|
||||
|
||||
Reference in New Issue
Block a user