Play reads a tune from a file
The loader, and the reason it is small: everything in a tune is an offset from wherever it was put, so taking one means adding the base to two tables and pointing four voices at their order lists. No sequence is walked. Nothing inside one is an address to be found and corrected, which is the difference between a malformed tune that plays wrongly and one that takes the loader with it. "SBTU", version, the tick in cycles, how many patches and sequences, offsets to the two tables, an order list each, and the patch each voice starts on. The magic is checked before anything else, because from there on the loader follows what the offsets name. break.sh shows what that guard is worth: without it, handing Play a PROGRAM runs the machine away until the cycle limit, rather than saying it is not a tune. PatchTable, SequenceTable and VoiceStart became pointers, so the engine does not care whether a tune came out of a file or was assembled in. Play's built-in tune now hands over the same three addresses a loaded one would, in eight lines - which is what keeps the two paths from drifting, and what made this rung change no scheduler code at all. Tests/maketune.py lays the fixture out byte by byte. IT IS NOT THE COMPILER: the sequences and the patches are literal bytes and the only thing computed is where each piece lands. That is the point - the loader is checked by something that does not share its idea of the format, which is the same reason SplitDisk and sbfs.asm share nothing but a specification. Both notes in the fixture are number 60, so the octave between them is a 0x80 command loading the second patch out of the file: the header, the tick, the relocation, an order list and a patch from a file, measured in one go. Play and the tune live on quiet.img rather than cosmos.img, so a fixture does not move ten recordings every time it changes size. 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
5d9b39514b
commit
bfc46d982e
@@ -109,6 +109,7 @@ stepCommand:
|
||||
DPDN.1 0d2
|
||||
|
||||
SETD.2 PatchTable
|
||||
LDD.2.2 ; The table, wherever this tune put it.
|
||||
DPUA.2
|
||||
DPUA.2 ; Twice, because an entry is two bytes.
|
||||
LDD.2.2 ; And DP2 follows the address it is now holding.
|
||||
@@ -149,9 +150,10 @@ stepSequenceEnd:
|
||||
STD.0.1 ; The order cursor, moved past this entry.
|
||||
|
||||
SETD.2 SequenceTable
|
||||
LDD.2.2 ; The table, wherever this tune put it.
|
||||
DPUA.2
|
||||
DPUA.2
|
||||
LDD.2.2 ; DP2 is the sequence that index names.
|
||||
LDD.2.2 ; And the sequence that index names.
|
||||
|
||||
DPUP.1 0d2
|
||||
STD.2.1 ; And the sequence cursor set to the new one.
|
||||
@@ -184,10 +186,12 @@ loadStartOne:
|
||||
SETD.3 ThisChannel
|
||||
STA.3
|
||||
|
||||
SETD.2 VoiceStart
|
||||
SETD.2 VoiceStartAt
|
||||
LDD.2.2
|
||||
DPUA.2
|
||||
LDA.2 ; Which patch this voice starts on.
|
||||
SETD.2 PatchTable
|
||||
LDD.2.2
|
||||
DPUA.2
|
||||
DPUA.2
|
||||
LDD.2.2
|
||||
@@ -204,6 +208,186 @@ loadStartOne:
|
||||
BNQ loadStartOne
|
||||
RET
|
||||
|
||||
; ---- base + offset, into wherever the caller wants it ----
|
||||
;
|
||||
; DP0 names two bytes of offset and DP1 two bytes to put the address in. They may be the same
|
||||
; place, which is how a table is relocated in position: the low byte is written before the
|
||||
; high byte is read, so nothing is clobbered under itself.
|
||||
;
|
||||
; The low bytes carry into the high ones, which is the whole reason ADD takes the Carry Flag.
|
||||
tuneAddr:
|
||||
SETD.2 TuneBase
|
||||
CCF
|
||||
INCD.0
|
||||
LDA.0 ; The offset, low byte.
|
||||
INCD.2
|
||||
LDB.2 ; And the base, low byte.
|
||||
ADD
|
||||
INCD.1
|
||||
STQ.1
|
||||
DECD.0
|
||||
LDA.0 ; The offset, high byte.
|
||||
DECD.2
|
||||
LDB.2
|
||||
ADD ; With the carry the low bytes made.
|
||||
DECD.1
|
||||
STQ.1
|
||||
RET
|
||||
|
||||
; Every entry of the table at DP0 turned from an offset into an address. B is how many.
|
||||
tuneReloc:
|
||||
PSHD.0
|
||||
POPD.1
|
||||
CALL tuneAddr ; A CALL hands DP0 and DP1 back, so both are still the entry.
|
||||
INCD.0
|
||||
INCD.0
|
||||
DECB
|
||||
BNB tuneReloc
|
||||
RET
|
||||
|
||||
; ---- A tune, from wherever it was put ----
|
||||
;
|
||||
; DP0 names its first byte. EVERYTHING IN A TUNE IS AN OFFSET FROM THERE, so this adds the
|
||||
; base to the two tables and the four order lists and nothing else in the file is touched. No
|
||||
; sequence is walked, and nothing inside one is an address to be found and corrected - which
|
||||
; is what makes a malformed tune something that plays wrongly rather than something that takes
|
||||
; the loader with it.
|
||||
;
|
||||
; Q is nought if the tune was taken, and anything else if the file was not one.
|
||||
useTune:
|
||||
SETD.1 TuneBase
|
||||
STD.0.1
|
||||
|
||||
; "SBTU", and version one. A file that is not a tune has to be refused here, because
|
||||
; everything below reads offsets out of it and jumps to what they name.
|
||||
SETD.1 TuneMagic
|
||||
INIB 0d5
|
||||
useTuneMagic:
|
||||
LDA.0
|
||||
PSHB
|
||||
LDB.1
|
||||
XOR
|
||||
POPB
|
||||
BNQ useTuneNo
|
||||
INCD.0
|
||||
INCD.1
|
||||
DECB
|
||||
BNB useTuneMagic
|
||||
|
||||
; The tick, straight into the timer. Three bytes, most significant first, which is the
|
||||
; order the ports take them in.
|
||||
LDA.0
|
||||
OUTA 0x52
|
||||
INCD.0
|
||||
LDA.0
|
||||
OUTA 0x53
|
||||
INCD.0
|
||||
LDA.0
|
||||
OUTA 0x54
|
||||
|
||||
; How many of each, kept before the pointers move.
|
||||
INCD.0
|
||||
LDA.0
|
||||
SETD.1 TunePatches
|
||||
STA.1
|
||||
INCD.0
|
||||
LDA.0
|
||||
SETD.1 TuneSequences
|
||||
STA.1
|
||||
|
||||
; The two tables.
|
||||
INCD.0
|
||||
SETD.1 PatchTable
|
||||
CALL tuneAddr
|
||||
INCD.0
|
||||
INCD.0
|
||||
SETD.1 SequenceTable
|
||||
CALL tuneAddr
|
||||
|
||||
; And an order list each.
|
||||
INCD.0
|
||||
INCD.0
|
||||
SETD.1 Voice0
|
||||
CALL tuneAddr
|
||||
INCD.0
|
||||
INCD.0
|
||||
SETD.1 Voice1
|
||||
CALL tuneAddr
|
||||
INCD.0
|
||||
INCD.0
|
||||
SETD.1 Voice2
|
||||
CALL tuneAddr
|
||||
INCD.0
|
||||
INCD.0
|
||||
SETD.1 Voice3
|
||||
CALL tuneAddr
|
||||
|
||||
; The starting instruments are not an offset but a place IN the file, so they are found by
|
||||
; counting rather than by adding.
|
||||
SETD.0 TuneBase
|
||||
LDD.0.0
|
||||
DPUP.0 0d22
|
||||
SETD.1 VoiceStartAt
|
||||
STD.0.1
|
||||
|
||||
; Now the tables themselves, whose entries are offsets like everything else.
|
||||
SETD.0 PatchTable
|
||||
LDD.0.0
|
||||
SETD.1 TunePatches
|
||||
LDB.1
|
||||
CALL tuneReloc
|
||||
SETD.0 SequenceTable
|
||||
LDD.0.0
|
||||
SETD.1 TuneSequences
|
||||
LDB.1
|
||||
CALL tuneReloc
|
||||
|
||||
CALL startVoices
|
||||
|
||||
; Q is nought, which is how this says it worked. There is no instruction that sets Q: it is
|
||||
; the ALU's output and nothing else, so saying nought means doing a sum that comes to it.
|
||||
RSTA
|
||||
RSTB
|
||||
XOR
|
||||
RET
|
||||
|
||||
useTuneNo:
|
||||
; Q is already not nought, because that is what got here.
|
||||
RET
|
||||
|
||||
; ---- Four voices at the beginning of their order lists ----
|
||||
;
|
||||
; Whoever supplied the tune has set the order cursors; this sets everything else. A voice
|
||||
; starts on Empty with a count of one, so its first tick runs the count out, finds the end of
|
||||
; a sequence, and goes to the order list for the real first one. The beginning of a piece
|
||||
; needs no special case anywhere.
|
||||
startVoices:
|
||||
SETD.1 Voice0
|
||||
CALL startOneVoice
|
||||
SETD.1 Voice1
|
||||
CALL startOneVoice
|
||||
SETD.1 Voice2
|
||||
CALL startOneVoice
|
||||
SETD.1 Voice3
|
||||
CALL startOneVoice
|
||||
INIA 0d4
|
||||
SETD.1 Playing
|
||||
STA.1
|
||||
CALL loadStartPatches
|
||||
RET
|
||||
|
||||
startOneVoice:
|
||||
SETD.0 Empty
|
||||
DPUP.1 0d2
|
||||
STD.0.1
|
||||
INCD.1
|
||||
INCD.1
|
||||
INIA 0d1
|
||||
STA.1 ; A count of one, which runs out on the first tick.
|
||||
INCD.1
|
||||
STA.1 ; And live.
|
||||
RET
|
||||
|
||||
; ---- A patch, onto the channel named by A ----
|
||||
;
|
||||
; A count, then that many pairs of parameter and value: the format SoundPatch writes and the
|
||||
@@ -253,6 +437,24 @@ Playing:
|
||||
ThisChannel:
|
||||
0x00
|
||||
|
||||
; Where this tune's tables are. Pointers rather than the tables themselves, because a tune
|
||||
; read from a file puts them wherever it was put, and the engine must not care which.
|
||||
PatchTable:
|
||||
0x00 0x00
|
||||
SequenceTable:
|
||||
0x00 0x00
|
||||
VoiceStartAt:
|
||||
0x00 0x00
|
||||
|
||||
TuneBase:
|
||||
0x00 0x00
|
||||
TunePatches:
|
||||
0x00
|
||||
TuneSequences:
|
||||
0x00
|
||||
TuneMagic:
|
||||
0x53 0x42 0x54 0x55 0x01 ; "SBTU" and version one.
|
||||
|
||||
; The sequence a voice starts on, so that its first tick goes through the order list like
|
||||
; every other bar does.
|
||||
Empty:
|
||||
|
||||
Reference in New Issue
Block a user