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:
+28
-8
@@ -10,11 +10,11 @@
|
||||
//
|
||||
// It is copied rather than submoduled. Two files against tying this build to another
|
||||
// repository's history is not a close call, and what a copy costs is that changes have to be
|
||||
// carried across on purpose - in BOTH directions, which has now happened once each way.
|
||||
// carried across on purpose - in BOTH directions, which has now happened twice each way.
|
||||
//
|
||||
// ---- What was changed ----
|
||||
//
|
||||
// Nothing. This is soundThing's engine at b73e5c0, character for character, except that
|
||||
// Nothing. This is soundThing's engine at 71e3cb2, character for character, except that
|
||||
// em-dashes and arrows in its comments are written as ASCII here because this tree is ASCII
|
||||
// only. That rule is local and is not an improvement, so it was not sent upstream.
|
||||
//
|
||||
@@ -24,6 +24,13 @@
|
||||
// waiting for a key, and a triggered voice that re-arms its oscillators so a one-shot is the
|
||||
// same one-shot twice. A game is nearly all one-shots, which is why the traffic went that way.
|
||||
//
|
||||
// The second round trip was the LFOs, which belonged to the Synth and so were shared by every
|
||||
// voice at once. That is right for one instrument played polyphonically and wrong for four
|
||||
// channels playing four different things: whichever patch loaded last owned the LFO for all of
|
||||
// them. They now live in the Voice. It went up from here and came back with a bug fixed and
|
||||
// one hunk that does nothing here - synthSyncVoices carrying the free cycle down, which only
|
||||
// matters to a caller that syncs, and this device never does.
|
||||
//
|
||||
// So the thing to keep current is no longer a list. It is this: if either copy changes, the
|
||||
// other one has to be told.
|
||||
//
|
||||
@@ -119,7 +126,7 @@ typedef enum {
|
||||
} LfoMode;
|
||||
|
||||
typedef struct {
|
||||
LfoState run; // the free-running cycle, read by every voice in LFO_FREE
|
||||
LfoState run; // this LFO's own cycle, free-running or reset at note on
|
||||
float rate; // Hz
|
||||
Waveform waveform;
|
||||
int active;
|
||||
@@ -194,10 +201,24 @@ typedef struct {
|
||||
// said what shapes the sound; this says who ends it. Without it a voice can only ever
|
||||
// finish because a key came up, which is no use to a drum.
|
||||
VoiceGate gate;
|
||||
// One cycle per LFO per voice, used only by the LFOs set to LFO_RETRIGGER. Runtime state
|
||||
// like the envelope stages and the filter's integrators, so synthSyncVoices leaves it
|
||||
// alone - it is where this voice is, not what the patch says.
|
||||
LfoState lfoRun[LFO_COUNT];
|
||||
// ---- Its own LFOs, like its own envelopes and its own filter ----
|
||||
//
|
||||
// They used to belong to the Synth, which is right for ONE INSTRUMENT played polyphonically
|
||||
// and wrong for anything else: a patch carries LFO settings, so loading a patch changed
|
||||
// what every other voice heard. That is invisible here, because every voice is given the
|
||||
// same patch - and it is the whole difficulty for anything driving these voices as separate
|
||||
// parts, where two patches disagree about the rate and the last one loaded wins for both.
|
||||
//
|
||||
// A chord is unaffected. synthSyncVoices copies these like everything else, so every voice
|
||||
// runs identical settings, and their cycles are started together in synthInit and advanced
|
||||
// every sample whether the voice is sounding or not - so they do not merely stay close,
|
||||
// they stay bit for bit identical, and one sweep still runs under the whole chord.
|
||||
//
|
||||
// Each LFO's own cycle lives inside it, like the envelope stages and the filter's
|
||||
// integrators, so synthSyncVoices copies the settings and leaves the cycle alone - it is
|
||||
// where this voice is, not what the patch says. LFO_RETRIGGER resets that same cycle at
|
||||
// note on, which is what it always did, only now to a cycle nothing else is listening to.
|
||||
LFO lfos[LFO_COUNT];
|
||||
} Voice;
|
||||
|
||||
typedef struct {
|
||||
@@ -207,7 +228,6 @@ typedef struct {
|
||||
int lastStolenVoice;
|
||||
Voice voices[VOICE_COUNT];
|
||||
float volume; // 0.0 to 1.0, master output level
|
||||
LFO lfos[LFO_COUNT];
|
||||
} Synth;
|
||||
|
||||
// Envelope functions:
|
||||
|
||||
Reference in New Issue
Block a user