388faafd0415757288828e259b9798cfb7ddbeac
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b2ff8d64e5 |
Fold soundThing's changes back down, and expose the two new switches
The three changes that went up came back as part of soundThing, along with two more that they made possible. The engine here is now b73e5c0 character for character, except that em-dashes and arrows in comments are written as ASCII because this tree is ASCII only - a local rule, not an improvement, and not sent up. So synth.h's "what was changed" list is gone. There is nothing to list: what has to be kept current is only that if either copy changes, the other one has to be told. ---- What came back ---- A VOICE CAN END ITSELF. Naming the level's source said what shapes a voice; nothing said what ends one, so the only thing that could ever finish one was a key coming up. A game is nearly all one-shots and not one of them wants its length decided by how long a note was held. Exposed as parameter 0x51: 0 gated, 1 triggered. AND A ONE-SHOT IS THE SAME ONE-SHOT TWICE. A triggered voice re-arms its oscillators, and an LFO can be told to start over with each voice - parameter 3 of either LFO. Both halves are needed and the check proves it: with the LFO left free, two triggered hits still differ. Their note warned that whatever applies a patch to a channel has to set these or they hold synthInit's defaults. Checked: Voyager never calls synthSyncVoices, so their 0001 is a no-op here as they predicted, and nothing reaches into an LFO's phase, so the struct split is safe. ---- What it is for ---- Lander's crash is a triggered voice now, so boomOff is gone. Nothing has to remember to end a bang. SoundPatch learnt voice_levelSource, voice_gate and lfo<N>_mode, which the new soundThing writes - without that it would have refused every patch saved from it, since an unknown field stops the tool on purpose. A patch from before those fields still converts, and says in its own comments that it predates the level routing. Three checks, each seen to fail on its own break: a gated voice still sounding with nothing holding it, a triggered one down to nothing with no gate ever dropped, and two hits identical sample for sample. One test bug worth keeping: the first version of the repeatability check struck the second note while the first was still ringing, so what it found and compared as "the second hit" was a point in the middle of the first one's tail. It now looks for sound after SILENCE rather than sound after an offset. |
||
|
|
f58b0f93af |
Vendor the voice engine, with the level it always had and could not say
soundThing's synth.c and synth.h, and nothing else of it: the voice engine pulls in maths, stdlib, stdint and stdio and knows nothing about Raylib, MIDI, patches or the interface, which is what made it liftable. Copied rather than submoduled - two files against tying this build to another repository's history is not a close call - so the header carries the whole of the difference and is meant to stay current. A VOICE'S LEVEL IS A ROUTING NOW. Envelope 0 was multiplied into the output unconditionally, so routing it to a filter or an oscillator meant it shaped the volume as well whether that was wanted or not, which is most of the trouble with making percussion. There was no flag to turn that off and there never had been: Envelope is the only one of the four modulating things without an active flag, where LFO, Filter and Oscillator all have one. It could not take the filter's shape either. filterTick returning its input unchanged is the right nothing-happens answer, but an envelope returning a constant would be useless as a mod source - and being a mod source while NOT being the volume is exactly the thing wanted. So the voice names the source that shapes its level, the way every other destination names its source, and MOD_SOURCE_NONE means nothing shapes it. Two things fall out that were not possible before: envelope 1 shaping the volume, and an LFO doing it, which is tremolo. Envelope 0 still decides how long a voice lasts, because it still ticks. A voice with its level unrouted will therefore cut rather than fade when the envelope reaches idle - fine or wanted for percussion, a click if the oscillators are loud at that moment, and a one line change to end on note-off instead if it turns out to matter. NOISE COMES FROM A SEEDED GENERATOR, one to each thing that makes it. rand() is global state shared with the whole process and varies between C libraries, so the same program sounded different on different machines and no recorded result could have meant anything. One generator each rather than one shared, because two noise sources drawing the same stream are not two noise sources. Checked rather than assumed: two synths from the same start produce identical samples, and 4,617 of 4,800 of them are non-zero, so it is noise and not silence. AND A CHANNEL IS THE CHANNEL YOU ASKED FOR. synthNoteOn hunts for a free voice and steals round-robin, which is what a keyboard wants and what the standalone synthesizer still does. Channel two is channel two. Both old calls are untouched. Nothing links it yet. It compiles clean and standalone under -std=c11 -pedantic, which is what make strict already checks, and the 169 tests are unmoved because nothing calls it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW |