- The envelope does not sustain if the region has `trigger=release`
- Change the envelope setup to pass the region rather than
individual envelope parameters
This commit is contained in:
Paul Ferrand 2020-02-17 20:51:41 +01:00
parent ffe708331b
commit f86d93c670
7 changed files with 131 additions and 68 deletions

View file

@ -12,25 +12,25 @@
namespace sfz { namespace sfz {
template <class Type> template <class Type>
void ADSREnvelope<Type>::reset(int attack, int release, Type sustain, int delay, int decay, int hold, Type start, Type depth) noexcept void ADSREnvelope<Type>::reset(const Region& region, const MidiState& state, int delay, uint8_t velocity, float sampleRate) noexcept
{ {
ASSERT(start <= 1.0f); auto secondsToSamples = [sampleRate](auto timeInSeconds) {
ASSERT(sustain <= 1.0f); return static_cast<int>(timeInSeconds * sampleRate);
};
sustain = clamp<Type>(sustain, 0.0, 1.0); const auto ccArray = state.getCCArray();
start = clamp<Type>(start, 0.0, 1.0); 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; releaseDelay = 0;
shouldRelease = false; shouldRelease = false;
freeRunning = ((region.trigger == SfzTrigger::release) || (region.trigger == SfzTrigger::release_key));
step = 0.0; step = 0.0;
currentValue = this->start; currentValue = this->start;
currentState = State::Delay; currentState = State::Delay;
@ -53,7 +53,7 @@ Type ADSREnvelope<Type>::getNextValue() noexcept
return start; return start;
currentState = State::Attack; currentState = State::Attack;
step = (static_cast<Type>(1.0) - currentValue) / (attack > 0 ? attack : 1); step = (peak - currentValue) / (attack > 0 ? attack : 1);
[[fallthrough]]; [[fallthrough]];
case State::Attack: case State::Attack:
if (attack-- > 0) { if (attack-- > 0) {
@ -62,7 +62,7 @@ Type ADSREnvelope<Type>::getNextValue() noexcept
} }
currentState = State::Hold; currentState = State::Hold;
currentValue = 1.0; currentValue = peak;
[[fallthrough]]; [[fallthrough]];
case State::Hold: case State::Hold:
if (hold-- > 0) if (hold-- > 0)
@ -81,6 +81,8 @@ Type ADSREnvelope<Type>::getNextValue() noexcept
currentValue = sustain; currentValue = sustain;
[[fallthrough]]; [[fallthrough]];
case State::Sustain: case State::Sustain:
if (freeRunning)
shouldRelease = true;
return currentValue; return currentValue;
case State::Release: case State::Release:
if (release-- > 0) { if (release-- > 0) {
@ -152,6 +154,8 @@ void ADSREnvelope<Type>::getBlock(absl::Span<Type> output) noexcept
currentState = State::Sustain; currentState = State::Sustain;
[[fallthrough]]; [[fallthrough]];
case State::Sustain: case State::Sustain:
if (freeRunning)
shouldRelease = true;
break; break;
case State::Release: case State::Release:
length = min(remainingSamples, release); length = min(remainingSamples, release);

View file

@ -6,6 +6,8 @@
#pragma once #pragma once
#include "LeakDetector.h" #include "LeakDetector.h"
#include "Region.h"
#include "MidiState.h"
#include <absl/types/span.h> #include <absl/types/span.h>
namespace sfz { namespace sfz {
/** /**
@ -19,19 +21,15 @@ class ADSREnvelope {
public: public:
ADSREnvelope() = default; ADSREnvelope() = default;
/** /**
* @brief Resets the ADSR envelope. There's alot of parameter but what can you do. * @brief Resets the ADSR envelope given a Region, the current midi state, and a delay and
* They all match the SFZ specification. * trigger velocity
* *
* @param attack * @param region
* @param release * @param state
* @param sustain
* @param delay * @param delay
* @param decay * @param velocity
* @param hold
* @param start
* @param depth
*/ */
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 * @brief Get the next value for the envelope
* *
@ -91,6 +89,7 @@ private:
Type sustain { 0 }; Type sustain { 0 };
int releaseDelay { 0 }; int releaseDelay { 0 };
bool shouldRelease { false }; bool shouldRelease { false };
bool freeRunning { false };
LEAK_DETECTOR(ADSREnvelope); LEAK_DETECTOR(ADSREnvelope);
}; };

View file

@ -109,23 +109,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value
initialDelay = delay + static_cast<uint32_t>(region->getDelay() * sampleRate); initialDelay = delay + static_cast<uint32_t>(region->getDelay() * sampleRate);
baseFrequency = midiNoteFrequency(number); baseFrequency = midiNoteFrequency(number);
bendStepFactor = centsFactor(region->bendStep); bendStepFactor = centsFactor(region->bendStep);
prepareEGEnvelope(initialDelay, value); egEnvelope.reset(*region, resources.midiState, delay, value, sampleRate);
}
void sfz::Voice::prepareEGEnvelope(int delay, uint8_t velocity) noexcept
{
auto secondsToSamples = [this](auto timeInSeconds) {
return static_cast<int>(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)));
} }
bool sfz::Voice::isFree() const noexcept bool sfz::Voice::isFree() const noexcept

View file

@ -224,13 +224,6 @@ private:
* @param buffer * @param buffer
*/ */
void fillWithGenerator(AudioSpan<float> buffer) noexcept; void fillWithGenerator(AudioSpan<float> 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 * @brief The function processing a mono sample source
* *

View file

@ -45,14 +45,18 @@ TEST_CASE("[ADSREnvelope] Basic state")
TEST_CASE("[ADSREnvelope] Attack") TEST_CASE("[ADSREnvelope] Attack")
{ {
sfz::ADSREnvelope<float> envelope; sfz::ADSREnvelope<float> 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<float, 5> output; std::array<float, 5> output;
std::array<float, 5> expected { 0.5f, 1.0f, 1.0f, 1.0f, 1.0f }; std::array<float, 5> expected { 0.5f, 1.0f, 1.0f, 1.0f, 1.0f };
for (auto& out : output) for (auto& out : output)
out = envelope.getNextValue(); out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected)); REQUIRE(approxEqual<float>(output, expected));
envelope.reset(2, 0); envelope.reset(region, state, 0, 0, 100.0f);
absl::c_fill(output, -1.0f); absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output)); envelope.getBlock(absl::MakeSpan(output));
REQUIRE(approxEqual<float>(output, expected)); REQUIRE(approxEqual<float>(output, expected));
@ -61,14 +65,18 @@ TEST_CASE("[ADSREnvelope] Attack")
TEST_CASE("[ADSREnvelope] Attack again") TEST_CASE("[ADSREnvelope] Attack again")
{ {
sfz::ADSREnvelope<float> envelope; sfz::ADSREnvelope<float> 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<float, 5> output; std::array<float, 5> output;
std::array<float, 5> expected { 0.33333f, 0.66667f, 1.0f, 1.0f, 1.0f }; std::array<float, 5> expected { 0.33333f, 0.66667f, 1.0f, 1.0f, 1.0f };
for (auto& out : output) for (auto& out : output)
out = envelope.getNextValue(); out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected)); REQUIRE(approxEqual<float>(output, expected));
envelope.reset(3, 0); envelope.reset(region, state, 0, 0, 100.0f);
absl::c_fill(output, -1.0f); absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output)); envelope.getBlock(absl::MakeSpan(output));
REQUIRE(approxEqual<float>(output, expected)); REQUIRE(approxEqual<float>(output, expected));
@ -77,7 +85,12 @@ TEST_CASE("[ADSREnvelope] Attack again")
TEST_CASE("[ADSREnvelope] Release") TEST_CASE("[ADSREnvelope] Release")
{ {
sfz::ADSREnvelope<float> envelope; sfz::ADSREnvelope<float> 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); envelope.startRelease(2);
std::array<float, 8> output; std::array<float, 8> output;
std::array<float, 8> expected { 0.5f, 1.0f, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f }; std::array<float, 8> 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(); out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected)); REQUIRE(approxEqual<float>(output, expected));
envelope.reset(2, 4); envelope.reset(region, state, 0, 0, 100.0f);
envelope.startRelease(2); envelope.startRelease(2);
absl::c_fill(output, -1.0f); absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output)); envelope.getBlock(absl::MakeSpan(output));
@ -95,15 +108,20 @@ TEST_CASE("[ADSREnvelope] Release")
TEST_CASE("[ADSREnvelope] Delay") TEST_CASE("[ADSREnvelope] Delay")
{ {
sfz::ADSREnvelope<float> envelope; sfz::ADSREnvelope<float> 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<float, 10> output; std::array<float, 10> output;
envelope.reset(region, state, 0, 0, 100.0f);
envelope.startRelease(4); envelope.startRelease(4);
std::array<float, 10> expected { 0.0f, 0.0f, 0.5f, 1.0f, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f }; std::array<float, 10> 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) for (auto& out : output)
out = envelope.getNextValue(); out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected)); REQUIRE(approxEqual<float>(output, expected));
envelope.reset(2, 4, 1.0f, 2); envelope.reset(region, state, 0, 0, 100.0f);
envelope.startRelease(4); envelope.startRelease(4);
absl::c_fill(output, -1.0f); absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output)); envelope.getBlock(absl::MakeSpan(output));
@ -113,14 +131,20 @@ TEST_CASE("[ADSREnvelope] Delay")
TEST_CASE("[ADSREnvelope] Lower sustain") TEST_CASE("[ADSREnvelope] Lower sustain")
{ {
sfz::ADSREnvelope<float> envelope; sfz::ADSREnvelope<float> 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<float, 10> output; std::array<float, 10> output;
envelope.reset(region, state, 0, 0, 100.0f);
std::array<float, 10> expected { 0.0f, 0.0f, 0.5f, 1.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f }; std::array<float, 10> 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) for (auto& out : output)
out = envelope.getNextValue(); out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected)); REQUIRE(approxEqual<float>(output, expected));
envelope.reset(2, 4, 0.5, 2); envelope.reset(region, state, 0, 0, 100.0f);
absl::c_fill(output, -1.0f); absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output)); envelope.getBlock(absl::MakeSpan(output));
REQUIRE(approxEqual<float>(output, expected)); REQUIRE(approxEqual<float>(output, expected));
@ -129,14 +153,21 @@ TEST_CASE("[ADSREnvelope] Lower sustain")
TEST_CASE("[ADSREnvelope] Decay") TEST_CASE("[ADSREnvelope] Decay")
{ {
sfz::ADSREnvelope<float> envelope; sfz::ADSREnvelope<float> 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<float, 10> output; std::array<float, 10> output;
envelope.reset(region, state, 0, 0, 100.0f);
std::array<float, 10> expected { 0.0f, 0.0f, 0.5f, 1.0f, 0.707107f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5 }; std::array<float, 10> 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) for (auto& out : output)
out = envelope.getNextValue(); out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected)); REQUIRE(approxEqual<float>(output, expected));
envelope.reset(2, 4, 0.5f, 2, 2); envelope.reset(region, state, 0, 0, 100.0f);
absl::c_fill(output, -1.0f); absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output)); envelope.getBlock(absl::MakeSpan(output));
REQUIRE(approxEqual<float>(output, expected)); REQUIRE(approxEqual<float>(output, expected));
@ -145,14 +176,22 @@ TEST_CASE("[ADSREnvelope] Decay")
TEST_CASE("[ADSREnvelope] Hold") TEST_CASE("[ADSREnvelope] Hold")
{ {
sfz::ADSREnvelope<float> envelope; sfz::ADSREnvelope<float> 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<float, 12> output; std::array<float, 12> output;
envelope.reset(region, state, 0, 0, 100.0f);
std::array<float, 12> 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 }; std::array<float, 12> 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) for (auto& out : output)
out = envelope.getNextValue(); out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected)); REQUIRE(approxEqual<float>(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); absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output)); envelope.getBlock(absl::MakeSpan(output));
REQUIRE(approxEqual<float>(output, expected)); REQUIRE(approxEqual<float>(output, expected));
@ -161,7 +200,15 @@ TEST_CASE("[ADSREnvelope] Hold")
TEST_CASE("[ADSREnvelope] Hold with release") TEST_CASE("[ADSREnvelope] Hold with release")
{ {
sfz::ADSREnvelope<float> envelope; sfz::ADSREnvelope<float> 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); envelope.startRelease(8);
std::array<float, 14> output; std::array<float, 14> output;
std::array<float, 14> 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 }; std::array<float, 14> 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(); out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected)); REQUIRE(approxEqual<float>(output, expected));
envelope.reset(2, 4, 0.5f, 2, 2, 2); envelope.reset(region, state, 0, 0, 100.0f);
envelope.startRelease(8); envelope.startRelease(8);
absl::c_fill(output, -1.0f); absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output)); envelope.getBlock(absl::MakeSpan(output));
@ -179,14 +226,22 @@ TEST_CASE("[ADSREnvelope] Hold with release")
TEST_CASE("[ADSREnvelope] Hold with release 2") TEST_CASE("[ADSREnvelope] Hold with release 2")
{ {
sfz::ADSREnvelope<float> envelope; sfz::ADSREnvelope<float> 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); envelope.startRelease(4);
std::array<float, 14> output; std::array<float, 14> output;
std::array<float, 14> 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 }; std::array<float, 14> 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) for (auto& out : output)
out = envelope.getNextValue(); out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected)); REQUIRE(approxEqual<float>(output, expected));
envelope.reset(2, 4, 0.5f, 2, 2, 2); envelope.reset(region, state, 0, 0, 100.0f);
envelope.startRelease(4); envelope.startRelease(4);
absl::c_fill(output, -1.0f); absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output)); envelope.getBlock(absl::MakeSpan(output));

View file

@ -162,3 +162,18 @@ TEST_CASE("[Synth] Releasing after the initial and normal mode does not trigger
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( !synth.getVoiceView(0)->isFree() ); 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<float> 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() );
}

View file

@ -0,0 +1,13 @@
<group>
lovel=0
hivel=127
<region>
trigger=release
sample=*noise
loop_mode=one_shot
ampeg_attack=0.02
ampeg_decay=0.02
ampeg_release=0
ampeg_sustain=0