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
@@ -32,7 +32,7 @@ vars.script 50
|
||||
blocks.script 343
|
||||
loops.script 272
|
||||
tune.sbx 318
|
||||
Play.sbx 740
|
||||
Play.sbx 892
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
|
||||
@@ -22,7 +22,7 @@ vars.script 50
|
||||
blocks.script 343
|
||||
loops.script 272
|
||||
tune.sbx 318
|
||||
Play.sbx 740
|
||||
Play.sbx 892
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
|
||||
@@ -39,7 +39,7 @@ vars.script 50
|
||||
blocks.script 343
|
||||
loops.script 272
|
||||
tune.sbx 318
|
||||
Play.sbx 740
|
||||
Play.sbx 892
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
|
||||
@@ -29,7 +29,7 @@ vars.script 50
|
||||
blocks.script 343
|
||||
loops.script 272
|
||||
tune.sbx 318
|
||||
Play.sbx 740
|
||||
Play.sbx 892
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
|
||||
@@ -29,7 +29,7 @@ vars.script 50
|
||||
blocks.script 343
|
||||
loops.script 272
|
||||
tune.sbx 318
|
||||
Play.sbx 740
|
||||
Play.sbx 892
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
|
||||
@@ -115,7 +115,7 @@ vars.script 50
|
||||
blocks.script 343
|
||||
loops.script 272
|
||||
tune.sbx 318
|
||||
Play.sbx 740
|
||||
Play.sbx 892
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
|
||||
@@ -34,7 +34,7 @@ vars.script 50
|
||||
blocks.script 343
|
||||
loops.script 272
|
||||
tune.sbx 318
|
||||
Play.sbx 740
|
||||
Play.sbx 892
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
|
||||
@@ -22,7 +22,7 @@ vars.script 50
|
||||
blocks.script 343
|
||||
loops.script 272
|
||||
tune.sbx 318
|
||||
Play.sbx 740
|
||||
Play.sbx 892
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
|
||||
@@ -20,7 +20,7 @@ vars.script 50
|
||||
blocks.script 343
|
||||
loops.script 272
|
||||
tune.sbx 318
|
||||
Play.sbx 740
|
||||
Play.sbx 892
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
|
||||
@@ -29,7 +29,7 @@ vars.script 50
|
||||
blocks.script 343
|
||||
loops.script 272
|
||||
tune.sbx 318
|
||||
Play.sbx 740
|
||||
Play.sbx 892
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
|
||||
+2
-1
@@ -182,7 +182,8 @@ printf '#! script\nfor a in 1 2\n for b in x y\n echo $a$b\n end\nend\nset
|
||||
"$TOOL" put "$DISKS/cosmos.img" "$WORK/tune.sbx" >/dev/null
|
||||
# And Play, which is the same beat carrying four voices instead of one - each with an
|
||||
# instrument of its own out of Sounds, which is what four patches at once looks like.
|
||||
"$ROOT/Assembler" -I "$ROOT/Programs/Sounds" -I "$ROOT/Programs/CosmOS/Source" \
|
||||
"$ROOT/Assembler" -I "$ROOT/Programs/Sounds" -I "$ROOT/Programs/Libraries" \
|
||||
-I "$ROOT/Programs/CosmOS/Source" \
|
||||
"$ROOT/Programs/CosmOS/Apps/Play.asm" -o "$WORK/Play.sbx" >/dev/null
|
||||
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Play.sbx" >/dev/null
|
||||
printf 'this is not a program' > notes.txt
|
||||
|
||||
+25
-5
@@ -94,7 +94,8 @@ run() {
|
||||
# run <name> <cycles>
|
||||
local name="$1"
|
||||
cat > "$BUILD/$name.asm"
|
||||
"$ASM" "$BUILD/$name.asm" -o "$BUILD/$name.bin" >"$BUILD/$name.log" 2>&1 || {
|
||||
"$ASM" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/Sounds" \
|
||||
"$BUILD/$name.asm" -o "$BUILD/$name.bin" >"$BUILD/$name.log" 2>&1 || {
|
||||
echo "could not assemble $name"; sed 's/^/ /' "$BUILD/$name.log"; return 1; }
|
||||
timeout 20 "$EMU" --fast --cycles "$2" --sound "$BUILD/$name.raw" "$BUILD/$name.bin" \
|
||||
> "$BUILD/$name.out" 2>&1
|
||||
@@ -659,12 +660,12 @@ near "$EIGHTH" 523.3 2 \
|
||||
# still fading into the second voice's stretch, and the window that should have read one note
|
||||
# read 634 hertz - which is not a note, not an octave, and was measured before it was written
|
||||
# down as an assertion.
|
||||
run fourVoices 1700000 < "$ROOT/Programs/testPrograms/fourVoiceTest.asm" || exit 1
|
||||
run fourVoices 2200000 < "$ROOT/Programs/testPrograms/fourVoiceTest.asm" || exit 1
|
||||
|
||||
COUNT="$(measure fourVoices count)"
|
||||
near "$COUNT" 72000 0.1 \
|
||||
&& result ok "twelve ticks of four voices" "$COUNT samples, and 12 x 125,000 cycles is 72,000" \
|
||||
|| result no "twelve ticks of four voices" "$COUNT samples, wanted about 72,000"
|
||||
near "$COUNT" 96000 0.1 \
|
||||
&& result ok "sixteen ticks of four voices" "$COUNT samples, and 16 x 125,000 cycles is 96,000" \
|
||||
|| result no "sixteen ticks of four voices" "$COUNT samples, wanted about 96,000"
|
||||
|
||||
FIRST="$(measure fourVoices pitch 2000 22000)"
|
||||
near "$FIRST" 261.6 2 \
|
||||
@@ -690,6 +691,25 @@ near "$RATIO" 2.0 3 \
|
||||
&& result ok "a patch belongs to its channel" "both tracks say note 60 and one sounds $RATIO times the other" \
|
||||
|| result no "a patch belongs to its channel" "the two voices are $RATIO apart, and their patches say two"
|
||||
|
||||
# ---- A voice goes on to the next sequence its order list names ----
|
||||
#
|
||||
# Sixteen ticks of four bars, and one phrase does nearly all of it: Beat is placed twice by
|
||||
# voice 0 and twice by voice 1, written once, on two different voices. A voice is a property
|
||||
# of where a sequence is PLACED and not of the sequence, which is what a per-voice order list
|
||||
# buys and what a sequence carrying its own channels would have cost.
|
||||
#
|
||||
# The three windows above already ran through it - each of them is a different bar of an order
|
||||
# list rather than a stretch of one long track - so what is left to check is the command.
|
||||
#
|
||||
# THE FOURTH BAR IS THE SAME NOTE NUMBER AS THE FIRST. Voice 0's last sequence begins with
|
||||
# 0x80, which changes its patch mid-piece, and the octave that comes out is the new patch's
|
||||
# and not the note's. A command takes no tick: the reader acts on it and reads the note behind
|
||||
# it on the same boundary.
|
||||
SWITCHED="$(measure fourVoices pitch 74000 94000)"
|
||||
near "$SWITCHED" 523.3 2 \
|
||||
&& result ok "a command changes the instrument mid-piece" "$SWITCHED hertz from note 60, which is the patch and not the note" \
|
||||
|| result no "a command changes the instrument mid-piece" "$SWITCHED hertz, wanted the octave the new patch gives"
|
||||
|
||||
# Both voices sounding is louder than either of them alone. Against the QUIETER of the two so
|
||||
# that the check cannot be passed by one voice getting louder, and by a margin well under the
|
||||
# two-fold a clean sum would give, because two notes are not in phase and do not simply add.
|
||||
|
||||
Reference in New Issue
Block a user