Use float velocities in envelopes

This commit is contained in:
Paul Fd 2020-03-26 00:46:58 +01:00 committed by Paul Ferrand
parent de0d86fa09
commit 787fb963f3
6 changed files with 97 additions and 94 deletions

View file

@ -12,7 +12,7 @@
namespace sfz {
template <class Type>
void ADSREnvelope<Type>::reset(const Region& region, const MidiState& state, int delay, uint8_t velocity, float sampleRate) noexcept
void ADSREnvelope<Type>::reset(const Region& region, const MidiState& state, int delay, float velocity, float sampleRate) noexcept
{
auto secondsToSamples = [sampleRate](Type timeInSeconds) {
return static_cast<int>(timeInSeconds * sampleRate);

View file

@ -29,7 +29,7 @@ public:
* @param delay
* @param velocity
*/
void reset(const Region& region, const MidiState& state, int delay, uint8_t velocity, float sampleRate) noexcept;
void reset(const Region& region, const MidiState& state, int delay, float velocity, float sampleRate) noexcept;
/**
* @brief Get the next value for the envelope
*

View file

@ -32,9 +32,7 @@
#include "MidiState.h"
#include <absl/types/optional.h>
namespace sfz
{
namespace sfz {
/**
* @brief A description for an SFZ envelope generator, with its envelope parameters
* and possible CC modulation. This is a structure to be integrated directly in a
@ -60,36 +58,35 @@ inline float ccSwitchedValue(const MidiState& state, const absl::optional<CCValu
return value;
}
struct EGDescription
{
struct EGDescription {
EGDescription() = default;
EGDescription(const EGDescription&) = default;
EGDescription(EGDescription&&) = default;
~EGDescription() = default;
float attack { Default::attack };
float decay { Default::decay };
float delay { Default::delayEG };
float hold { Default::hold };
float release { Default::release };
float start { Default::start };
float sustain { Default::sustain };
int depth { Default::depth };
float vel2attack { Default::attack };
float vel2decay { Default::decay };
float vel2delay { Default::delayEG };
float vel2hold { Default::hold };
float vel2release { Default::vel2release };
float vel2sustain { Default::vel2sustain };
int vel2depth { Default::depth };
float attack { Default::attack };
float decay { Default::decay };
float delay { Default::delayEG };
float hold { Default::hold };
float release { Default::release };
float start { Default::start };
float sustain { Default::sustain };
int depth { Default::depth };
float vel2attack { Default::attack };
float vel2decay { Default::decay };
float vel2delay { Default::delayEG };
float vel2hold { Default::hold };
float vel2release { Default::vel2release };
float vel2sustain { Default::vel2sustain };
int vel2depth { Default::depth };
absl::optional<CCValuePair<float>> ccAttack;
absl::optional<CCValuePair<float>> ccDecay;
absl::optional<CCValuePair<float>> ccDelay;
absl::optional<CCValuePair<float>> ccHold;
absl::optional<CCValuePair<float>> ccRelease;
absl::optional<CCValuePair<float>> ccStart;
absl::optional<CCValuePair<float>> ccSustain;
absl::optional<CCValuePair<float>> ccAttack;
absl::optional<CCValuePair<float>> ccDecay;
absl::optional<CCValuePair<float>> ccDelay;
absl::optional<CCValuePair<float>> ccHold;
absl::optional<CCValuePair<float>> ccRelease;
absl::optional<CCValuePair<float>> ccStart;
absl::optional<CCValuePair<float>> ccSustain;
/**
* @brief Get the attack with possibly a CC modifier and a velocity modifier
@ -98,9 +95,10 @@ struct EGDescription
* @param velocity
* @return float
*/
float getAttack(const MidiState &state, uint8_t velocity) const noexcept
float getAttack(const MidiState& state, float velocity) const noexcept
{
return Default::egTimeRange.clamp(ccSwitchedValue(state, ccAttack, attack) + normalizeVelocity(velocity)*vel2attack);
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
return Default::egTimeRange.clamp(ccSwitchedValue(state, ccAttack, attack) + velocity * vel2attack);
}
/**
* @brief Get the decay with possibly a CC modifier and a velocity modifier
@ -109,9 +107,10 @@ struct EGDescription
* @param velocity
* @return float
*/
float getDecay(const MidiState &state, uint8_t velocity) const noexcept
float getDecay(const MidiState& state, float velocity) const noexcept
{
return Default::egTimeRange.clamp(ccSwitchedValue(state, ccDecay, decay) + normalizeVelocity(velocity)*vel2decay);
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
return Default::egTimeRange.clamp(ccSwitchedValue(state, ccDecay, decay) + velocity * vel2decay);
}
/**
* @brief Get the delay with possibly a CC modifier and a velocity modifier
@ -120,9 +119,10 @@ struct EGDescription
* @param velocity
* @return float
*/
float getDelay(const MidiState &state, uint8_t velocity) const noexcept
float getDelay(const MidiState& state, float velocity) const noexcept
{
return Default::egTimeRange.clamp(ccSwitchedValue(state, ccDelay, delay) + normalizeVelocity(velocity)*vel2delay);
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
return Default::egTimeRange.clamp(ccSwitchedValue(state, ccDelay, delay) + velocity * vel2delay);
}
/**
* @brief Get the holding duration with possibly a CC modifier and a velocity modifier
@ -131,9 +131,10 @@ struct EGDescription
* @param velocity
* @return float
*/
float getHold(const MidiState &state, uint8_t velocity) const noexcept
float getHold(const MidiState& state, float velocity) const noexcept
{
return Default::egTimeRange.clamp(ccSwitchedValue(state, ccHold, hold) + normalizeVelocity(velocity)*vel2hold);
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
return Default::egTimeRange.clamp(ccSwitchedValue(state, ccHold, hold) + velocity * vel2hold);
}
/**
* @brief Get the release duration with possibly a CC modifier and a velocity modifier
@ -142,9 +143,10 @@ struct EGDescription
* @param velocity
* @return float
*/
float getRelease(const MidiState &state, uint8_t velocity) const noexcept
float getRelease(const MidiState& state, float velocity) const noexcept
{
return Default::egTimeRange.clamp(ccSwitchedValue(state, ccRelease, release) + normalizeVelocity(velocity)*vel2release);
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
return Default::egTimeRange.clamp(ccSwitchedValue(state, ccRelease, release) + velocity * vel2release);
}
/**
* @brief Get the starting level with possibly a CC modifier and a velocity modifier
@ -153,7 +155,7 @@ struct EGDescription
* @param velocity
* @return float
*/
float getStart(const MidiState &state, uint8_t velocity) const noexcept
float getStart(const MidiState& state, float velocity) const noexcept
{
UNUSED(velocity);
return Default::egPercentRange.clamp(ccSwitchedValue(state, ccStart, start));
@ -165,9 +167,10 @@ struct EGDescription
* @param velocity
* @return float
*/
float getSustain(const MidiState &state, uint8_t velocity) const noexcept
float getSustain(const MidiState& state, float velocity) const noexcept
{
return Default::egPercentRange.clamp(ccSwitchedValue(state, ccSustain, sustain) + normalizeVelocity(velocity)*vel2sustain);
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
return Default::egPercentRange.clamp(ccSwitchedValue(state, ccSustain, sustain) + velocity * vel2sustain);
}
LEAK_DETECTOR(EGDescription);
};

View file

@ -140,7 +140,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,
initialDelay = delay + static_cast<int>(region->getDelay() * sampleRate);
baseFrequency = midiNoteFrequency(number);
bendStepFactor = centsFactor(region->bendStep);
egEnvelope.reset(*region, resources.midiState, delay, denormalize7Bits<uint8_t>(value), sampleRate);
egEnvelope.reset(*region, resources.midiState, delay, value, sampleRate);
}
bool sfz::Voice::isFree() const noexcept

View file

@ -49,14 +49,14 @@ TEST_CASE("[ADSREnvelope] Attack")
sfz::Region region { state };
region.amplitudeEG.attack = 0.02f;
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 100.0f);
std::array<float, 5> output;
std::array<float, 5> expected { 0.5f, 1.0f, 1.0f, 1.0f, 1.0f };
for (auto& out : output)
out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected));
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 100.0f);
absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output));
REQUIRE(approxEqual<float>(output, expected));
@ -69,14 +69,14 @@ TEST_CASE("[ADSREnvelope] Attack again")
sfz::Region region { state };
region.amplitudeEG.attack = 0.03f;
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 100.0f);
std::array<float, 5> output;
std::array<float, 5> expected { 0.33333f, 0.66667f, 1.0f, 1.0f, 1.0f };
for (auto& out : output)
out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected));
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 100.0f);
absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output));
REQUIRE(approxEqual<float>(output, expected));
@ -90,7 +90,7 @@ TEST_CASE("[ADSREnvelope] Release")
region.amplitudeEG.attack = 0.02f;
region.amplitudeEG.release = 0.04f;
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 100.0f);
envelope.startRelease(2);
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 };
@ -98,7 +98,7 @@ TEST_CASE("[ADSREnvelope] Release")
out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected));
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 100.0f);
envelope.startRelease(2);
absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output));
@ -114,14 +114,14 @@ TEST_CASE("[ADSREnvelope] Delay")
region.amplitudeEG.release = 0.04f;
region.amplitudeEG.delay = 0.02f;
std::array<float, 10> output;
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 100.0f);
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 };
for (auto& out : output)
out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected));
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 100.0f);
envelope.startRelease(4);
absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output));
@ -138,13 +138,13 @@ TEST_CASE("[ADSREnvelope] Lower sustain")
region.amplitudeEG.delay = 0.02f;
region.amplitudeEG.sustain = 50.0f;
std::array<float, 10> output;
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 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 };
for (auto& out : output)
out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected));
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 100.0f);
absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output));
REQUIRE(approxEqual<float>(output, expected));
@ -161,13 +161,13 @@ TEST_CASE("[ADSREnvelope] Decay")
region.amplitudeEG.sustain = 50.0f;
region.amplitudeEG.decay = 0.02f;
std::array<float, 10> output;
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 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 };
for (auto& out : output)
out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected));
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 100.0f);
absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output));
REQUIRE(approxEqual<float>(output, expected));
@ -185,13 +185,13 @@ TEST_CASE("[ADSREnvelope] Hold")
region.amplitudeEG.decay = 0.02f;
region.amplitudeEG.hold = 0.02f;
std::array<float, 12> output;
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 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 };
for (auto& out : output)
out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected));
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 100.0f);
absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output));
REQUIRE(approxEqual<float>(output, expected));
@ -208,7 +208,7 @@ TEST_CASE("[ADSREnvelope] Hold with release")
region.amplitudeEG.sustain = 50.0f;
region.amplitudeEG.decay = 0.02f;
region.amplitudeEG.hold = 0.02f;
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 100.0f);
envelope.startRelease(8);
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 };
@ -216,7 +216,7 @@ TEST_CASE("[ADSREnvelope] Hold with release")
out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected));
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 100.0f);
envelope.startRelease(8);
absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output));
@ -234,14 +234,14 @@ TEST_CASE("[ADSREnvelope] Hold with release 2")
region.amplitudeEG.sustain = 50.0f;
region.amplitudeEG.decay = 0.02f;
region.amplitudeEG.hold = 0.02f;
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 100.0f);
envelope.startRelease(4);
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 };
for (auto& out : output)
out = envelope.getNextValue();
REQUIRE(approxEqual<float>(output, expected));
envelope.reset(region, state, 0, 0, 100.0f);
envelope.reset(region, state, 0, 0.0f, 100.0f);
envelope.startRelease(4);
absl::c_fill(output, -1.0f);
envelope.getBlock(absl::MakeSpan(output));

View file

@ -18,13 +18,13 @@ TEST_CASE("[EGDescription] Attack range")
eg.attack = 1;
eg.vel2attack = -1.27f;
eg.ccAttack = { 63, 1.27f };
REQUIRE( eg.getAttack(state, 0) == 1.0f );
REQUIRE( eg.getAttack(state, 127) == 0.0f );
REQUIRE( eg.getAttack(state, 0_norm) == 1.0f );
REQUIRE( eg.getAttack(state, 127_norm) == 0.0f );
state.ccEvent(0, 63, 127_norm);
REQUIRE( eg.getAttack(state, 127) == 1.0f );
REQUIRE( eg.getAttack(state, 0) == 2.27f );
REQUIRE( eg.getAttack(state, 127_norm) == 1.0f );
REQUIRE( eg.getAttack(state, 0_norm) == 2.27f );
eg.ccAttack = { 63, 127.0f };
REQUIRE( eg.getAttack(state, 0) == 100.0f );
REQUIRE( eg.getAttack(state, 0_norm) == 100.0f );
}
TEST_CASE("[EGDescription] Delay range")
@ -34,13 +34,13 @@ TEST_CASE("[EGDescription] Delay range")
eg.delay = 1;
eg.vel2delay = -1.27f;
eg.ccDelay = { 63, 1.27f };
REQUIRE( eg.getDelay(state, 0) == 1.0f );
REQUIRE( eg.getDelay(state, 127) == 0.0f );
REQUIRE( eg.getDelay(state, 0_norm) == 1.0f );
REQUIRE( eg.getDelay(state, 127_norm) == 0.0f );
state.ccEvent(0, 63, 127_norm);
REQUIRE( eg.getDelay(state, 127) == 1.0f );
REQUIRE( eg.getDelay(state, 0) == 2.27f );
REQUIRE( eg.getDelay(state, 127_norm) == 1.0f );
REQUIRE( eg.getDelay(state, 0_norm) == 2.27f );
eg.ccDelay = { 63, 127.0f };
REQUIRE( eg.getDelay(state, 0) == 100.0f );
REQUIRE( eg.getDelay(state, 0_norm) == 100.0f );
}
TEST_CASE("[EGDescription] Decay range")
@ -50,13 +50,13 @@ TEST_CASE("[EGDescription] Decay range")
eg.decay = 1.0f;
eg.vel2decay = -1.27f;
eg.ccDecay = { 63, 1.27f };
REQUIRE( eg.getDecay(state, 0) == 1.0f );
REQUIRE( eg.getDecay(state, 127) == 0.0f );
REQUIRE( eg.getDecay(state, 0_norm) == 1.0f );
REQUIRE( eg.getDecay(state, 127_norm) == 0.0f );
state.ccEvent(0, 63, 127_norm);
REQUIRE( eg.getDecay(state, 127) == 1.0f );
REQUIRE( eg.getDecay(state, 0) == 2.27f );
REQUIRE( eg.getDecay(state, 127_norm) == 1.0f );
REQUIRE( eg.getDecay(state, 0_norm) == 2.27f );
eg.ccDecay = { 63, 127.0f };
REQUIRE( eg.getDecay(state, 0) == 100.0f );
REQUIRE( eg.getDecay(state, 0_norm) == 100.0f );
}
TEST_CASE("[EGDescription] Release range")
@ -66,13 +66,13 @@ TEST_CASE("[EGDescription] Release range")
eg.release = 1;
eg.vel2release = -1.27f;
eg.ccRelease = { 63, 1.27f };
REQUIRE( eg.getRelease(state, 0) == 1.0f );
REQUIRE( eg.getRelease(state, 127) == 0.0f );
REQUIRE( eg.getRelease(state, 0_norm) == 1.0f );
REQUIRE( eg.getRelease(state, 127_norm) == 0.0f );
state.ccEvent(0, 63, 127_norm);
REQUIRE( eg.getRelease(state, 127) == 1.0f );
REQUIRE( eg.getRelease(state, 0) == 2.27f );
REQUIRE( eg.getRelease(state, 127_norm) == 1.0f );
REQUIRE( eg.getRelease(state, 0_norm) == 2.27f );
eg.ccRelease = { 63, 127.0f };
REQUIRE( eg.getRelease(state, 0) == 100.0f );
REQUIRE( eg.getRelease(state, 0_norm) == 100.0f );
}
TEST_CASE("[EGDescription] Hold range")
@ -82,13 +82,13 @@ TEST_CASE("[EGDescription] Hold range")
eg.hold = 1;
eg.vel2hold = -1.27f;
eg.ccHold = { 63, 1.27f };
REQUIRE( eg.getHold(state, 0) == 1.0f );
REQUIRE( eg.getHold(state, 127) == 0.0f );
REQUIRE( eg.getHold(state, 0_norm) == 1.0f );
REQUIRE( eg.getHold(state, 127_norm) == 0.0f );
state.ccEvent(0, 63, 127_norm);
REQUIRE( eg.getHold(state, 127) == 1.0f );
REQUIRE( eg.getHold(state, 0) == 2.27f );
REQUIRE( eg.getHold(state, 127_norm) == 1.0f );
REQUIRE( eg.getHold(state, 0_norm) == 2.27f );
eg.ccHold = { 63, 127.0f };
REQUIRE( eg.getHold(state, 0) == 100.0f );
REQUIRE( eg.getHold(state, 0_norm) == 100.0f );
}
TEST_CASE("[EGDescription] Sustain level")
@ -98,12 +98,12 @@ TEST_CASE("[EGDescription] Sustain level")
eg.sustain = 50;
eg.vel2sustain = -100;
eg.ccSustain = { 63, 100.0f };
REQUIRE( eg.getSustain(state, 0) == 50.0f );
REQUIRE( eg.getSustain(state, 127) == 0.0f );
REQUIRE( eg.getSustain(state, 0_norm) == 50.0f );
REQUIRE( eg.getSustain(state, 127_norm) == 0.0f );
state.ccEvent(0, 63, 127_norm);
REQUIRE( eg.getSustain(state, 127) == 50.0f );
REQUIRE( eg.getSustain(state, 127_norm) == 50.0f );
eg.ccSustain = { 63, 200.0f };
REQUIRE( eg.getSustain(state, 0) == 100.0f );
REQUIRE( eg.getSustain(state, 0_norm) == 100.0f );
}
TEST_CASE("[EGDescription] Start level")
@ -112,10 +112,10 @@ TEST_CASE("[EGDescription] Start level")
sfz::MidiState state;
eg.start = 0;
eg.ccStart = { 63, 127.0f };
REQUIRE( eg.getStart(state, 0) == 0.0f );
REQUIRE( eg.getStart(state, 127) == 0.0f );
REQUIRE( eg.getStart(state, 0_norm) == 0.0f );
REQUIRE( eg.getStart(state, 127_norm) == 0.0f );
state.ccEvent(0, 63, 127_norm);
REQUIRE( eg.getStart(state, 0) == 100.0f );
REQUIRE( eg.getStart(state, 0_norm) == 100.0f );
eg.ccStart = { 63, -127.0f };
REQUIRE( eg.getStart(state, 0) == 0.0f );
REQUIRE( eg.getStart(state, 0_norm) == 0.0f );
}