diff --git a/src/sfizz/ADSREnvelope.cpp b/src/sfizz/ADSREnvelope.cpp index 7421389f..d5aa2c87 100644 --- a/src/sfizz/ADSREnvelope.cpp +++ b/src/sfizz/ADSREnvelope.cpp @@ -12,25 +12,25 @@ namespace sfz { template -void ADSREnvelope::reset(int attack, int release, Type sustain, int delay, int decay, int hold, Type start, Type depth) noexcept +void ADSREnvelope::reset(const Region& region, const MidiState& state, int delay, uint8_t velocity, float sampleRate) noexcept { - ASSERT(start <= 1.0f); - ASSERT(sustain <= 1.0f); + auto secondsToSamples = [sampleRate](auto timeInSeconds) { + return static_cast(timeInSeconds * sampleRate); + }; - sustain = clamp(sustain, 0.0, 1.0); - start = clamp(start, 0.0, 1.0); + const auto ccArray = state.getCCArray(); + this->delay = delay + secondsToSamples(region.amplitudeEG.getDelay(ccArray, velocity)); + this->attack = secondsToSamples(region.amplitudeEG.getAttack(ccArray, velocity)); + this->decay = secondsToSamples(region.amplitudeEG.getDecay(ccArray, velocity)); + this->release = secondsToSamples(region.amplitudeEG.getRelease(ccArray, velocity)); + this->hold = secondsToSamples(region.amplitudeEG.getHold(ccArray, velocity)); + this->peak = 1.0; + this->sustain = normalizePercents(region.amplitudeEG.getSustain(ccArray, velocity)); + this->start = this->peak * normalizePercents(region.amplitudeEG.getStart(ccArray, velocity)); - currentState = State::Done; - this->delay = delay; - this->attack = attack; - this->decay = decay; - this->release = release; - this->hold = hold; - this->start = depth * start; - this->sustain = depth * sustain; - this->peak = depth; releaseDelay = 0; shouldRelease = false; + freeRunning = ((region.trigger == SfzTrigger::release) || (region.trigger == SfzTrigger::release_key)); step = 0.0; currentValue = this->start; currentState = State::Delay; @@ -53,7 +53,7 @@ Type ADSREnvelope::getNextValue() noexcept return start; currentState = State::Attack; - step = (static_cast(1.0) - currentValue) / (attack > 0 ? attack : 1); + step = (peak - currentValue) / (attack > 0 ? attack : 1); [[fallthrough]]; case State::Attack: if (attack-- > 0) { @@ -62,7 +62,7 @@ Type ADSREnvelope::getNextValue() noexcept } currentState = State::Hold; - currentValue = 1.0; + currentValue = peak; [[fallthrough]]; case State::Hold: if (hold-- > 0) @@ -81,6 +81,8 @@ Type ADSREnvelope::getNextValue() noexcept currentValue = sustain; [[fallthrough]]; case State::Sustain: + if (freeRunning) + shouldRelease = true; return currentValue; case State::Release: if (release-- > 0) { @@ -152,6 +154,8 @@ void ADSREnvelope::getBlock(absl::Span output) noexcept currentState = State::Sustain; [[fallthrough]]; case State::Sustain: + if (freeRunning) + shouldRelease = true; break; case State::Release: length = min(remainingSamples, release); diff --git a/src/sfizz/ADSREnvelope.h b/src/sfizz/ADSREnvelope.h index 2c74f208..afa45f91 100644 --- a/src/sfizz/ADSREnvelope.h +++ b/src/sfizz/ADSREnvelope.h @@ -6,6 +6,8 @@ #pragma once #include "LeakDetector.h" +#include "Region.h" +#include "MidiState.h" #include namespace sfz { /** @@ -19,19 +21,15 @@ class ADSREnvelope { public: ADSREnvelope() = default; /** - * @brief Resets the ADSR envelope. There's alot of parameter but what can you do. - * They all match the SFZ specification. + * @brief Resets the ADSR envelope given a Region, the current midi state, and a delay and + * trigger velocity * - * @param attack - * @param release - * @param sustain + * @param region + * @param state * @param delay - * @param decay - * @param hold - * @param start - * @param depth + * @param velocity */ - void reset(int attack, int release, Type sustain = 1.0, int delay = 0, int decay = 0, int hold = 0, Type start = 0.0, Type depth = 1) noexcept; + void reset(const Region& region, const MidiState& state, int delay, uint8_t velocity, float sampleRate) noexcept; /** * @brief Get the next value for the envelope * @@ -91,6 +89,7 @@ private: Type sustain { 0 }; int releaseDelay { 0 }; bool shouldRelease { false }; + bool freeRunning { false }; LEAK_DETECTOR(ADSREnvelope); }; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 5873d5c6..95d854c5 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -109,23 +109,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value initialDelay = delay + static_cast(region->getDelay() * sampleRate); baseFrequency = midiNoteFrequency(number); bendStepFactor = centsFactor(region->bendStep); - prepareEGEnvelope(initialDelay, value); -} - -void sfz::Voice::prepareEGEnvelope(int delay, uint8_t velocity) noexcept -{ - auto secondsToSamples = [this](auto timeInSeconds) { - return static_cast(timeInSeconds * sampleRate); - }; - const auto& ccArray = resources.midiState.getCCArray(); - egEnvelope.reset( - secondsToSamples(region->amplitudeEG.getAttack(ccArray, velocity)), - secondsToSamples(region->amplitudeEG.getRelease(ccArray, velocity)), - normalizePercents(region->amplitudeEG.getSustain(ccArray, velocity)), - delay + secondsToSamples(region->amplitudeEG.getDelay(ccArray, velocity)), - secondsToSamples(region->amplitudeEG.getDecay(ccArray, velocity)), - secondsToSamples(region->amplitudeEG.getHold(ccArray, velocity)), - normalizePercents(region->amplitudeEG.getStart(ccArray, velocity))); + egEnvelope.reset(*region, resources.midiState, delay, value, sampleRate); } bool sfz::Voice::isFree() const noexcept diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index ca4dbc09..3b1f7f26 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -224,13 +224,6 @@ private: * @param buffer */ void fillWithGenerator(AudioSpan buffer) noexcept; - /** - * @brief Computes the values for the envelope depending on the note or CC number and the velocity/cc value - * - * @param delay - * @param velocity - */ - void prepareEGEnvelope(int delay, uint8_t velocity) noexcept; /** * @brief The function processing a mono sample source * diff --git a/tests/ADSREnvelopeT.cpp b/tests/ADSREnvelopeT.cpp index a2ace0a2..f11945b4 100644 --- a/tests/ADSREnvelopeT.cpp +++ b/tests/ADSREnvelopeT.cpp @@ -45,14 +45,18 @@ TEST_CASE("[ADSREnvelope] Basic state") TEST_CASE("[ADSREnvelope] Attack") { sfz::ADSREnvelope envelope; - envelope.reset(2, 0); + sfz::MidiState state; + sfz::Region region { state }; + region.amplitudeEG.attack = 0.02f; + + envelope.reset(region, state, 0, 0, 100.0f); std::array output; std::array expected { 0.5f, 1.0f, 1.0f, 1.0f, 1.0f }; for (auto& out : output) out = envelope.getNextValue(); REQUIRE(approxEqual(output, expected)); - envelope.reset(2, 0); + envelope.reset(region, state, 0, 0, 100.0f); absl::c_fill(output, -1.0f); envelope.getBlock(absl::MakeSpan(output)); REQUIRE(approxEqual(output, expected)); @@ -61,14 +65,18 @@ TEST_CASE("[ADSREnvelope] Attack") TEST_CASE("[ADSREnvelope] Attack again") { sfz::ADSREnvelope envelope; - envelope.reset(3, 0); + sfz::MidiState state; + sfz::Region region { state }; + region.amplitudeEG.attack = 0.03f; + + envelope.reset(region, state, 0, 0, 100.0f); std::array output; std::array expected { 0.33333f, 0.66667f, 1.0f, 1.0f, 1.0f }; for (auto& out : output) out = envelope.getNextValue(); REQUIRE(approxEqual(output, expected)); - envelope.reset(3, 0); + envelope.reset(region, state, 0, 0, 100.0f); absl::c_fill(output, -1.0f); envelope.getBlock(absl::MakeSpan(output)); REQUIRE(approxEqual(output, expected)); @@ -77,7 +85,12 @@ TEST_CASE("[ADSREnvelope] Attack again") TEST_CASE("[ADSREnvelope] Release") { sfz::ADSREnvelope envelope; - envelope.reset(2, 4); + sfz::MidiState state; + sfz::Region region { state }; + region.amplitudeEG.attack = 0.02f; + region.amplitudeEG.release = 0.04f; + + envelope.reset(region, state, 0, 0, 100.0f); envelope.startRelease(2); std::array output; std::array expected { 0.5f, 1.0f, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f }; @@ -85,7 +98,7 @@ TEST_CASE("[ADSREnvelope] Release") out = envelope.getNextValue(); REQUIRE(approxEqual(output, expected)); - envelope.reset(2, 4); + envelope.reset(region, state, 0, 0, 100.0f); envelope.startRelease(2); absl::c_fill(output, -1.0f); envelope.getBlock(absl::MakeSpan(output)); @@ -95,15 +108,20 @@ TEST_CASE("[ADSREnvelope] Release") TEST_CASE("[ADSREnvelope] Delay") { sfz::ADSREnvelope envelope; - envelope.reset(2, 4, 1.0f, 2); + sfz::MidiState state; + sfz::Region region { state }; + region.amplitudeEG.attack = 0.02f; + region.amplitudeEG.release = 0.04f; + region.amplitudeEG.delay = 0.02f; std::array output; + envelope.reset(region, state, 0, 0, 100.0f); envelope.startRelease(4); std::array expected { 0.0f, 0.0f, 0.5f, 1.0f, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f }; for (auto& out : output) out = envelope.getNextValue(); REQUIRE(approxEqual(output, expected)); - envelope.reset(2, 4, 1.0f, 2); + envelope.reset(region, state, 0, 0, 100.0f); envelope.startRelease(4); absl::c_fill(output, -1.0f); envelope.getBlock(absl::MakeSpan(output)); @@ -113,14 +131,20 @@ TEST_CASE("[ADSREnvelope] Delay") TEST_CASE("[ADSREnvelope] Lower sustain") { sfz::ADSREnvelope envelope; - envelope.reset(2, 4, 0.5f, 2); + sfz::MidiState state; + sfz::Region region { state }; + region.amplitudeEG.attack = 0.02f; + region.amplitudeEG.release = 0.04f; + region.amplitudeEG.delay = 0.02f; + region.amplitudeEG.sustain = 50.0f; std::array output; + envelope.reset(region, state, 0, 0, 100.0f); std::array expected { 0.0f, 0.0f, 0.5f, 1.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f }; for (auto& out : output) out = envelope.getNextValue(); REQUIRE(approxEqual(output, expected)); - envelope.reset(2, 4, 0.5, 2); + envelope.reset(region, state, 0, 0, 100.0f); absl::c_fill(output, -1.0f); envelope.getBlock(absl::MakeSpan(output)); REQUIRE(approxEqual(output, expected)); @@ -129,14 +153,21 @@ TEST_CASE("[ADSREnvelope] Lower sustain") TEST_CASE("[ADSREnvelope] Decay") { sfz::ADSREnvelope envelope; - envelope.reset(2, 4, 0.5f, 2, 2); + sfz::MidiState state; + sfz::Region region { state }; + region.amplitudeEG.attack = 0.02f; + region.amplitudeEG.release = 0.04f; + region.amplitudeEG.delay = 0.02f; + region.amplitudeEG.sustain = 50.0f; + region.amplitudeEG.decay = 0.02f; std::array output; + envelope.reset(region, state, 0, 0, 100.0f); std::array expected { 0.0f, 0.0f, 0.5f, 1.0f, 0.707107f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5 }; for (auto& out : output) out = envelope.getNextValue(); REQUIRE(approxEqual(output, expected)); - envelope.reset(2, 4, 0.5f, 2, 2); + envelope.reset(region, state, 0, 0, 100.0f); absl::c_fill(output, -1.0f); envelope.getBlock(absl::MakeSpan(output)); REQUIRE(approxEqual(output, expected)); @@ -145,14 +176,22 @@ TEST_CASE("[ADSREnvelope] Decay") TEST_CASE("[ADSREnvelope] Hold") { sfz::ADSREnvelope envelope; - envelope.reset(2, 4, 0.5f, 2, 2, 2); + sfz::MidiState state; + sfz::Region region { state }; + region.amplitudeEG.attack = 0.02f; + region.amplitudeEG.release = 0.04f; + region.amplitudeEG.delay = 0.02f; + region.amplitudeEG.sustain = 50.0f; + region.amplitudeEG.decay = 0.02f; + region.amplitudeEG.hold = 0.02f; std::array output; + envelope.reset(region, state, 0, 0, 100.0f); std::array expected { 0.0f, 0.0f, 0.5f, 1.0f, 1.0f, 1.0f, 0.707107f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f }; for (auto& out : output) out = envelope.getNextValue(); REQUIRE(approxEqual(output, expected)); - envelope.reset(2, 4, 0.5f, 2, 2, 2); + envelope.reset(region, state, 0, 0, 100.0f); absl::c_fill(output, -1.0f); envelope.getBlock(absl::MakeSpan(output)); REQUIRE(approxEqual(output, expected)); @@ -161,7 +200,15 @@ TEST_CASE("[ADSREnvelope] Hold") TEST_CASE("[ADSREnvelope] Hold with release") { sfz::ADSREnvelope envelope; - envelope.reset(2, 4, 0.5f, 2, 2, 2); + sfz::MidiState state; + sfz::Region region { state }; + region.amplitudeEG.attack = 0.02f; + region.amplitudeEG.release = 0.04f; + region.amplitudeEG.delay = 0.02f; + region.amplitudeEG.sustain = 50.0f; + region.amplitudeEG.decay = 0.02f; + region.amplitudeEG.hold = 0.02f; + envelope.reset(region, state, 0, 0, 100.0f); envelope.startRelease(8); std::array output; std::array expected { 0.0f, 0.0f, 0.5f, 1.0f, 1.0f, 1.0f, 0.707107f, 0.5f, 0.05f, 0.005f, 0.0005f, 0.00005f, 0.0f, 0.0f }; @@ -169,7 +216,7 @@ TEST_CASE("[ADSREnvelope] Hold with release") out = envelope.getNextValue(); REQUIRE(approxEqual(output, expected)); - envelope.reset(2, 4, 0.5f, 2, 2, 2); + envelope.reset(region, state, 0, 0, 100.0f); envelope.startRelease(8); absl::c_fill(output, -1.0f); envelope.getBlock(absl::MakeSpan(output)); @@ -179,14 +226,22 @@ TEST_CASE("[ADSREnvelope] Hold with release") TEST_CASE("[ADSREnvelope] Hold with release 2") { sfz::ADSREnvelope envelope; - envelope.reset(2, 4, 0.5f, 2, 2, 2); + sfz::MidiState state; + sfz::Region region { state }; + region.amplitudeEG.attack = 0.02f; + region.amplitudeEG.release = 0.04f; + region.amplitudeEG.delay = 0.02f; + region.amplitudeEG.sustain = 50.0f; + region.amplitudeEG.decay = 0.02f; + region.amplitudeEG.hold = 0.02f; + envelope.reset(region, state, 0, 0, 100.0f); envelope.startRelease(4); std::array output; std::array expected { 0.0f, 0.0f, 0.5f, 1.0f, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f, 0.0f, 0.0 }; for (auto& out : output) out = envelope.getNextValue(); REQUIRE(approxEqual(output, expected)); - envelope.reset(2, 4, 0.5f, 2, 2, 2); + envelope.reset(region, state, 0, 0, 100.0f); envelope.startRelease(4); absl::c_fill(output, -1.0f); envelope.getBlock(absl::MakeSpan(output)); diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 0d3070f6..6c306c00 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -162,3 +162,18 @@ TEST_CASE("[Synth] Releasing after the initial and normal mode does not trigger synth.renderBlock(buffer); REQUIRE( !synth.getVoiceView(0)->isFree() ); } + +TEST_CASE("[Synth] Trigger=release and an envelope properly kills the voice at the end of the envelope") +{ + sfz::Synth synth; + synth.setSamplesPerBlock(1024); + sfz::AudioBuffer buffer(2, 1024); + synth.setNumVoices(1); + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/envelope_trigger_release.sfz"); + synth.noteOn(10, 60, 63); + synth.noteOff(10, 60, 63); + REQUIRE( !synth.getVoiceView(0)->isFree() ); + for (int i = 0; i < 10; ++i) + synth.renderBlock(buffer); + REQUIRE( synth.getVoiceView(0)->isFree() ); +} diff --git a/tests/TestFiles/envelope_trigger_release.sfz b/tests/TestFiles/envelope_trigger_release.sfz new file mode 100644 index 00000000..24c8e709 --- /dev/null +++ b/tests/TestFiles/envelope_trigger_release.sfz @@ -0,0 +1,13 @@ + + +lovel=0 +hivel=127 + + +trigger=release +sample=*noise +loop_mode=one_shot +ampeg_attack=0.02 +ampeg_decay=0.02 +ampeg_release=0 +ampeg_sustain=0