Add dynamic to the basic envelope

This commit is contained in:
Paul Fd 2021-06-27 16:51:29 +02:00
parent e79e5cf04d
commit d2ae2a0fe0
14 changed files with 130 additions and 59 deletions

View file

@ -38,20 +38,13 @@ Float ADSREnvelope::secondsToExpRate(Float timeInSeconds) const noexcept
return std::exp(Float(-9.0) / (timeInSeconds * sampleRate)); return std::exp(Float(-9.0) / (timeInSeconds * sampleRate));
}; };
void ADSREnvelope::reset(const EGDescription& desc, const Region& region, const MidiState& state, int delay, float velocity, float sampleRate) noexcept void ADSREnvelope::reset(const EGDescription& desc, const Region& region, int delay, float velocity, float sampleRate) noexcept
{ {
this->sampleRate = sampleRate; this->sampleRate = sampleRate;
desc_ = &desc;
this->delay = delay + secondsToSamples(desc.getDelay(state, velocity)); triggerVelocity_ = velocity;
this->attackStep = secondsToLinRate(desc.getAttack(state, velocity)); updateValues(delay);
this->decayRate = secondsToExpRate(desc.getDecay(state, velocity));
this->releaseRate = secondsToExpRate(desc.getRelease(state, velocity));
this->hold = secondsToSamples(desc.getHold(state, velocity));
this->sustain = clamp(desc.getSustain(state, velocity), 0.0f, 1.0f);
this->start = clamp(desc.getStart(state, velocity), 0.0f, 1.0f);
releaseDelay = 0; releaseDelay = 0;
sustainThreshold = this->sustain + config::virtuallyZero;
shouldRelease = false; shouldRelease = false;
freeRunning = ( freeRunning = (
(this->sustain <= Float(config::sustainFreeRunningThreshold)) (this->sustain <= Float(config::sustainFreeRunningThreshold))
@ -61,7 +54,36 @@ void ADSREnvelope::reset(const EGDescription& desc, const Region& region, const
currentState = State::Delay; currentState = State::Delay;
} }
void ADSREnvelope::updateValues(int delay) noexcept
{
this->delay = delay + secondsToSamples(desc_->getDelay(midiState_, triggerVelocity_, delay));
this->attackStep = secondsToLinRate(desc_->getAttack(midiState_, triggerVelocity_, delay));
this->decayRate = secondsToExpRate(desc_->getDecay(midiState_, triggerVelocity_, delay));
this->releaseRate = secondsToExpRate(desc_->getRelease(midiState_, triggerVelocity_, delay));
this->hold = secondsToSamples(desc_->getHold(midiState_, triggerVelocity_, delay));
this->sustain = clamp(desc_->getSustain(midiState_, triggerVelocity_, delay), 0.0f, 1.0f);
this->start = clamp(desc_->getStart(midiState_, triggerVelocity_, delay), 0.0f, 1.0f);
sustainThreshold = this->sustain + config::virtuallyZero;
}
void ADSREnvelope::getBlock(absl::Span<Float> output) noexcept void ADSREnvelope::getBlock(absl::Span<Float> output) noexcept
{
if (desc_ && desc_->dynamic) {
int processed = 0;
int remaining = static_cast<int>(output.size());
while(remaining > 0) {
updateValues(processed);
int chunkSize = min(config::processChunkSize, remaining);
getBlockInternal(output.subspan(processed, chunkSize));
processed += chunkSize;
remaining -= chunkSize;
}
} else {
getBlockInternal(output);
}
}
void ADSREnvelope::getBlockInternal(absl::Span<Float> output) noexcept
{ {
State currentState = this->currentState; State currentState = this->currentState;
Float currentValue = this->currentValue; Float currentValue = this->currentValue;

View file

@ -18,7 +18,8 @@ class ADSREnvelope {
public: public:
using Float = float; using Float = float;
ADSREnvelope() = default; ADSREnvelope(const MidiState& state)
: midiState_(state) {}
/** /**
* @brief Resets the ADSR envelope given a Region, the current midi state, and a delay and * @brief Resets the ADSR envelope given a Region, the current midi state, and a delay and
* trigger velocity * trigger velocity
@ -29,10 +30,9 @@ public:
* @param delay * @param delay
* @param velocity * @param velocity
*/ */
void reset(const EGDescription& desc, const Region& region, const MidiState& state, int delay, float velocity, float sampleRate) noexcept; void reset(const EGDescription& desc, const Region& region, int delay, float velocity, float sampleRate) noexcept;
/** /**
* @brief Get a block of values for the envelope. This method tries hard to be efficient * @brief Get the next block of values for the envelope.
* and hopefully it is.
* *
* @param output * @param output
*/ */
@ -81,6 +81,8 @@ private:
int secondsToSamples(Float timeInSeconds) const noexcept; int secondsToSamples(Float timeInSeconds) const noexcept;
Float secondsToLinRate(Float timeInSeconds) const noexcept; Float secondsToLinRate(Float timeInSeconds) const noexcept;
Float secondsToExpRate(Float timeInSeconds) const noexcept; Float secondsToExpRate(Float timeInSeconds) const noexcept;
void updateValues(int delay = 0) noexcept;
void getBlockInternal(absl::Span<Float> output) noexcept;
enum class State { enum class State {
Delay, Delay,
@ -94,6 +96,9 @@ private:
}; };
State currentState { State::Done }; State currentState { State::Done };
Float currentValue { 0.0 }; Float currentValue { 0.0 };
const EGDescription* desc_ { nullptr };
const MidiState& midiState_;
float triggerVelocity_ { 0.0f };
int delay { 0 }; int delay { 0 };
Float attackStep { 0 }; Float attackStep { 0 };
Float decayRate { 0 }; Float decayRate { 0 };

View file

@ -148,6 +148,7 @@ FloatSpec egPercent { 0.0f, {0.0f, 100.0f}, kNormalizePercent|kPermissiveBounds
FloatSpec egPercentMod { 0.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds }; FloatSpec egPercentMod { 0.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds };
FloatSpec egDepth { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds }; FloatSpec egDepth { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds };
FloatSpec egVel2Depth { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds }; FloatSpec egVel2Depth { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds };
BoolSpec egDynamic { 0, {0, 1}, kEnforceBounds };
BoolSpec flexEGAmpeg { false, {0, 1}, kEnforceBounds }; BoolSpec flexEGAmpeg { false, {0, 1}, kEnforceBounds };
BoolSpec flexEGDynamic { 0, {0, 1}, kEnforceBounds }; BoolSpec flexEGDynamic { 0, {0, 1}, kEnforceBounds };
Int32Spec flexEGSustain { 0, {0, 100}, kEnforceLowerBound|kPermissiveUpperBound }; Int32Spec flexEGSustain { 0, {0, 100}, kEnforceLowerBound|kPermissiveUpperBound };

View file

@ -260,6 +260,7 @@ namespace Default
extern const OpcodeSpec<float> egPercentMod; extern const OpcodeSpec<float> egPercentMod;
extern const OpcodeSpec<float> egDepth; extern const OpcodeSpec<float> egDepth;
extern const OpcodeSpec<float> egVel2Depth; extern const OpcodeSpec<float> egVel2Depth;
extern const OpcodeSpec<bool> egDynamic;
extern const OpcodeSpec<bool> flexEGAmpeg; extern const OpcodeSpec<bool> flexEGAmpeg;
extern const OpcodeSpec<bool> flexEGDynamic; extern const OpcodeSpec<bool> flexEGDynamic;
extern const OpcodeSpec<int32_t> flexEGSustain; extern const OpcodeSpec<int32_t> flexEGSustain;

View file

@ -42,22 +42,6 @@ namespace sfz {
* *
*/ */
/**
* @brief If a cc switch exists for the value, returns the value with the CC modifier, otherwise returns the value alone.
*
* @param ccValues
* @param ccSwitch
* @param value
* @return float
*/
inline float ccSwitchedValue(const MidiState& state, const absl::optional<CCData<float>>& ccSwitch, float value) noexcept
{
if (ccSwitch)
return value + ccSwitch->data * state.getCCValue(ccSwitch->cc);
else
return value;
}
struct EGDescription { struct EGDescription {
EGDescription() = default; EGDescription() = default;
EGDescription(const EGDescription&) = default; EGDescription(const EGDescription&) = default;
@ -89,6 +73,7 @@ struct EGDescription {
CCMap<float> ccRelease; CCMap<float> ccRelease;
CCMap<float> ccStart; CCMap<float> ccStart;
CCMap<float> ccSustain; CCMap<float> ccSustain;
bool dynamic { false };
/** /**
* @brief Get the attack with possibly a CC modifier and a velocity modifier * @brief Get the attack with possibly a CC modifier and a velocity modifier
@ -97,12 +82,12 @@ struct EGDescription {
* @param velocity * @param velocity
* @return float * @return float
*/ */
float getAttack(const MidiState& state, float velocity) const noexcept float getAttack(const MidiState& state, float velocity, int delay = 0) const noexcept
{ {
ASSERT(velocity >= 0.0f && velocity <= 1.0f); ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { attack + velocity * vel2attack }; float returnedValue { attack + velocity * vel2attack };
for (auto& mod: ccAttack) { for (auto& mod: ccAttack) {
returnedValue += state.getCCValue(mod.cc) * mod.data; returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
} }
return returnedValue; return returnedValue;
} }
@ -113,12 +98,12 @@ struct EGDescription {
* @param velocity * @param velocity
* @return float * @return float
*/ */
float getDecay(const MidiState& state, float velocity) const noexcept float getDecay(const MidiState& state, float velocity, int delay = 0) const noexcept
{ {
ASSERT(velocity >= 0.0f && velocity <= 1.0f); ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { decay + velocity * vel2decay }; float returnedValue { decay + velocity * vel2decay };
for (auto& mod: ccDecay) { for (auto& mod: ccDecay) {
returnedValue += state.getCCValue(mod.cc) * mod.data; returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
} }
return returnedValue; return returnedValue;
} }
@ -129,12 +114,12 @@ struct EGDescription {
* @param velocity * @param velocity
* @return float * @return float
*/ */
float getDelay(const MidiState& state, float velocity) const noexcept float getDelay(const MidiState& state, float velocity, int delay = 0) const noexcept
{ {
ASSERT(velocity >= 0.0f && velocity <= 1.0f); ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { delay + velocity * vel2delay }; float returnedValue { this->delay + velocity * vel2delay };
for (auto& mod: ccDelay) { for (auto& mod: ccDelay) {
returnedValue += state.getCCValue(mod.cc) * mod.data; returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
} }
return returnedValue; return returnedValue;
} }
@ -145,12 +130,12 @@ struct EGDescription {
* @param velocity * @param velocity
* @return float * @return float
*/ */
float getHold(const MidiState& state, float velocity) const noexcept float getHold(const MidiState& state, float velocity, int delay = 0) const noexcept
{ {
ASSERT(velocity >= 0.0f && velocity <= 1.0f); ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { hold + velocity * vel2hold }; float returnedValue { hold + velocity * vel2hold };
for (auto& mod: ccHold) { for (auto& mod: ccHold) {
returnedValue += state.getCCValue(mod.cc) * mod.data; returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
} }
return returnedValue; return returnedValue;
} }
@ -161,12 +146,12 @@ struct EGDescription {
* @param velocity * @param velocity
* @return float * @return float
*/ */
float getRelease(const MidiState& state, float velocity) const noexcept float getRelease(const MidiState& state, float velocity, int delay = 0) const noexcept
{ {
ASSERT(velocity >= 0.0f && velocity <= 1.0f); ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { release + velocity * vel2release }; float returnedValue { release + velocity * vel2release };
for (auto& mod: ccRelease) { for (auto& mod: ccRelease) {
returnedValue += state.getCCValue(mod.cc) * mod.data; returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
} }
return returnedValue; return returnedValue;
} }
@ -177,12 +162,12 @@ struct EGDescription {
* @param velocity * @param velocity
* @return float * @return float
*/ */
float getStart(const MidiState& state, float velocity) const noexcept float getStart(const MidiState& state, float velocity, int delay = 0) const noexcept
{ {
UNUSED(velocity); UNUSED(velocity);
float returnedValue { start }; float returnedValue { start };
for (auto& mod: ccStart) { for (auto& mod: ccStart) {
returnedValue += state.getCCValue(mod.cc) * mod.data; returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
} }
return returnedValue; return returnedValue;
} }
@ -193,12 +178,12 @@ struct EGDescription {
* @param velocity * @param velocity
* @return float * @return float
*/ */
float getSustain(const MidiState& state, float velocity) const noexcept float getSustain(const MidiState& state, float velocity, int delay = 0) const noexcept
{ {
ASSERT(velocity >= 0.0f && velocity <= 1.0f); ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { sustain + velocity * vel2sustain }; float returnedValue { sustain + velocity * vel2sustain };
for (auto& mod: ccSustain) { for (auto& mod: ccSustain) {
returnedValue += state.getCCValue(mod.cc) * mod.data; returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
} }
return returnedValue; return returnedValue;
} }

View file

@ -1091,6 +1091,10 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg)
break; break;
case_any_eg("dynamic"):
eg.dynamic = opcode.read(Default::egDynamic);
break;
case hash("pitcheg_depth"): case hash("pitcheg_depth"):
getOrCreateConnection( getOrCreateConnection(
ModKey::createNXYZ(ModId::PitchEG, id), ModKey::createNXYZ(ModId::PitchEG, id),

View file

@ -71,7 +71,7 @@ Synth::Impl::Impl()
genController_.reset(new ControllerSource(resources_, voiceManager_)); genController_.reset(new ControllerSource(resources_, voiceManager_));
genLFO_.reset(new LFOSource(voiceManager_)); genLFO_.reset(new LFOSource(voiceManager_));
genFlexEnvelope_.reset(new FlexEnvelopeSource(voiceManager_)); genFlexEnvelope_.reset(new FlexEnvelopeSource(voiceManager_));
genADSREnvelope_.reset(new ADSREnvelopeSource(voiceManager_, midiState)); genADSREnvelope_.reset(new ADSREnvelopeSource(voiceManager_));
genChannelAftertouch_.reset(new ChannelAftertouchSource(voiceManager_, midiState)); genChannelAftertouch_.reset(new ChannelAftertouchSource(voiceManager_, midiState));
genPolyAftertouch_.reset(new PolyAftertouchSource(voiceManager_, midiState)); genPolyAftertouch_.reset(new PolyAftertouchSource(voiceManager_, midiState));
} }

View file

@ -1085,6 +1085,33 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
client.receive<'f'>(delay, path, region.amplitudeEG.vel2depth); client.receive<'f'>(delay, path, region.amplitudeEG.vel2depth);
} break; } break;
MATCH("/region&/ampeg_dynamic", "") {
GET_REGION_OR_BREAK(indices[0])
if (region.amplitudeEG.dynamic) {
client.receive<'T'>(delay, path, {});
} else {
client.receive<'F'>(delay, path, {});
}
} break;
MATCH("/region&/fileg_dynamic", "") {
GET_REGION_OR_BREAK(indices[0])
if (region.filterEG && region.filterEG->dynamic) {
client.receive<'T'>(delay, path, {});
} else {
client.receive<'F'>(delay, path, {});
}
} break;
MATCH("/region&/pitcheg_dynamic", "") {
GET_REGION_OR_BREAK(indices[0])
if (region.pitchEG && region.pitchEG->dynamic) {
client.receive<'T'>(delay, path, {});
} else {
client.receive<'F'>(delay, path, {});
}
} break;
MATCH("/region&/note_polyphony", "") { MATCH("/region&/note_polyphony", "") {
GET_REGION_OR_BREAK(indices[0]) GET_REGION_OR_BREAK(indices[0])
if (region.notePolyphony) { if (region.notePolyphony) {

View file

@ -271,7 +271,7 @@ struct Voice::Impl
std::unique_ptr<LFO> lfoPitch_; std::unique_ptr<LFO> lfoPitch_;
std::unique_ptr<LFO> lfoFilter_; std::unique_ptr<LFO> lfoFilter_;
ADSREnvelope egAmplitude_; ADSREnvelope egAmplitude_ { resources_.getMidiState() };
std::unique_ptr<ADSREnvelope> egPitch_; std::unique_ptr<ADSREnvelope> egPitch_;
std::unique_ptr<ADSREnvelope> egFilter_; std::unique_ptr<ADSREnvelope> egFilter_;
@ -1832,7 +1832,7 @@ void Voice::setPitchEGEnabledPerVoice(bool havePitchEG)
{ {
Impl& impl = *impl_; Impl& impl = *impl_;
if (havePitchEG) if (havePitchEG)
impl.egPitch_.reset(new ADSREnvelope); impl.egPitch_.reset(new ADSREnvelope(impl.resources_.getMidiState()));
else else
impl.egPitch_.reset(); impl.egPitch_.reset();
} }
@ -1841,7 +1841,7 @@ void Voice::setFilterEGEnabledPerVoice(bool haveFilterEG)
{ {
Impl& impl = *impl_; Impl& impl = *impl_;
if (haveFilterEG) if (haveFilterEG)
impl.egFilter_.reset(new ADSREnvelope); impl.egFilter_.reset(new ADSREnvelope(impl.resources_.getMidiState()));
else else
impl.egFilter_.reset(); impl.egFilter_.reset();
} }

View file

@ -13,8 +13,8 @@
namespace sfz { namespace sfz {
ADSREnvelopeSource::ADSREnvelopeSource(VoiceManager& manager, MidiState& state) ADSREnvelopeSource::ADSREnvelopeSource(VoiceManager& manager)
: voiceManager_(manager), midiState_(state) : voiceManager_(manager)
{ {
} }
@ -79,7 +79,7 @@ void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId,
const TriggerEvent& triggerEvent = voice->getTriggerEvent(); const TriggerEvent& triggerEvent = voice->getTriggerEvent();
const float sampleRate = voice->getSampleRate(); const float sampleRate = voice->getSampleRate();
eg->reset(*desc, *region, midiState_, delay, triggerEvent.value, sampleRate); eg->reset(*desc, *region, delay, triggerEvent.value, sampleRate);
} }
void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)

View file

@ -7,14 +7,13 @@
#pragma once #pragma once
#include "../ModGenerator.h" #include "../ModGenerator.h"
#include "../../VoiceManager.h" #include "../../VoiceManager.h"
#include "../../MidiState.h"
namespace sfz { namespace sfz {
class Synth; class Synth;
class ADSREnvelopeSource : public ModGenerator { class ADSREnvelopeSource : public ModGenerator {
public: public:
explicit ADSREnvelopeSource(VoiceManager &manager, MidiState& state); explicit ADSREnvelopeSource(VoiceManager &manager);
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override; void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override; void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
void cancelRelease(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override; void cancelRelease(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
@ -22,7 +21,6 @@ public:
private: private:
VoiceManager& voiceManager_; VoiceManager& voiceManager_;
MidiState& midiState_;
}; };
} // namespace sfz } // namespace sfz

View file

@ -45,7 +45,7 @@ TEST_CASE("[EGDescription] Delay range")
//REQUIRE(eg.getDelay(state, 127_norm) == 0.0f); //REQUIRE(eg.getDelay(state, 127_norm) == 0.0f);
state.ccEvent(0, 63, 127_norm); state.ccEvent(0, 63, 127_norm);
REQUIRE(eg.getDelay(state, 127_norm) == 1.0f); REQUIRE(eg.getDelay(state, 127_norm) == 1.0f);
REQUIRE(eg.getDelay(state, 0_norm) == 2.27f); REQUIRE(eg.getDelay(state, 0_norm, 1) == 2.27f);
//eg.ccDelay[63] = 127.0f; //eg.ccDelay[63] = 127.0f;
//REQUIRE(eg.getDelay(state, 0_norm) == 100.0f); //REQUIRE(eg.getDelay(state, 0_norm) == 100.0f);
eg.ccDelay[63] = 1.27f; eg.ccDelay[63] = 1.27f;

View file

@ -222,8 +222,8 @@ TEST_CASE("[Polyphony] Self-masking")
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<region> sample=*sine key=64 note_polyphony=2 <region> sample=*sine key=64 note_polyphony=2
)"); )");
synth.noteOn(0, 64, 63 ); synth.noteOn(0, 64, 63);
synth.noteOn(1, 64, 62 ); synth.noteOn(1, 64, 62);
synth.noteOn(2, 64, 64); synth.noteOn(2, 64, 64);
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing

View file

@ -3301,3 +3301,31 @@ TEST_CASE("[Values] Flex EGs CC")
}; };
REQUIRE(messageList == expected); REQUIRE(messageList == expected);
} }
TEST_CASE("[Values] Dynamic EGs")
{
Synth synth;
std::vector<std::string> messageList;
Client client(&messageList);
client.setReceiveCallback(&simpleMessageReceiver);
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
<region> sample=kick.wav
<region> sample=kick.wav ampeg_dynamic=1 pitcheg_dynamic=1 fileg_dynamic=1
)");
synth.dispatchMessage(client, 0, "/region0/ampeg_dynamic", "", nullptr);
synth.dispatchMessage(client, 0, "/region0/pitcheg_dynamic", "", nullptr);
synth.dispatchMessage(client, 0, "/region0/fileg_dynamic", "", nullptr);
synth.dispatchMessage(client, 0, "/region1/ampeg_dynamic", "", nullptr);
synth.dispatchMessage(client, 0, "/region1/pitcheg_dynamic", "", nullptr);
synth.dispatchMessage(client, 0, "/region1/fileg_dynamic", "", nullptr);
std::vector<std::string> expected {
"/region0/ampeg_dynamic,F : { }",
"/region0/pitcheg_dynamic,F : { }",
"/region0/fileg_dynamic,F : { }",
"/region1/ampeg_dynamic,T : { }",
"/region1/pitcheg_dynamic,T : { }",
"/region1/fileg_dynamic,T : { }",
};
REQUIRE(messageList == expected);
}