From a8b6b09a59d20de5cd30306b71cd4adcca925f62 Mon Sep 17 00:00:00 2001 From: Anachronaut Date: Fri, 4 Sep 2026 13:00:18 -0400 Subject: [PATCH] SoundPatch: design a sound where it can be heard, then convert it The bang was guessed at directly in bytes and came out as a low gurgle, which is what guessing at bytes gets you: a cutoff of 40 looks small and is 57 Hz, so the filter sweep ended almost shut. Voyager's sound device is soundThing's voice engine with the editor taken off, so a patch designed in soundThing - where there is a screen, a keyboard and a pair of ears - makes the same sound here. What differs is only how it arrives. SoundPatch converts one into the other. Every parameter the device takes is a documented function of a natural value and all of them invert: times are squared into four seconds, cutoff and rate are exponential, depths and detune are centred on 128. It writes a table of a count and that many parameter and value pairs, and playPatch hands it to the device - so every sound after this costs a table and a call rather than forty lines of its own. Two things the conversion has to say out loud. soundThing has no field for the level routing, because there it is always the amplitude envelope, so the table says so explicitly - a channel keeps its patch between notes and a leftover from a previous one would otherwise be carried in. And a field the tool does not recognise STOPS it: a patch format that has moved on would otherwise produce a table that quietly means something else. THE BUILD DOES NOT DEPEND ON IT. soundThing lives in its own repository and is not needed to build anything here; the tables are checked in and the tool is for when a sound is being changed. Lander's crash now plays Kick808 as a stand-in until a bang is designed for it, and the difference is the point: the hand-guessed patch wandered between 1600 and 8200 for eight tenths of a second, and this decays 3492, 2743, 2037, 1515, 1040, 614, 87, nothing. The docs check caught the tool count in two manuals, which is what it is for. --- Programs/CosmOS/Apps/Lander.asm | 114 +++++--------- Programs/Sounds/boom.asm | 55 +++++++ README.md | 34 ++++- SoundPatch | Bin 0 -> 24824 bytes Source/Patch/SoundPatch.c | 226 ++++++++++++++++++++++++++++ SplitBit Test Manual.md | 8 +- Tests/expected/cosmosCrossDisk.out | 2 +- Tests/expected/cosmosDrives.out | 2 +- Tests/expected/cosmosFault.out | 2 +- Tests/expected/cosmosFlip.out | 2 +- Tests/expected/cosmosGrid.out | 2 +- Tests/expected/cosmosMonitor.out | 2 +- Tests/expected/cosmosMonitorRun.out | 2 +- Tests/expected/cosmosRun.out | 2 +- Tests/expected/cosmosSlowDisk.out | 2 +- Tests/expected/cosmosSprite.out | 2 +- Tests/makedisks.sh | 3 +- makefile | 26 +++- 18 files changed, 385 insertions(+), 101 deletions(-) create mode 100644 Programs/Sounds/boom.asm create mode 100755 SoundPatch create mode 100644 Source/Patch/SoundPatch.c diff --git a/Programs/CosmOS/Apps/Lander.asm b/Programs/CosmOS/Apps/Lander.asm index c3c8938..aba3f79 100644 --- a/Programs/CosmOS/Apps/Lander.asm +++ b/Programs/CosmOS/Apps/Lander.asm @@ -2661,10 +2661,18 @@ hideSprite: ; ---- The bang, set up once and played in two writes ---- ; -; A patch is twenty odd writes and a note is two, which is the whole point of the selector and +; A patch is forty odd writes and a note is two, which is the whole point of the selector and ; value registers - so the instrument is built at startup like the tiles are, and a crash only ; has to say "this channel, this note". ; +; ---- And the numbers came out of soundThing ---- +; +; The first version of this was guessed at in bytes, and a byte is not seconds or hertz: a +; cutoff of 40 looked small and is 57 Hz, which made the bang a low gurgle. Voyager's sound +; device is soundThing's voice engine with the editor taken off, so a patch designed there - +; where it can be HEARD - means the same thing here. SoundPatch converts one into the table +; below. Change the sound in soundThing and convert it again; do not edit the numbers. +; ; ---- Channel three, counting down ---- ; ; Effects take the top channel and work downwards, so music, if it ever arrives, can take @@ -2673,87 +2681,31 @@ hideSprite: putBoom: INIA 0x03 OUTA 0x41 ; Channel three. Every parameter write below lands on it. + SETD.0 BoomPatch + CALL playPatch + RET - ; ---- Noise, which is what an explosion is ---- - ; - ; Every other waveform here has a pitch, and a pitched bang is a note. Writing a port leaves - ; A alone, so the three that selected the channel would select parameter three; the wave is - ; parameter nought and has to be said. - RSTA +; ---- A whole patch, out of a table ---- +; +; DP0 names a count and then that many parameter and value pairs. Every sound after this one +; costs a table and a call rather than forty lines of its own. +playPatch: + LDA.0 + SETD.1 PatchLeft + STA.1 + INCD.0 +playPatchOne: + LDA.0 OUTA 0x42 - INIA 0d5 ; Noise. - OUTA 0x43 - INIA 0x01 ; Oscillator nought, gain. - OUTA 0x42 - INIA 0xFF - OUTA 0x43 - - ; ---- Instant, then a long way down ---- - ; - ; No attack at all, because a bang that fades in is not a bang. A sustain of nothing does NOT - ; end the note - a voice holding at nothing is what a held key is - so the gate still has to - ; be dropped afterwards, which the explosion does once its pieces have gone. - INIA 0x20 ; Amplitude envelope, attack. - OUTA 0x42 - RSTA - OUTA 0x43 - INIA 0x21 ; ---- Decay: about half a second ---- - OUTA 0x42 ; - ; A byte is not seconds. It is squared and scaled to four of them, - ; so 200 is two and a half - which over the eight tenths of a - ; second the pieces are in the air is not a bang fading, it is the - ; first third of one, and it read as a flat wash of noise. - INIA 0d90 - OUTA 0x43 - INIA 0x22 ; Sustain: nothing. - OUTA 0x42 - RSTA - OUTA 0x43 - INIA 0x23 ; Release. - OUTA 0x42 - INIA 0d40 - OUTA 0x43 - - ; ---- And a filter closing, which is what makes it a boom and not a hiss ---- - ; - ; Noise on its own is a cymbal. The modulation envelope shuts the low pass over the same - ; moment the level falls, so the bright part is only at the front of it. - INIA 0x40 ; Filter, on. - OUTA 0x42 - INIA 0x01 - OUTA 0x43 - INIA 0x41 ; Type: low pass. - OUTA 0x42 - RSTA - OUTA 0x43 - INIA 0x42 ; Cutoff, low, which is where it ends up. - OUTA 0x42 - INIA 0d40 - OUTA 0x43 - INIA 0x43 ; A little resonance, so the sweep can be heard. - OUTA 0x42 - INIA 0d120 - OUTA 0x43 - INIA 0x44 ; What opens it: the modulation envelope. - OUTA 0x42 - INIA 0d2 - OUTA 0x43 - INIA 0x45 ; And how far above where it sits. - OUTA 0x42 - INIA 0d200 - OUTA 0x43 - INIA 0x30 ; That envelope: open at once, - OUTA 0x42 - RSTA - OUTA 0x43 - INIA 0x31 ; shut over about a third of a second, - OUTA 0x42 - INIA 0d90 - OUTA 0x43 - INIA 0x32 ; and stay shut. - OUTA 0x42 - RSTA + INCD.0 + LDA.0 OUTA 0x43 + INCD.0 + SETD.1 PatchLeft + LDA.1 + DECA + STA.1 + BNA playPatchOne RET ; Two writes, which is what a note is meant to cost. @@ -3948,6 +3900,8 @@ CopyLeft: 0x00 HideSlot: 0x00 +PatchLeft: + 0x00 ; What a flame is being asked to be, before it is written out. FlameSlot: @@ -4132,3 +4086,5 @@ LanderArtAt: #Reserve 0d2 #Include math.asm + +#Include boom.asm diff --git a/Programs/Sounds/boom.asm b/Programs/Sounds/boom.asm new file mode 100644 index 0000000..c6bd568 --- /dev/null +++ b/Programs/Sounds/boom.asm @@ -0,0 +1,55 @@ +; BoomPatch, converted from External/soundThing/patches/Drums/Kick808.json by SoundPatch. +; Designed in soundThing, where it can be heard. Do not edit the numbers +; here: change the patch and convert it again. + +#Data + +BoomPatch: + 0d47 ; how many pairs follow + 0x50 0x01 ; the level is the amplitude envelope + 0x00 0d0 ; oscillator 0, waveform + 0x01 0d255 ; gain + 0x02 0d128 ; duty + 0x03 0d128 ; detune, in cents + 0x04 0d127 ; octave + 0x05 0d1 ; on + 0x06 0d0 ; what moves its width + 0x07 0d128 ; and how far + 0x08 0d2 ; what moves its pitch + 0x09 0d197 ; and how far + 0x0A 0d0 ; what moves its gain + 0x0B 0d128 ; and how far + 0x10 0d5 ; oscillator 1, waveform + 0x11 0d146 ; gain + 0x12 0d128 ; duty + 0x13 0d128 ; detune, in cents + 0x14 0d128 ; octave + 0x15 0d1 ; on + 0x16 0d0 ; what moves its width + 0x17 0d128 ; and how far + 0x18 0d0 ; what moves its pitch + 0x19 0d128 ; and how far + 0x1A 0d0 ; what moves its gain + 0x1B 0d128 ; and how far + 0x20 0d4 ; amplitude envelope, attack + 0x21 0d79 ; decay + 0x22 0d0 ; sustain + 0x23 0d31 ; release + 0x30 0d4 ; modulation envelope, attack + 0x31 0d35 ; decay + 0x32 0d0 ; sustain + 0x33 0d13 ; release + 0x40 0d1 ; filter, on + 0x41 0d0 ; type: 0 low, 1 high, 2 band + 0x42 0d92 ; cutoff, in hertz + 0x43 0d10 ; resonance + 0x44 0d0 ; what moves the cutoff + 0x45 0d128 ; and how far, in hertz + 0x46 0d0 ; what moves the resonance + 0x47 0d128 ; and how far + 0x60 0d0 ; LFO 0, on + 0x61 0d0 ; waveform + 0x62 0d127 ; rate, in hertz + 0x70 0d0 ; LFO 1, on + 0x71 0d0 ; waveform + 0x72 0d127 ; rate, in hertz diff --git a/README.md b/README.md index 9d3a2a6..20b2630 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,10 @@ wrote Asm.sbx: program 7533, data 4099, labels 555 | [`Source/Assembler`](Source/Assembler) | The assembler that runs on a host | | [`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 | | [`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) | Patch tables, converted from soundThing and checked in so the build never needs it | | [`Programs/Loader`](Programs/Loader) | The standalone loader CosmOS grew out of | | [`Programs/CosmOS`](Programs/CosmOS) | The operating system, its applications, and the native assembler | | [`Programs/testPrograms`](Programs/testPrograms) | What the test suite drives | @@ -114,7 +116,7 @@ Manual. ## Getting Started: -Clone it and build the four tools. You need gcc and make, or similar: +Clone it and build the five tools. You need gcc and make, or similar: ``` git clone https://github.com/RealBusinessAccount/SplitBit-Emulator.git @@ -304,6 +306,32 @@ 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`. +## Making A Patch: SoundPatch + +``` +./SoundPatch