Skip to content

Commit

Permalink
Handle NAN and INF from Formula (surge-synthesizer#5576)
Browse files Browse the repository at this point in the history
NaN and Inf from formula would rip through the engine and
blow through various checks and so on. So inside formula
do an explicit std::isfinite and set a flag if they return
these. Also add a unit test of the behavior.

Closes surge-synthesizer#5566
  • Loading branch information
baconpaul authored Dec 6, 2021
1 parent ad921a1 commit 4c9d5da
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 4 deletions.
16 changes: 13 additions & 3 deletions src/common/dsp/modulators/FormulaModulationHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -525,12 +525,22 @@ void valueAt(int phaseIntPart, float phaseFracPart, FormulaModulatorStorage *fs,
// stack is now just the result
if (lres == LUA_OK)
{
s->isFinite = true;
auto checkFinite = [s](float f) {
if (!std::isfinite(f))
{
s->isFinite = false;
return 0.f;
}
return f;
};

if (lua_isnumber(s->L, -1))
{
// OK so you returned a value. Just use it
auto r = lua_tonumber(s->L, -1);
lua_pop(s->L, 1);
output[0] = r;
output[0] = checkFinite(r);
return;
}
if (!lua_istable(s->L, -1))
Expand All @@ -552,7 +562,7 @@ void valueAt(int phaseIntPart, float phaseFracPart, FormulaModulatorStorage *fs,
float res = 0.0;
if (lua_isnumber(s->L, -1))
{
output[0] = lua_tonumber(s->L, -1);
output[0] = checkFinite(lua_tonumber(s->L, -1));
}
else if (lua_istable(s->L, -1))
{
Expand All @@ -574,7 +584,7 @@ void valueAt(int phaseIntPart, float phaseFracPart, FormulaModulatorStorage *fs,
}

// Remember - LUA is 0 based
output[idx - 1] = lua_tonumber(s->L, -1);
output[idx - 1] = checkFinite(lua_tonumber(s->L, -1));
lua_pop(s->L, 1);
len = std::max(len, idx - 1);
}
Expand Down
1 change: 1 addition & 0 deletions src/common/dsp/modulators/FormulaModulationHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ struct EvaluatorState

bool isvalid = false;
bool useEnvelope = true;
bool isFinite = true;

bool subVoice{false}, subLfoParams{true}, subLfoEnvelope{false}, subTiming{true};
bool subMacros[n_customcontrollers], subAnyMacro{false};
Expand Down
72 changes: 71 additions & 1 deletion src/surge-testrunner/UnitTestsLUA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -826,4 +826,74 @@ TEST_CASE("Macros Are Available", "[formula]")
auto pitchId = surge->storage.getPatch().scene[0].osc[0].pitch.id;
surge->setModulation(pitchId, ms_lfo1, 0, 0, 0.1);
REQUIRE(1);
}
}

TEST_CASE("Nan Clampsr", "[formula]")
{
SECTION("Nan Formula")
{
auto surge = Surge::Test::surgeOnSine();
surge->storage.getPatch().scene[0].lfo[0].shape.val.i = lt_formula;
auto pitchId = surge->storage.getPatch().scene[0].osc[0].pitch.id;
surge->setModulation(pitchId, ms_lfo1, 0, 0, 0.1);

surge->storage.getPatch().formulamods[0][0].setFormula(R"FN(
function init(modstate)
modstate["depth"] = 0
return modstate
end
function process(modstate)
modstate["output"] = (modstate["phase"] * 2 - 1) / modstate["depth" ]
return modstate
end)FN");
for (int i = 0; i < 10; ++i)
surge->process();

surge->playNote(0, 60, 100, 0);
for (int i = 0; i < 10; ++i)
surge->process();

REQUIRE(!surge->voices[0].empty());
auto ms = surge->voices[0].front()->modsources[ms_lfo1];
REQUIRE(ms);
auto lms = dynamic_cast<LFOModulationSource *>(ms);
REQUIRE(lms);

REQUIRE(lms->get_output(0) == 0.f);
REQUIRE(!lms->formulastate.isFinite);
}

SECTION("Not Nan Formula")
{
auto surge = Surge::Test::surgeOnSine();
surge->storage.getPatch().scene[0].lfo[0].shape.val.i = lt_formula;
auto pitchId = surge->storage.getPatch().scene[0].osc[0].pitch.id;
surge->setModulation(pitchId, ms_lfo1, 0, 0, 0.1);

surge->storage.getPatch().formulamods[0][0].setFormula(R"FN(
function init(modstate)
modstate["depth"] = 1
return modstate
end
function process(modstate)
modstate["output"] = (modstate["phase"] * 2 - 1) / modstate["depth" ]
return modstate
end)FN");
for (int i = 0; i < 10; ++i)
surge->process();

surge->playNote(0, 60, 100, 0);
for (int i = 0; i < 10; ++i)
surge->process();

REQUIRE(!surge->voices[0].empty());
auto ms = surge->voices[0].front()->modsources[ms_lfo1];
REQUIRE(ms);
auto lms = dynamic_cast<LFOModulationSource *>(ms);
REQUIRE(lms);

REQUIRE(lms->formulastate.isFinite);
}
}
21 changes: 21 additions & 0 deletions src/surge-xt/gui/widgets/LFOAndStepDisplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ void LFOAndStepDisplay::paintWaveform(juce::Graphics &g)

float priorval = 0.f, priorwval = 0.f;

bool warnForInvalid{false};
std::string invalidMessage;

for (int i = 0; i < totalSamples; i += averagingWindow)
{
float val = 0;
Expand All @@ -263,6 +266,16 @@ void LFOAndStepDisplay::paintWaveform(juce::Graphics &g)
tFullWave->process_block();
}

if (lfodata->shape.val.i == lt_formula)
{
if (!tlfo->formulastate.isFinite ||
(tFullWave && !tFullWave->formulastate.isFinite))
{
warnForInvalid = true;
invalidMessage = "Formula produced nan or inf";
}
}

if (susCountdown < 0 && tlfo->env_state == lfoeg_stuck)
{
susCountdown = susTime * samplerate / BLOCK_SIZE;
Expand Down Expand Up @@ -606,6 +619,14 @@ void LFOAndStepDisplay::paintWaveform(juce::Graphics &g)
dc->drawLine(sp, ep);
#endif
}

if (warnForInvalid)
{
g.setColour(skin->getColor(Colors::LFO::Waveform::Wave));
g.setFont(Surge::GUI::getFontManager()->getLatoAtSize(14, juce::Font::bold));
g.drawText(invalidMessage, waveform_display.withTrimmedBottom(30),
juce::Justification::centred);
}
}

void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g)
Expand Down

0 comments on commit 4c9d5da

Please sign in to comment.