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.
This commit is contained in:
Anachronaut
2026-09-04 22:20:26 -04:00
parent 3ca5f193e6
commit d361ea1e46
7 changed files with 184 additions and 104 deletions
+20 -15
View File
@@ -1075,9 +1075,9 @@ Anywhere a setting asks *what modulates* something, the answer is one of these:
| 3 | LFO 0. |
| 4 | LFO 1. |
**The two LFOs belong to the device and not to a channel**, so writing 0x60 to 0x7F ignores
whichever channel is selected. That is what makes them useful: a vibrato that every voice
shares is one wobble rather than four that drift apart.
**Each channel has its own pair of LFOs**, so writing 0x60 to 0x7F sets them on whichever
channel is selected, exactly like every parameter above. Four channels are four independent
instruments, and an LFO is part of how an instrument sounds.
### What A Byte Means:
@@ -1148,25 +1148,30 @@ The consequence is worth knowing rather than fixing: a retriggered noise source
repeatable, so every hit is literally the same noise, the way a sampler is. On a hi-hat that
can read as machine-gunny. Where variation is wanted, leave that LFO or that voice free.
### A Warning About Sharing Them:
### They Used To Be Shared, And The Fault That Caused:
**An LFO belongs to the device and not to a channel.** There are two of them against four
voices, so setting one from a patch meant for channel three changes what channel nought hears,
immediately and for as long as nothing sets it back.
Worth knowing, because it is the shape of bug this device is built to avoid and the manual
said the opposite for a while.
That matters because a patch naturally carries LFO settings along with everything else - it is
one instrument, and its LFO is part of how it sounds. A program that loads its instruments once
at startup therefore ends up with whichever of them was written *last*, for all of them.
The two LFOs used to belong to the **device**, so setting one from a patch meant for channel
three changed what channel nought heard, immediately and for as long as nothing set it back.
A patch naturally carries LFO settings along with everything else, so a program that loaded
its instruments once at startup ended up with whichever of them was written *last*, for all
of them.
The failure this produces is unpleasant to diagnose, because it is intermittent by nature: a
The failure that produces is unpleasant to diagnose, because it is intermittent by nature: a
sound is correct until some unrelated thing plays, and correct again next time the machine
starts. A game here lost a warning's trill after the first landing of each run, because the
landing's patch happened to carry an LFO that was switched off.
**Load a patch immediately before the note that needs it.** A patch is forty-odd writes and
this costs nothing at a moment that is already making a sound. What it cannot fix is two
sounds overlapping - they still share the LFOs, and with two between four voices that is in
the nature of the device.
A program could work around the first half of that by loading each patch immediately before
the note that needed it. It could not work around the second: two sounds **overlapping** still
interfered, and no amount of care in the program could separate them. So the device was
changed rather than the programs, and an LFO now belongs to the channel it was written to.
**Loading a patch immediately before its note is still good practice**, for the different
reason that a channel used by two sounds has to be told which of them it is about to be. A
patch is forty-odd writes and costs nothing at a moment already making a sound.
### Knowing When It Has Finished: