Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LFOxEG GATE/GATEENV fixes #953

Merged
merged 1 commit into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/nightlychangelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,7 @@
- The EGxVCA didn't trigger envelopes for newly added polyphony while
the gate was high. This caused polyphony mis-fires on patch startup
in some rare cases. Now it will trigger an envlope for a
newly added channel in a high gate situation.
newly added channel in a high gate situation.
- Rework the LFOxEG gate behavior so the code is less confusing.
As a result, fix a bug where the envelope would mis-trigger when
both GATE and GATEENV were connected.
129 changes: 50 additions & 79 deletions src/LFO.h
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,14 @@ struct LFO : modules::XTModule
{
surge_lfo[i]->assign(storage.get(), lfostorage, storage->getPatch().scenedata[0],
nullptr, surge_ss.get(), surge_ms.get(), surge_fs.get());
isGated[i] = false;
isGateConnected[i] = false;
isTriggered[i] = false;
isTriggeredEnvOnly[i] = false;

gateInputHigh[i] = false;
gateEnvInputHigh[i] = false;
anyGateInputHigh[i] = false;
prevAnyGateInputHigh[i] = false;
gateInputTriggered[i] = false;
gateEnvInputTriggered[i] = false;

priorIntPhase[i] = -1;
endPhaseCountdown[i] = 0;
trigABCountdown[0][i] = 0;
Expand Down Expand Up @@ -354,8 +358,11 @@ struct LFO : modules::XTModule
bool firstProcess{true};

rack::dsp::SchmittTrigger envGateTrigger[MAX_POLY], envGateTriggerEnvOnly[MAX_POLY];
bool isGated[MAX_POLY], isGateConnected[MAX_POLY];
bool isTriggered[MAX_POLY], isTriggeredEnvOnly[MAX_POLY];

bool gateInputHigh[MAX_POLY]{}, gateEnvInputHigh[MAX_POLY]{}, anyGateInputHigh[MAX_POLY]{};
bool prevAnyGateInputHigh[MAX_POLY]{};
bool gateInputTriggered[MAX_POLY]{}, gateEnvInputTriggered[MAX_POLY]{};

int priorIntPhase[MAX_POLY], endPhaseCountdown[MAX_POLY], priorEnvStage[MAX_POLY];
int trigABCountdown[2][MAX_POLY];

Expand Down Expand Up @@ -405,8 +412,8 @@ struct LFO : modules::XTModule
if (tt == FOLLOW_TRIG_POLY &&
(inputs[INPUT_GATE].isConnected() || inputs[INPUT_GATE_ENVONLY].isConnected()))
{
nChan = std::max(1, inputs[INPUT_GATE].getChannels());
nChan = std::max(nChan, inputs[INPUT_GATE_ENVONLY].getChannels());
nChan = std::max(
{1, inputs[INPUT_GATE].getChannels(), inputs[INPUT_GATE_ENVONLY].getChannels()});
}
else
{
Expand All @@ -430,25 +437,37 @@ struct LFO : modules::XTModule
{
if (inputs[INPUT_GATE].isConnected() &&
envGateTrigger[c].process(inputs[INPUT_GATE].getVoltage(c)))
isTriggered[c] = true;
gateInputTriggered[c] = true;
gateInputHigh[c] = inputs[INPUT_GATE].getVoltage(c) > 2;

if (inputs[INPUT_GATE_ENVONLY].isConnected() &&
envGateTriggerEnvOnly[c].process(inputs[INPUT_GATE_ENVONLY].getVoltage(c)))
isTriggeredEnvOnly[c] = true;
gateEnvInputTriggered[c] = true;
gateEnvInputHigh[c] = inputs[INPUT_GATE_ENVONLY].getVoltage(c) > 2;

anyGateInputHigh[c] = gateEnvInputHigh[c] || gateInputHigh[c];
}
}
else
{
// broadcast 0 to the rest
if (inputs[INPUT_GATE].isConnected() &&
envGateTrigger[0].process(inputs[INPUT_GATE].getVoltage(0)))
isTriggered[0] = true;
gateInputTriggered[0] = true;
gateInputHigh[0] = inputs[INPUT_GATE].getVoltage(0) > 2;
if (inputs[INPUT_GATE_ENVONLY].isConnected() &&
envGateTriggerEnvOnly[0].process(inputs[INPUT_GATE_ENVONLY].getVoltage(0)))
isTriggeredEnvOnly[0] = true;
gateEnvInputTriggered[0] = true;
gateEnvInputHigh[0] = inputs[INPUT_GATE].getVoltage(0) > 2;
anyGateInputHigh[0] = gateEnvInputHigh[0] || gateInputHigh[0];

for (int c = 1; c < nChan; ++c)
{
isTriggered[c] = isTriggered[0];
isTriggeredEnvOnly[c] = isTriggeredEnvOnly[0];
gateInputTriggered[c] = gateInputTriggered[0];
gateEnvInputTriggered[c] = gateEnvInputTriggered[0];
gateInputHigh[c] = gateInputHigh[0];
gateEnvInputHigh[c] = gateEnvInputHigh[0];
anyGateInputHigh[c] = anyGateInputHigh[0];
}
}

Expand Down Expand Up @@ -525,75 +544,26 @@ struct LFO : modules::XTModule

lfostorage->rate.deactivated = direct;
bool scaleAmp = params[SCALE_RAW_OUTPUTS].getValue() > 0.5;
bool anyGateConnected =
inputs[INPUT_GATE].isConnected() || inputs[INPUT_GATE_ENVONLY].isConnected();
for (int c = 0; c < nChan; ++c)
{
int trigChan = c;
if (tt == TAKE_CHANNEL_0)
trigChan = 0;

float ampScale[3];
ampScale[0] = 1.f;
ampScale[1] = scaleAmp ? modAssist.values[AMPLITUDE][c] : 1;
ampScale[2] = scaleAmp ? modAssist.values[AMPLITUDE][c] : 1;

bool inNewAttack = firstProcess;
bool inNewEnvAttack = false;
// move this to every sample and record it eliminating the first process thing too
if (isTriggered[c])
{
isGated[c] = true;
inNewAttack = true;
isGateConnected[c] = true;
isTriggered[c] = false;
}
else if (isTriggeredEnvOnly[c])
{
inNewEnvAttack = true;
isTriggeredEnvOnly[c] = false;
isGated[c] = true;
isGateConnected[c] = true;
}
else if (inputs[INPUT_GATE].isConnected() &&
inputs[INPUT_GATE_ENVONLY].isConnected())
{
// HANDLE THIS DUAL CASE
}
else if (inputs[INPUT_GATE].isConnected() && isGated[c] &&
(inputs[INPUT_GATE].getVoltage(trigChan) < 1.f))
{
isGated[c] = false;
surge_lfo[c]->release();
isGateConnected[c] = true;
}
else if (inputs[INPUT_GATE_ENVONLY].isConnected() && isGated[c] &&
(inputs[INPUT_GATE_ENVONLY].getVoltage(trigChan) < 1.f))
{
isGated[c] = false;
surge_lfo[c]->release();
// Clear this state once consumed
bool attackEntireLFO = gateInputTriggered[c];
gateInputTriggered[c] = false;

isGateConnected[c] = true;
}
else if (!inputs[INPUT_GATE].isConnected() &&
!inputs[INPUT_GATE_ENVONLY].isConnected())
bool attackEnvelopeOnly = gateEnvInputTriggered[c];
gateEnvInputTriggered[c] = false;
if (prevAnyGateInputHigh[c] && !anyGateInputHigh[c])
{
if (isGateConnected[c])
inNewAttack = true;
if (!isGated[c])
inNewAttack = true;
isGated[c] = true;
isGateConnected[c] = false;
}
else if ((inputs[INPUT_GATE].isConnected() ||
inputs[INPUT_GATE_ENVONLY].isConnected()) &&
!isGateConnected[c])
{
// Handle the at-startup case where we load a connected thing
// from construction. Make us connected, but not gated, and don't
// attack (since we won't release later since we aren't getaed yet)
isGateConnected[c] = true;
isGated[c] = false;
inNewAttack = false;
surge_lfo[c]->release();
}
prevAnyGateInputHigh[c] = anyGateInputHigh[c];

if (direct)
{
Expand All @@ -620,7 +590,7 @@ struct LFO : modules::XTModule

surge_lfo[c]->onepoleFactor = onepoleFactor;

if (inNewAttack)
if (attackEntireLFO)
{
if (retriggerFromZero)
surge_lfo[c]->envRetrigMode = LFOModulationSource::FROM_ZERO;
Expand All @@ -631,15 +601,16 @@ struct LFO : modules::XTModule
priorEnvStage[c] = -1;
}
surge_lfo[c]->process_block();
if (inNewAttack)
if (attackEntireLFO)
{
// Do the painful thing in the infrequent case
// Do the painful thing in the infrequent case. This basically moves the
// interpolant forward one step at the start of a new attack
output0[0][c / 4].s[c % 4] = surge_lfo[c]->get_output(0) * ampScale[0];
output0[1][c / 4].s[c % 4] = surge_lfo[c]->get_output(1) * ampScale[1];
output0[2][c / 4].s[c % 4] = surge_lfo[c]->get_output(2) * ampScale[2];
surge_lfo[c]->process_block();
}
else if (inNewEnvAttack)
else if (attackEnvelopeOnly)
{
if (retriggerFromZero)
surge_lfo[c]->envRetrigMode = LFOModulationSource::FROM_ZERO;
Expand All @@ -660,7 +631,7 @@ struct LFO : modules::XTModule
priorEnvStage[c] = surge_lfo[c]->getEnvState();
newEnvStage = true;
if (priorEnvStage[c] == lfoeg_stuck)
newEnvRelease = !isGated[c];
newEnvRelease = !anyGateInputHigh[c];
}

auto feg = surge_lfo[c]->retrigger_FEG;
Expand Down Expand Up @@ -695,7 +666,7 @@ struct LFO : modules::XTModule

for (int p = 0; p < 3; ++p)
ts[p][c] = surge_lfo[c]->get_output(p) * ampScale[p];
ts[2][c] *= isGateConnected[c] ? 1.f : untrigEnvMult;
ts[2][c] *= anyGateConnected ? 1.f : untrigEnvMult;
}
for (int p = 0; p < 3; ++p)
for (int i = 0; i < 4; ++i)
Expand Down
2 changes: 1 addition & 1 deletion surge
Submodule surge updated 77 files
+0 −0 doc/Adding a Filter.md
+1 −1 doc/Adding an FX.md
+1 −1 doc/Adding an SVG.md
+0 −0 doc/Baconpaul's Debug Fragments.txt
+0 −0 doc/Building for Apple Silicon.md
+0 −0 doc/Linux and Other Unix-like Distributions.md
+0 −0 doc/Wavetables.md
+ resources/data/patches_3rdparty/Kinsey Dulcet/Audio In/Favorite Sidechainer.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Bass/Acid Funk Bass.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Bass/Asym Bass.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Bass/Big Fat Combinator Bass.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Bass/Character Bass.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Bass/Fresh FM Bass.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Bass/From Subs To Reeses & Back Again.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Bass/Indie Dance Saw Bass.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Bass/Modern, Dirty & Fat.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Bass/OJD Bass.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Bass/Peachfuzz Bass.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Bass/The Flat Eric Fan Club Has Its Perks.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Bass/This Resonates With Me Deeply.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Bass/Very Versitile Bass.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Bass/Wet Donk Scribbles.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/FX/Dissociating In An Abandoned Mall.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Keys/70s Sci-Fi String Ensemble.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Keys/Bread & Butter Synth Brass.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Keys/Cameron Vs. Carpenter IV.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Keys/Cracks In The Sky.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Keys/Electro Steel Drum.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Keys/Fascinated With SK Brass.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Keys/Its $25 But You Gotta Haul It.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Keys/Japanese Suitcase.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Keys/Let's Start With An Accordian.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Keys/New Wave Wordy.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Keys/So Wavey.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Keys/Solo String DX Stack.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Keys/Symphonies Under My Fingertips.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Keys/The Friendliest NPC.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Keys/The Housewives of Lofi.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Leads/Brush Your Shoulders Off.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Leads/Scratchy Detuner.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Leads/Warehouse Lead.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Pads/Accident Prone Robot Arm.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Pads/Bell Pads Are So 90s.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Pads/Creamy Soap.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Pads/Jarre On Cassette.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Pads/Left Town Yesterday.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Pads/Pointillism.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Pads/She Loves You, Answer the Phone.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Pads/Smeared Bell Engine.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Pads/Triumphant Wasteland.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Pads/Warm Silicon Gel.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Percussion/808s & More.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Percussion/Deep Cut Snare.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Percussion/Modern Electro Snare.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Percussion/Nice Kicks.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Percussion/S+H Clap.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Percussion/Tasty Hats.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Percussion/Toms With Deep Secrets.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Percussion/Tuned Kicks For Days.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Plucks/No Frills Harp Pluck.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Plucks/Wooden Block Pluck.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Polysynths/1985 Called.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Polysynths/Chippy Polysynth.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Polysynths/I'll Wait.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Polysynths/Mono & Poly Reverser.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Polysynths/Progressive House Like It's 2007.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Polysynths/Retrofuture Vibes.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Polysynths/Tall Morning Shadows.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Sequences/Black Lights & Nitrous Balloons.fxp
+ resources/data/patches_3rdparty/Kinsey Dulcet/Sequences/Challenging Stage.fxp
+1 −5 src/surge-xt/SurgeSynthEditor.cpp
+48 −5 src/surge-xt/SurgeSynthProcessor.cpp
+11 −3 src/surge-xt/SurgeSynthProcessor.h
+10 −1 src/surge-xt/gui/SurgeGUIEditorValueCallbacks.cpp
+2 −1 src/surge-xt/gui/overlays/LuaEditors.cpp
+79 −1 src/surge-xt/osc/OpenSoundControl.cpp
+1 −0 src/surge-xt/osc/OpenSoundControl.h