Files
SplitBit-Emulator/Programs/testPrograms/fourVoiceTest.asm
T
AnachronautandClaude Opus 5 8029063bdc 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
2026-09-05 19:54:38 -04:00

155 lines
5.0 KiB
NASM

; Four voices on one tick, staggered so that each can be heard on its own.
;
; M2 of the music player. What is being checked is that the voices share the tick and NOTHING
; ELSE: each keeps its own place in its own track and its own count of how long the note it is
; holding lasts, so one can rest while another plays.
;
; Twelve ticks of 125,000 cycles, which is a sixteenth note at 120 beats a minute:
;
; ticks 0-3 voice 0 plays middle C voice 1 rests
; ticks 4-7 voice 0 rests voice 1 plays the C above it
; ticks 8-11 voice 0 plays middle C voice 1 plays the C above it
;
; BOTH VOICES ARE ASKED FOR NOTE 60. The octave between them is in their patches, which is
; M3: a patch belongs to its channel, so four of them can be up at once. Under a device where
; it did not, the second patch loaded would own both voices and they would sound in unison.
;
; So the first two windows have one voice in them and can be measured for pitch, and the third
; has both and can be measured for level. Two voices sounding at once cannot be asked their
; pitch - a crossing counter given two notes answers with neither.
;
; THIS IS DELIBERATELY NOT Apps/Play.asm. It shares the design and not the code, and it is
; smaller: no track ever ends here, so there is no live flag and no count of what is still
; playing. What it has is the part worth pinning - a cursor and a countdown per voice, moved
; with LDD and STD.
;
; 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
; 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
INIA 0x60
OUTA 0x46
; 125,000 cycles a tick. The period before the control byte, because writing control with
; the run bit set is what loads it.
INIA 0x01
OUTA 0x52
INIA 0xE8
OUTA 0x53
INIA 0x48
OUTA 0x54
INIA 0x07
OUTA 0x51
SIF
; Sixteen ticks. B survives the calls below, because a CALL saves it.
INIB 0d16
tickLoop:
SETD.1 Voice0
RSTA
CALL stepVoice
SETD.1 Voice1
INIA 0d1
CALL stepVoice
DECB
BRB lastTick
WAIT
BRI tickLoop
lastTick:
; 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
done:
CIF
RSTA
OUTA 0x51
HALT
tick:
RETI
#Data
; Middle C, then out of the way, then back - against the octave above doing the opposite.
; ---- 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
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
; but a patch of their own could do.
PatchLow:
0d8
0x00 0d2 ; Saw
0x01 0xFF ; at full gain
0x05 0d1 ; and switched on.
0x20 0d0 ; No attack
0x21 0d0 ; no decay
0x22 0xFF ; and held at full, so a note lasts exactly its ticks
0x23 0d5 ; and goes quickly when the gate drops, which the windows below
; depend on: a note still fading into the next voice's stretch is
; a second pitch in it, and a crossing counter given two answers
; with neither.
0x04 0d128 ; Octave, centred.
PatchHigh:
0d8
0x00 0d2
0x01 0xFF
0x05 0d1
0x20 0d0
0x21 0d0
0x22 0xFF
0x23 0d5
0x04 0d129 ; And this one an octave up.
#Include player.asm
#Vectors
Boot start
Device 0x50 tick