The player speaks indices, which is the shape a file has to be

Order lists hold one-byte sequence indices and 0x80 holds a one-byte
patch index. Two tables, PatchTable and SequenceTable, are the only
places an address lives.

THAT IS WHAT MAKES A TUNE LOADABLE WITHOUT WALKING IT. Nothing inside a
sequence or an order list is an address, so putting one in memory means
adding the load address to two arrays and nothing else. The alternative
is a loader that parses every sequence looking for addresses to correct,
which is a loader a malformed file can walk off a cliff.

Doing it now, while the tune is still assembled in, means the file form
and the assembled form are the same shape - so reading a tune from a file
will change no engine code at all. The whole point of the rung.

The patch each voice starts on moved from four calls in a row into
VoiceStart, four declared bytes, and loadStartPatches reads them. A
starting instrument is state and belongs where state goes: the user's
ruling is that a voice with undefined state is an error, prompted by
noticing that a program run a second time starts with the memory the
first run left, because loading is what initialises and running is not.

Order lists also halved in size, which was not the reason but is welcome.

Hand-writing the two tables is exactly the tedium the compiler exists to
remove - every sequence counted into its place, and moving one means
renumbering. Better to feel that here than after a tool has baked the
shape in.

Verified by rendering: bar for bar the same piece. break.sh confirms the
scaling, since an index is doubled to reach a two-byte entry and halving
that step lands on the wrong sequence and fails four checks.

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-05 20:45:40 -04:00
co-authored by Claude Opus 5
parent 8029063bdc
commit 5d9b39514b
14 changed files with 132 additions and 64 deletions
+20 -16
View File
@@ -28,16 +28,11 @@
#Program
start:
; A patch each, in the format SoundPatch writes: a count and that many pairs. They differ
; in ONE parameter - the octave - and the low one is loaded first, so if a patch belonged to
; the device rather than to its channel the second would overwrite the first and both voices
; would sound the same.
SETD.0 PatchLow
RSTA
CALL loadPatch
SETD.0 PatchHigh
INIA 0d1
CALL loadPatch
; A patch each, in the format SoundPatch writes: a count and that many pairs. They differ in
; ONE parameter - the octave - and VoiceStart gives the low one to voice 0 and the high one
; to voice 1. If a patch belonged to the device rather than to its channel the second would
; overwrite the first and both voices would sound the same.
CALL loadStartPatches
INIA 0x60
OUTA 0x46
@@ -96,15 +91,24 @@ tick:
; voice 0 Beat Quiet Beat Switch
; voice 1 Quiet Beat Beat (its order list ends here)
Order0:
Beat Quiet Beat Switch Tail 0xFF 0xFF
0d0 0d1 0d0 0d2 0d3 0xFF
Order1:
Quiet Beat Beat 0xFF 0xFF ; And this one ENDS, so the last bar has voice 1 stopped rather
0d1 0d0 0d0 0xFF ; And this one ENDS, so the last bar has voice 1 stopped rather
; than resting. A player that missed the terminator would read
; whatever follows as a sequence address and play it.
; whatever follows as a sequence index and play it.
Order2:
Tail 0xFF 0xFF
0d3 0xFF
Order3:
Tail 0xFF 0xFF
0d3 0xFF
PatchTable:
PatchLow PatchHigh
SequenceTable:
Beat Quiet Switch Tail ; 0 to 3
VoiceStart:
0d0 0d1 0d0 0d0
Beat:
0d60 0d4 ; Note 60 on both voices. The octave between them is their patches.
@@ -113,7 +117,7 @@ Quiet:
0d00 0d4
0xFF
Switch:
0x80 PatchHigh ; And voice 0 takes voice 1's instrument, mid-piece.
0x80 0d1 ; Voice 0 takes patch one, which is voice 1's, mid-piece.
0d60 0d4 ; The same note number it has played all along.
0xFF
Tail: