Sequences, order lists, and a command that changes the instrument
The rung between M3 and M4, and the point of doing it before the format: the engine learns the tracker's model with the tune still assembled in, so M4 becomes serialising a thing that exists rather than designing a thing that does not. A voice no longer walks one long track. 0xFF now means THIS SEQUENCE ended, and the voice takes the next address from an order list of its own. That is where repetition comes from, and it costs no notation: the bass plays the same sequence in the first bar and the last and it is written once. Per voice rather than one shared table of four-column rows, because a voice's order cursor is then a pointer it advances by itself - the same LDD and STD move everything else here makes. Four columns is how it reads, not how it is stored. Sequences also carry COMMANDS, which take no tick: the reader acts and reads the next event on the same boundary. One is defined, 0x80, which plays the rest of that voice on another patch, and the other 125 values are left alone. A patch change reshapes whatever is still ringing on the voice and nothing can be done about that - a channel has one set of parameters and a note in its release is using them - so it is a fact about the hardware and the cure is a rest, which is the composer's. The engine moved to Libraries/player.asm rather than being copied into the test a second time, now that it is big enough to drift. Play supplies the tune and the beat; the library supplies the scheduler, the patch loader and a voice's state. Verified by rendering: bar for bar identical across the move. AND THE TEST FOUND A REAL FLAW IN THE FORMAT, which is the whole argument for building the reader first. The order list ended with 0x0000, on the reasoning that no sequence could live below the 0x3000 this program is based at. True of a loaded program, false of a boot image whose data starts at zero - so the first test written against it read its own first sequence as the end of the list and played nothing at all. It is 0xFFFF now, which mirrors the 0xFF ending a sequence and is impossible everywhere: a sequence at 0xFF00 or above has fewer bytes left than it needs. An address is a poor place to hide a flag unless the address is impossible in every program, not just this one. One order list in the test now really ends, because otherwise nothing reached the terminator at all: every voice sat on a long rest and the break went unnoticed. With it, breaking the test reads garbage past the end and the counter sums two notes at 781 hertz. 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
f9b08cf7f9
commit
8029063bdc
@@ -26,6 +26,7 @@
|
||||
; Written by Anachronaut
|
||||
|
||||
#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
|
||||
@@ -53,8 +54,8 @@ start:
|
||||
OUTA 0x51
|
||||
SIF
|
||||
|
||||
; Twelve ticks. B survives the calls below, because a CALL saves it.
|
||||
INIB 0d12
|
||||
; Sixteen ticks. B survives the calls below, because a CALL saves it.
|
||||
INIB 0d16
|
||||
tickLoop:
|
||||
SETD.1 Voice0
|
||||
RSTA
|
||||
@@ -68,7 +69,7 @@ tickLoop:
|
||||
BRI tickLoop
|
||||
|
||||
lastTick:
|
||||
; The twelfth step started a note and nothing has waited for it yet. Without this the last
|
||||
; The last step started a note and nothing has waited for it yet. Without this the final
|
||||
; tick is never rendered, and a window measuring it runs off the end of the samples.
|
||||
WAIT
|
||||
|
||||
@@ -78,86 +79,46 @@ done:
|
||||
OUTA 0x51
|
||||
HALT
|
||||
|
||||
; A is the channel and DP1 the voice's four bytes: cursor high, cursor low, count, spare.
|
||||
; Both survive the CALL that got here.
|
||||
stepVoice:
|
||||
SETD.3 ThisChannel
|
||||
STA.3
|
||||
|
||||
DPUP.1 0d2
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1 ; One tick less of whatever is sounding.
|
||||
DPDN.1 0d2
|
||||
BNA stepDone
|
||||
|
||||
; The count ran out. Select the channel first: every sound port writes to whichever channel
|
||||
; was named last, so a voice that forgot would be playing somebody else's part.
|
||||
LDA.3
|
||||
OUTA 0x41
|
||||
RSTA
|
||||
OUTA 0x45 ; Let go of the note that just ended.
|
||||
|
||||
LDD.0.1 ; DP0 is this voice's place in its track.
|
||||
LDA.0
|
||||
INCD.0
|
||||
LDB.0 ; How many ticks it lasts.
|
||||
INCD.0
|
||||
STD.0.1
|
||||
DPUP.1 0d2
|
||||
STB.1
|
||||
|
||||
; A is still the note. Zero is a rest: the ticks pass with nothing started.
|
||||
BRA stepDone
|
||||
OUTA 0x44
|
||||
stepDone:
|
||||
RET
|
||||
|
||||
; ---- A patch, onto the channel named by A ----
|
||||
;
|
||||
; The channel is selected first: every parameter write lands on whichever channel was named
|
||||
; last, so a patch loaded without one would be rewriting somebody else's voice.
|
||||
loadPatch:
|
||||
OUTA 0x41
|
||||
LDB.0
|
||||
INCD.0
|
||||
loadPatchPair:
|
||||
LDA.0
|
||||
OUTA 0x42
|
||||
INCD.0
|
||||
LDA.0
|
||||
OUTA 0x43
|
||||
INCD.0
|
||||
DECB
|
||||
BNB loadPatchPair
|
||||
RET
|
||||
|
||||
tick:
|
||||
RETI
|
||||
|
||||
#Data
|
||||
|
||||
; Cursor high, cursor low, count, spare. A label written here comes out as its address, most
|
||||
; significant first, which is the shape LDD reads - so a voice starts pointed at its track
|
||||
; with no code at all. The count starts at one so the first pass runs it out and fetches.
|
||||
Voice0:
|
||||
Track0 0d1 0d0
|
||||
Voice1:
|
||||
Track1 0d1 0d0
|
||||
ThisChannel:
|
||||
0x00
|
||||
|
||||
; Middle C, then out of the way, then back - against the octave above doing the opposite.
|
||||
Track0:
|
||||
0d60 0d4
|
||||
; ---- The order lists, and what they are for ----
|
||||
;
|
||||
; Four bars of four ticks each, and the SAME TWO SEQUENCES do nearly all of it. Beat is used
|
||||
; twice by voice 0 and twice by voice 1 - four placings of one phrase, written once, and on
|
||||
; two different voices, because the voice is a property of where a sequence is placed and not
|
||||
; of the sequence.
|
||||
;
|
||||
; ticks 0-3 4-7 8-11 12-15
|
||||
; voice 0 Beat Quiet Beat Switch
|
||||
; voice 1 Quiet Beat Beat (its order list ends here)
|
||||
Order0:
|
||||
Beat Quiet Beat Switch Tail 0xFF 0xFF
|
||||
Order1:
|
||||
Quiet Beat Beat 0xFF 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.
|
||||
Order2:
|
||||
Tail 0xFF 0xFF
|
||||
Order3:
|
||||
Tail 0xFF 0xFF
|
||||
|
||||
Beat:
|
||||
0d60 0d4 ; Note 60 on both voices. The octave between them is their patches.
|
||||
0xFF
|
||||
Quiet:
|
||||
0d00 0d4
|
||||
0d60 0d4
|
||||
0d00 0xFF
|
||||
Track1:
|
||||
0d00 0d4
|
||||
0d60 0d4
|
||||
0d60 0d4
|
||||
0d00 0xFF
|
||||
0xFF
|
||||
Switch:
|
||||
0x80 PatchHigh ; And voice 0 takes voice 1's instrument, mid-piece.
|
||||
0d60 0d4 ; The same note number it has played all along.
|
||||
0xFF
|
||||
Tail:
|
||||
0d00 0xFF ; Long enough that nothing runs off the end of its order list.
|
||||
0xFF
|
||||
|
||||
; Seven pairs each, and only the last of them differs: the octave, centred on 128. So the two
|
||||
; voices below are asked for THE SAME NOTE NUMBER and answer an octave apart, which nothing
|
||||
@@ -186,6 +147,8 @@ PatchHigh:
|
||||
0x23 0d5
|
||||
0x04 0d129 ; And this one an octave up.
|
||||
|
||||
#Include player.asm
|
||||
|
||||
#Vectors
|
||||
Boot start
|
||||
Device 0x50 tick
|
||||
|
||||
Reference in New Issue
Block a user