From 2808688fa1dd2ea4b1ad3ce324a39023dff55e18 Mon Sep 17 00:00:00 2001 From: Anachronaut Date: Sat, 5 Sep 2026 21:11:36 -0400 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW --- .gitignore | 2 + Programs/Tunes/two.tune.txt | 24 ++ README.md | 40 ++- SoundPatch | Bin 24824 -> 0 bytes Source/Patch/SoundPatch.c | 45 +++- Source/Tune/TuneC.c | 513 ++++++++++++++++++++++++++++++++++++ SplitBit Test Manual.md | 8 +- Tests/makedisks.sh | 23 ++ Tests/sound.sh | 49 ++++ makefile | 21 +- 10 files changed, 713 insertions(+), 12 deletions(-) create mode 100644 Programs/Tunes/two.tune.txt delete mode 100755 SoundPatch create mode 100644 Source/Tune/TuneC.c diff --git a/.gitignore b/.gitignore index 117985d..3e97566 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ /SplitDisk /SplitLint /Voyager +/SoundPatch +/TuneC /CLAUDE.md /claudeResume.sh /codexResume.sh diff --git a/Programs/Tunes/two.tune.txt b/Programs/Tunes/two.tune.txt new file mode 100644 index 0000000..f966407 --- /dev/null +++ b/Programs/Tunes/two.tune.txt @@ -0,0 +1,24 @@ +; two.tune.txt +; The smallest thing that is a tune: one voice, two sequences, and an instrument change. +; Written by Anachronaut +; +; BOTH NOTES ARE NUMBER 60. The octave between them is the #Use in the second sequence, which +; loads the other patch - so what this plays says the whole path works: a header, a tick taken +; from it, two tables relocated, an order list followed, and a patch out of the file. + +#Tick 0d125000 ; A sixteenth note at 120 beats a minute. + +#Patch Low low.patch +#Patch High high.patch + +#Voice 0d0 Low ; What voice nought starts on. The other three have no part. + +#Sequence Plain + 0d60 0d4 + +#Sequence Octave + #Use High ; A command, which takes no tick at all. + 0d60 0d4 + +#Order 0d0 + Plain Octave diff --git a/README.md b/README.md index f42ce24..4677a27 100644 --- a/README.md +++ b/README.md @@ -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 ] +``` + +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