TuneC: a written tune becomes the bytes the player reads
The compiler, and the last thing the ladder was waiting for. A tune names
its instruments, writes sequences of notes and durations, and gives each
voice an order list of sequence names - which is where repetition comes
from, since a phrase played four times is written once and named four
times.
#Tick 0d125000
#Patch Oboe oboe.patch
#Voice 0d0 Oboe
#Sequence Verse
0d64 0d4 0d67 0d4 0d72 0d8
#Order 0d0
Verse Verse Ending
"#" is a directive and ";" is a comment, exactly as in SplitBit assembly
and in the shell's scripts, and numbers are written the way the assembler
writes them. One rule across the machine rather than a third dialect -
and the rule earned itself immediately: the first tune I wrote said
"#Voice 0" and was refused, correctly, for a bare number.
WHAT IT REFUSES IS EVERYTHING THE PLAYER CANNOT NOTICE. The machine has
no names, so it cannot say a sequence does not exist. It has no lengths,
so it cannot say the voices will come apart four bars after the mistake.
A duration of nought is counted down to 255 and held, which sounds like a
hang rather than an error. And by the time a tune is loaded, "no starting
instrument" and "instrument nought" are the same byte - so the user's
ruling, that a voice with a part and no instrument is an error, can only
be kept here.
SoundPatch gains --blob, writing the same table as raw bytes. It stays
the only thing that reads soundThing's JSON: a second program parsing
that format is a second opinion about what a patch means, and the seam
between two opinions is where the LFO bug lived for a fortnight. Patches
are found beside the tune and then on a -I path, the way an include is.
THE TEST IS THAT TWO IMPLEMENTATIONS AGREE. maketune.py lays the fixture
out by hand and TuneC compiles a written source, and the suite checks
they match byte for byte - the discipline SplitDisk and sbfs.asm are held
to, for the same reason: either alone is only self-consistent. The
fixture predates the compiler, so this is also TuneC checked against
something written before it existed. Four more checks cover the four
refusals.
Also: the SoundPatch binary was tracked, alone among the six tools, and
.gitignore lists every other one. Untracked, and TuneC added beside it.
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
bfc46d982e
commit
2808688fa1
@@ -25,6 +25,7 @@ wrote Asm.sbx: program 7533, data 4099, labels 555
|
||||
| [`Source/DiskTool`](Source/DiskTool) | SplitDisk, which reads and writes SplitBit's filesystem |
|
||||
| [`Source/Linter`](Source/Linter) | SplitLint, which points out needlessly long assembly forms |
|
||||
| [`Source/Patch`](Source/Patch) | SoundPatch, which turns a soundThing patch into a table the sound device takes |
|
||||
| [`Source/Tune`](Source/Tune) | TuneC, which turns a written tune into the bytes the music player reads |
|
||||
| [`Programs/Examples`](Programs/Examples) | Programs to read: hello, a calculator, Fibonacci, a prime sieve, Life, the colours |
|
||||
| [`Programs/Libraries`](Programs/Libraries) | Code included by name rather than linked, since there is no linker |
|
||||
| [`Programs/Sounds`](Programs/Sounds) | Patches as soundThing saved them, and the tables SoundPatch made from them, both checked in so the build never needs soundThing |
|
||||
@@ -116,7 +117,7 @@ Manual.
|
||||
|
||||
## Getting Started:
|
||||
|
||||
Clone it and build the five tools. You need gcc and make, or similar:
|
||||
Clone it and build the six tools. You need gcc and make, or similar:
|
||||
|
||||
```
|
||||
git clone https://github.com/RealBusinessAccount/SplitBit-Emulator.git
|
||||
@@ -306,10 +307,43 @@ this is what turns such a count into a list of routine names.
|
||||
|
||||
Without `-o` the output takes the source file's name, in the directory you called the assembler from, with the extension the format asks for: `.bin` for a boot image and `.sbx` for a loadable program. Included files are looked for beside the file that includes them, and then along the directories given with `-I`.
|
||||
|
||||
## Writing A Tune: TuneC
|
||||
|
||||
```
|
||||
./TuneC [-I <dir>] <tune> <output.tune>
|
||||
```
|
||||
|
||||
Four voices on one clock. A tune names its instruments, writes sequences of notes and durations, and gives each voice an order list of sequence names - which is where repetition comes from, since a phrase played four times is written once and named four times.
|
||||
|
||||
```
|
||||
; Four bars, and the bass is the same one under two of them.
|
||||
#Tick 0d125000 ; cycles a tick: a sixteenth note at 120 beats a minute
|
||||
#Patch Oboe oboe.patch
|
||||
#Patch Strings strings.patch
|
||||
#Voice 0d0 Oboe ; which instrument each voice starts on
|
||||
|
||||
#Sequence Verse
|
||||
0d64 0d4 0d67 0d4 0d72 0d8
|
||||
|
||||
#Sequence Ending
|
||||
#Use Strings ; a command, which takes no time at all
|
||||
0d72 0d16
|
||||
|
||||
#Order 0d0
|
||||
Verse Verse Ending
|
||||
```
|
||||
|
||||
`#` is a directive and `;` is a comment, exactly as in SplitBit assembly and in the shell's scripts, and numbers are written the way the assembler writes them - `0d` and `0x`, and no bare numbers. One rule across the machine rather than a third dialect.
|
||||
|
||||
Patches come from `SoundPatch --blob`, which stays the only thing that understands what a soundThing patch means, and are looked for beside the tune and then on the `-I` path the way an include is.
|
||||
|
||||
**What it refuses is everything the player cannot notice for itself.** The machine has no names, so it cannot say a sequence does not exist; it has no lengths, so it cannot say the voices will come apart four bars after the mistake; and a duration of nought is counted down to 255 and held, which sounds like a hang rather than an error. A voice with a part and no instrument is refused too, because by the time a tune is loaded, "never said" and "instrument nought" are the same byte.
|
||||
|
||||
## Making A Patch: SoundPatch
|
||||
|
||||
```
|
||||
./SoundPatch <patch.json> <label> [output.asm]
|
||||
./SoundPatch --blob <patch.json> <name> <output.patch>
|
||||
```
|
||||
|
||||
The sound device is soundThing's voice engine with the editor taken off, so a patch designed
|
||||
@@ -640,7 +674,7 @@ $(BUILD)/%.bin: %.asm
|
||||
make test
|
||||
```
|
||||
|
||||
Builds the five tools, checks they build clean under strict ISO C, and runs nine scripts.
|
||||
Builds the six tools, checks they build clean under strict ISO C, and runs nine scripts.
|
||||
`Tests/run.sh` assembles and runs every program in `Programs/` and compares the results
|
||||
against recorded output; six more ask the questions a recorded file cannot answer. Between
|
||||
them they check the two assemblers against each other byte for byte, the two SBFS
|
||||
@@ -659,7 +693,7 @@ correct.
|
||||
make sanitize
|
||||
```
|
||||
|
||||
Rebuilds all five tools with the address and undefined behaviour sanitizers and runs the
|
||||
Rebuilds all six tools with the address and undefined behaviour sanitizers and runs the
|
||||
whole suite under them. It catches reads and writes past the end of an array, use after
|
||||
free, leaks, and undefined arithmetic, takes about twice as long, and puts the ordinary
|
||||
binaries back when it finishes.
|
||||
|
||||
Reference in New Issue
Block a user