diff --git a/src/sfizz/FlexEnvelope.cpp b/src/sfizz/FlexEnvelope.cpp index 3a7402dd..4158978f 100644 --- a/src/sfizz/FlexEnvelope.cpp +++ b/src/sfizz/FlexEnvelope.cpp @@ -45,6 +45,7 @@ struct FlexEnvelope::Impl { float currentTime_ { 0.0 }; absl::optional currentFramesUntilRelease_ { absl::nullopt }; bool isReleased_ { false }; + bool freeRunning_ { false }; // void process(absl::Span out); @@ -70,6 +71,15 @@ void FlexEnvelope::configure(const FlexEGDescription* desc) { Impl& impl = *impl_; impl.desc_ = desc; + + // + impl.freeRunning_ = false; + impl.isReleased_ = false; + + // + impl.currentStageNumber_ = 0; + impl.currentLevel_ = 0.0; + impl.currentTime_ = 0.0; } void FlexEnvelope::start(unsigned triggerDelay) @@ -90,12 +100,12 @@ void FlexEnvelope::start(unsigned triggerDelay) impl.stageSustained_ = desc.sustain == 0; impl.stageCurve_ = &point.curve(); impl.currentFramesUntilRelease_ = absl::nullopt; - impl.isReleased_ = false; +} - // - impl.currentStageNumber_ = 0; - impl.currentLevel_ = 0.0; - impl.currentTime_ = 0.0; +void FlexEnvelope::setFreeRunning(bool freeRunning) +{ + Impl& impl = *impl_; + impl.freeRunning_ = freeRunning; } void FlexEnvelope::release(unsigned releaseDelay) @@ -134,7 +144,6 @@ void FlexEnvelope::Impl::process(absl::Span out) const FlexEGDescription& desc = *desc_; size_t numFrames = out.size(); const float samplePeriod = samplePeriod_; - // Skip the initial delay, for frame-accurate trigger size_t skipFrames = std::min(numFrames, delayFramesLeft_); if (skipFrames > 0) { @@ -171,9 +180,9 @@ void FlexEnvelope::Impl::process(absl::Span out) } } } - while (!stageSustained_ && currentTime_ >= stageTime_) { + while ((!stageSustained_ || freeRunning_) && currentTime_ >= stageTime_) { // advance through completed timed stages - ASSERT(isReleased_ || !stageSustained_); + ASSERT(isReleased_ || !stageSustained_ || freeRunning_); if (stageTime_ == 0) { // if stage is of zero duration, immediate transition to level currentLevel_ = stageTargetLevel_; diff --git a/src/sfizz/FlexEnvelope.h b/src/sfizz/FlexEnvelope.h index d1d01a4a..d8cc2bf0 100644 --- a/src/sfizz/FlexEnvelope.h +++ b/src/sfizz/FlexEnvelope.h @@ -30,6 +30,13 @@ public: */ void configure(const FlexEGDescription* desc); + /** + * @brief Set the EG to be freeRunning or not + * + * @param freeRunning + */ + void setFreeRunning(bool freeRunning); + /** Start processing an EG as a region is triggered. */ diff --git a/src/sfizz/modulations/sources/FlexEnvelope.cpp b/src/sfizz/modulations/sources/FlexEnvelope.cpp index 557dd4db..13965373 100644 --- a/src/sfizz/modulations/sources/FlexEnvelope.cpp +++ b/src/sfizz/modulations/sources/FlexEnvelope.cpp @@ -38,6 +38,12 @@ void FlexEnvelopeSource::init(const ModKey& sourceKey, NumericId voiceId, FlexEnvelope* eg = voice->getFlexEG(egIndex); eg->configure(®ion->flexEGs[egIndex]); + bool freeRunning = ( + (region->loopMode == SfzLoopMode::one_shot && region->isOscillator()) + ); + if (freeRunning && region->flexAmpEG && egIndex == *region->flexAmpEG) + eg->setFreeRunning(true); + eg->start(delay); } diff --git a/tests/FlexEGT.cpp b/tests/FlexEGT.cpp index 5a8412eb..439ead5c 100644 --- a/tests/FlexEGT.cpp +++ b/tests/FlexEGT.cpp @@ -6,6 +6,7 @@ #include "sfizz/Synth.h" +#include "sfizz/AudioBuffer.h" #include "sfizz/FlexEnvelope.h" #include "catch2/catch.hpp" #include "TestHelpers.h" @@ -357,3 +358,61 @@ TEST_CASE("[FlexEG] Early release") } } } + +TEST_CASE("[FlexEG] Free-running flex AmpEG (no sustain)") +{ + sfz::Synth synth; + + synth.loadSfzString(fs::current_path(), R"( + sample=*noise + key=60 + loop_mode=one_shot + eg1_ampeg=1 + eg1_time1=0 eg1_level1=1 + eg1_time2=0.03 eg1_level2=0.6 + eg1_time3=0.06 eg1_level3=0.3 + eg1_time4=0.12 eg1_level4=0.1 + eg1_time5=0.3 eg1_level5=0 + sample=*noise + key=62 + loop_mode=one_shot + eg1_ampeg=1 + eg1_time1=0 eg1_level1=1 + eg1_time2=0.03 eg1_level2=0.6 + eg1_time3=0.06 eg1_level3=0.3 + eg1_time4=0.12 eg1_level4=0.1 + eg1_time5=0.3 eg1_level5=0 eg1_sustain=5 + sample=*noise + key=64 + eg1_ampeg=1 + eg1_time1=0 eg1_level1=1 + eg1_time2=0.03 eg1_level2=0.6 + eg1_time3=0.06 eg1_level3=0.3 + eg1_time4=0.12 eg1_level4=0.1 + eg1_time5=0.3 eg1_level5=0 eg1_sustain=5 + )"); + synth.noteOn(0, 60, 0); + sfz::AudioBuffer buffer { 2, 256 }; + synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices(true) == 1 ); + for (unsigned i = 0; i < 100; ++i) + synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices(true) == 0 ); + + synth.noteOn(0, 62, 0); + synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices(true) == 1 ); + for (unsigned i = 0; i < 100; ++i) + synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices(true) == 0 ); + + synth.noteOn(0, 64, 0); + synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices(true) == 1 ); + for (unsigned i = 0; i < 100; ++i) + synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices(true) == 1 ); + synth.noteOff(0, 64, 0); // the release stage is 0 duration + synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices(true) == 0 ); +}