cdee9acfae0bbb674c6ce811ed79bfb059032a85
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d361ea1e46 |
An LFO belongs to its channel, not to the whole device
The two LFOs lived in the Synth, so four channels shared them and whichever patch loaded last owned them for every voice at once. A sound with its LFO switched off silenced the trill under a sound that was still playing - which is what made Lunar Porter's low fuel warning intermittent: the first landing, docking or crash of a run took its trill away, and it was right again next time the machine started. The engine fix went upstream to soundThing and has come back. synth.c and synth.h are re-vendored at 71e3cb2, character for character bar the ASCII transliteration, and now carry two changes: the LFOs moved into the Voice, and synthSyncVoices carries a free LFO's cycle down alongside its rate. That second hunk does nothing here - it only matters to a caller that syncs voices, and this device never does, because syncing would flatten four channels into one instrument. It is taken so the vendored file stays identical in both trees, and it is commented as such. Upstream also found a bug in the original patch, in patchLoad, which is soundThing's own file and does not travel. Downstream the LFO parameter groups 0x60 and 0x70 now read the selected channel like every parameter beside them, so an LFO written to one channel is inaudible on the other three. Everything else about the device is unchanged. Lunar Porter keeps loading each patch immediately before its note, but for the smaller reason that now applies: the bang and the latch share channel three, and a channel used by two sounds has to be told which of them it is about to be. The comment that said otherwise, and the manual's warning about sharing, are rewritten as history rather than as a caveat. Tests/sound.sh's shared-LFO check is inverted to assert the fixed behaviour, with a third leg added: after proving another channel's patch leaves this one alone, it switches this channel's OWN LFO off and requires the pitch to move. Without that, both checks would pass on a device where writing an LFO did nothing at all. Routing either group back to voice 0 is caught. Cost, measured: four channels sounding continuously for 400 seconds of audio takes 5.0 s of wall clock against 4.59 s before, about 9% of total emulator time. Half of that is wasted on voices that cannot sound, since VOICE_COUNT is 8 and there are four channels; recovering it would mean diverging the vendored file, which is not worth it at this price. |
||
|
|
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. |
||
|
|
d388cd3122 |
Give the machine a sound device
Four channels on ports 0x40 to 0x4F, each one a whole soundThing voice:
two oscillators, two envelopes, a filter and the routing between them. A
channel keeps its patch between notes, so a program sets an instrument up
once and then plays it.
Six ports rather than forty, because a voice has around forty settings and
four of them would spend more than half the port space on one device.
There is a selector and a value instead: say which channel, say which
setting, write it. That is three writes to change a setting and two to
play a note, which is the right way round - patches are loaded, notes are
played in an inner loop.
Samples come from the machine's clock and not the host's: 48,000 a second
of emulated time, worked out in whole numbers so it never drifts. A
million cycles is exactly 48,000 samples on any host at any speed, which
is what makes a sound something a test can compare. --sound writes them
out, the way --screen writes a picture, for the same reason: the suite has
no speaker.
Tests/sound.sh is 22 checks and found three real defects the first time it
ran, all the same shape - a synthesizer written for a patch editor, wired
up as hardware and inheriting the editor's assumptions:
- Only one voice had an oscillator switched on, so three of the four
channels could not make a sound whatever was written to them.
- That voice's oscillator arrived at full gain and every other one
arrived at nothing, an asymmetry with no reason behind it.
- A note with no sustain is silent but not over, so the obvious way to
wait for a sound to finish waits for ever.
The first two are fixed by the device defining its own power-on state
rather than inheriting synthInit's: every channel arrives able to make a
sound, so writing a note number is the whole of playing a note. The third
was already written into the manual as advice, an hour before the check
existed. The check disagreed with the documentation and the check was
right; the manual now says the one rule, which is that a note sounds until
the gate is dropped.
Programs/Examples/tune.asm plays eight notes, taking its tempo from the
screen's frame interrupt because that is the only regular beat this
machine has. It spends 99.8% of its cycles asleep in WAIT.
Voyager has no speaker yet - this is the device and its tests. Playing the
samples out of the window is the next commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
|