From e79e5cf04d0a59a72c2899f20431e3e0b8ad754b Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sun, 27 Jun 2021 12:13:44 +0200 Subject: [PATCH 1/3] Flex envelope dynamic update --- src/sfizz/Config.h | 3 ++- src/sfizz/FilePool.cpp | 2 +- src/sfizz/FlexEGDescription.cpp | 8 ++++---- src/sfizz/FlexEGDescription.h | 4 ++-- src/sfizz/FlexEnvelope.cpp | 22 +++++++++++++++++----- src/sfizz/MidiState.cpp | 11 +++++++++++ src/sfizz/MidiState.h | 11 ++++++++++- src/sfizz/Oversampler.h | 2 +- 8 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 3631e6c7..a458a837 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -70,7 +70,8 @@ namespace config { constexpr float powerFollowerReleaseTime { 200e-3f }; constexpr uint16_t numCCs { 512 }; constexpr int maxCurves { 256 }; - constexpr int chunkSize { 1024 }; + constexpr int fileChunkSize { 1024 }; + constexpr int processChunkSize { 16 }; constexpr unsigned int defaultAlignment { 16 }; constexpr int filtersInPool { maxVoices * 2 }; constexpr int excessFileFrames { 64 }; diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index 6bd4e35a..cce2941f 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -101,7 +101,7 @@ void streamFromFile(sfz::AudioReader& reader, sfz::FileAudioBuffer& output, std: { const auto numFrames = static_cast(reader.frames()); const auto numChannels = reader.channels(); - const auto chunkSize = static_cast(sfz::config::chunkSize); + const auto chunkSize = static_cast(sfz::config::fileChunkSize); output.reset(); output.addChannels(reader.channels()); diff --git a/src/sfizz/FlexEGDescription.cpp b/src/sfizz/FlexEGDescription.cpp index f92e20eb..98d99d21 100644 --- a/src/sfizz/FlexEGDescription.cpp +++ b/src/sfizz/FlexEGDescription.cpp @@ -86,19 +86,19 @@ void FlexEGs::clearUnusedCurves() } /// -float FlexEGPoint::getTime(const MidiState& state) const noexcept +float FlexEGPoint::getTime(const MidiState& state, int delay) const noexcept { float returnedValue { time }; for (const CCData& mod : ccTime) - returnedValue += state.getCCValue(mod.cc) * mod.data; + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; return returnedValue; } -float FlexEGPoint::getLevel(const MidiState& state) const noexcept +float FlexEGPoint::getLevel(const MidiState& state, int delay) const noexcept { float returnedValue { level }; for (const CCData& mod : ccLevel) - returnedValue += state.getCCValue(mod.cc) * mod.data; + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; return returnedValue; } diff --git a/src/sfizz/FlexEGDescription.h b/src/sfizz/FlexEGDescription.h index 4c816bef..8965746f 100644 --- a/src/sfizz/FlexEGDescription.h +++ b/src/sfizz/FlexEGDescription.h @@ -26,8 +26,8 @@ struct FlexEGPoint { CCMap ccTime; CCMap ccLevel; - float getTime(const MidiState& state) const noexcept; - float getLevel(const MidiState& state) const noexcept; + float getTime(const MidiState& state, int delay = 0) const noexcept; + float getLevel(const MidiState& state, int delay = 0) const noexcept; void setShape(float shape); float shape() const noexcept { return shape_; } diff --git a/src/sfizz/FlexEnvelope.cpp b/src/sfizz/FlexEnvelope.cpp index 3cbd4161..2d75dd65 100644 --- a/src/sfizz/FlexEnvelope.cpp +++ b/src/sfizz/FlexEnvelope.cpp @@ -57,7 +57,7 @@ struct FlexEnvelope::Impl { void process(absl::Span out); bool advanceToStage(unsigned stageNumber); bool advanceToNextStage(); - void updateCurrentTimeAndLevel(); + void updateCurrentTimeAndLevel(int delay = 0); }; FlexEnvelope::FlexEnvelope(Resources &resources) @@ -155,7 +155,19 @@ bool FlexEnvelope::isFinished() const noexcept void FlexEnvelope::process(absl::Span out) { Impl& impl = *impl_; - impl.process(out); + if (impl.desc_->dynamic) { + int processed = 0; + int remaining = static_cast(out.size()); + while(remaining > 0) { + impl.updateCurrentTimeAndLevel(processed); + int chunkSize = min(config::processChunkSize, remaining); + impl.process(out.subspan(processed, chunkSize)); + processed += chunkSize; + remaining -= chunkSize; + } + } else { + impl.process(out); + } } void FlexEnvelope::Impl::process(absl::Span out) @@ -271,7 +283,7 @@ bool FlexEnvelope::Impl::advanceToNextStage() return advanceToStage(currentStageNumber_ + 1); } -void FlexEnvelope::Impl::updateCurrentTimeAndLevel() +void FlexEnvelope::Impl::updateCurrentTimeAndLevel(int delay) { const FlexEGDescription& desc = *desc_; if (currentStageNumber_ >= desc.points.size()) @@ -279,8 +291,8 @@ void FlexEnvelope::Impl::updateCurrentTimeAndLevel() const FlexEGPoint& point = desc.points[currentStageNumber_]; const MidiState& midiState = resources_->getMidiState(); - stageTargetLevel_ = point.getLevel(midiState); - stageTime_ = point.getTime(midiState); + stageTargetLevel_ = point.getLevel(midiState, delay); + stageTime_ = point.getTime(midiState, delay); } } // namespace sfz diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 4abaffb8..4d0d45eb 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -202,6 +202,17 @@ float sfz::MidiState::getCCValue(int ccNumber) const noexcept return ccEvents[ccNumber].back().value; } +float sfz::MidiState::getCCValueAt(int ccNumber, int delay) const noexcept +{ + ASSERT(ccNumber >= 0 && ccNumber < config::numCCs); + const auto ccEvent = absl::c_lower_bound( + ccEvents[ccNumber], delay, MidiEventDelayComparator {}); + if (ccEvent != ccEvents[ccNumber].end()) + return ccEvent->value; + else + return ccEvents[ccNumber].back().value; +} + void sfz::MidiState::reset() noexcept { for (auto& velocity: lastNoteVelocities) diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index 16197c2f..eaf947c6 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -167,13 +167,22 @@ public: bool isNotePressed(int noteNumber) const noexcept { return noteStates[noteNumber]; } /** - * @brief Get the CC value for CC number + * @brief Get the last CC value for CC number * * @param ccNumber * @return float */ float getCCValue(int ccNumber) const noexcept; + /** + * @brief Get the CC value for CC number + * + * @param ccNumber + * @param delay + * @return float + */ + float getCCValueAt(int ccNumber, int delay) const noexcept; + /** * @brief Reset the midi state (does not impact the last note on time) * diff --git a/src/sfizz/Oversampler.h b/src/sfizz/Oversampler.h index e109384c..745a4479 100644 --- a/src/sfizz/Oversampler.h +++ b/src/sfizz/Oversampler.h @@ -39,7 +39,7 @@ public: * @param factor * @param chunkSize */ - Oversampler(Oversampling factor = Oversampling::x1, size_t chunkSize = config::chunkSize); + Oversampler(Oversampling factor = Oversampling::x1, size_t chunkSize = config::fileChunkSize); /** * @brief Stream the oversampling of an input AudioBuffer into an output * one, possibly signaling the caller along the way of the number of From d2ae2a0fe0713669bb9648987af9f09ed66f723a Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sun, 27 Jun 2021 16:51:29 +0200 Subject: [PATCH 2/3] Add dynamic to the basic envelope --- src/sfizz/ADSREnvelope.cpp | 44 ++++++++++++----- src/sfizz/ADSREnvelope.h | 13 +++-- src/sfizz/Defaults.cpp | 1 + src/sfizz/Defaults.h | 1 + src/sfizz/EGDescription.h | 47 +++++++------------ src/sfizz/Region.cpp | 4 ++ src/sfizz/Synth.cpp | 2 +- src/sfizz/SynthMessaging.cpp | 27 +++++++++++ src/sfizz/Voice.cpp | 6 +-- .../modulations/sources/ADSREnvelope.cpp | 6 +-- src/sfizz/modulations/sources/ADSREnvelope.h | 4 +- tests/EGDescriptionT.cpp | 2 +- tests/PolyphonyT.cpp | 4 +- tests/RegionValuesT.cpp | 28 +++++++++++ 14 files changed, 130 insertions(+), 59 deletions(-) diff --git a/src/sfizz/ADSREnvelope.cpp b/src/sfizz/ADSREnvelope.cpp index 873069ce..cda4ba31 100644 --- a/src/sfizz/ADSREnvelope.cpp +++ b/src/sfizz/ADSREnvelope.cpp @@ -38,20 +38,13 @@ Float ADSREnvelope::secondsToExpRate(Float timeInSeconds) const noexcept 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->delay = delay + secondsToSamples(desc.getDelay(state, velocity)); - this->attackStep = secondsToLinRate(desc.getAttack(state, velocity)); - 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); - + desc_ = &desc; + triggerVelocity_ = velocity; + updateValues(delay); releaseDelay = 0; - sustainThreshold = this->sustain + config::virtuallyZero; shouldRelease = false; freeRunning = ( (this->sustain <= Float(config::sustainFreeRunningThreshold)) @@ -61,7 +54,36 @@ void ADSREnvelope::reset(const EGDescription& desc, const Region& region, const 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 output) noexcept +{ + if (desc_ && desc_->dynamic) { + int processed = 0; + int remaining = static_cast(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 output) noexcept { State currentState = this->currentState; Float currentValue = this->currentValue; diff --git a/src/sfizz/ADSREnvelope.h b/src/sfizz/ADSREnvelope.h index ace1da7b..a970a1d4 100644 --- a/src/sfizz/ADSREnvelope.h +++ b/src/sfizz/ADSREnvelope.h @@ -18,7 +18,8 @@ class ADSREnvelope { public: 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 * trigger velocity @@ -29,10 +30,9 @@ public: * @param delay * @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 - * and hopefully it is. + * @brief Get the next block of values for the envelope. * * @param output */ @@ -81,6 +81,8 @@ private: int secondsToSamples(Float timeInSeconds) const noexcept; Float secondsToLinRate(Float timeInSeconds) const noexcept; Float secondsToExpRate(Float timeInSeconds) const noexcept; + void updateValues(int delay = 0) noexcept; + void getBlockInternal(absl::Span output) noexcept; enum class State { Delay, @@ -94,6 +96,9 @@ private: }; State currentState { State::Done }; Float currentValue { 0.0 }; + const EGDescription* desc_ { nullptr }; + const MidiState& midiState_; + float triggerVelocity_ { 0.0f }; int delay { 0 }; Float attackStep { 0 }; Float decayRate { 0 }; diff --git a/src/sfizz/Defaults.cpp b/src/sfizz/Defaults.cpp index 40ce37c7..18844897 100644 --- a/src/sfizz/Defaults.cpp +++ b/src/sfizz/Defaults.cpp @@ -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 egDepth { 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 flexEGDynamic { 0, {0, 1}, kEnforceBounds }; Int32Spec flexEGSustain { 0, {0, 100}, kEnforceLowerBound|kPermissiveUpperBound }; diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 7d6c54fa..db73157a 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -260,6 +260,7 @@ namespace Default extern const OpcodeSpec egPercentMod; extern const OpcodeSpec egDepth; extern const OpcodeSpec egVel2Depth; + extern const OpcodeSpec egDynamic; extern const OpcodeSpec flexEGAmpeg; extern const OpcodeSpec flexEGDynamic; extern const OpcodeSpec flexEGSustain; diff --git a/src/sfizz/EGDescription.h b/src/sfizz/EGDescription.h index 26202ff1..fce5fc75 100644 --- a/src/sfizz/EGDescription.h +++ b/src/sfizz/EGDescription.h @@ -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>& ccSwitch, float value) noexcept -{ - if (ccSwitch) - return value + ccSwitch->data * state.getCCValue(ccSwitch->cc); - else - return value; -} - struct EGDescription { EGDescription() = default; EGDescription(const EGDescription&) = default; @@ -89,6 +73,7 @@ struct EGDescription { CCMap ccRelease; CCMap ccStart; CCMap ccSustain; + bool dynamic { false }; /** * @brief Get the attack with possibly a CC modifier and a velocity modifier @@ -97,12 +82,12 @@ struct EGDescription { * @param velocity * @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); float returnedValue { attack + velocity * vel2attack }; for (auto& mod: ccAttack) { - returnedValue += state.getCCValue(mod.cc) * mod.data; + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; } return returnedValue; } @@ -113,12 +98,12 @@ struct EGDescription { * @param velocity * @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); float returnedValue { decay + velocity * vel2decay }; for (auto& mod: ccDecay) { - returnedValue += state.getCCValue(mod.cc) * mod.data; + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; } return returnedValue; } @@ -129,12 +114,12 @@ struct EGDescription { * @param velocity * @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); - float returnedValue { delay + velocity * vel2delay }; + float returnedValue { this->delay + velocity * vel2delay }; for (auto& mod: ccDelay) { - returnedValue += state.getCCValue(mod.cc) * mod.data; + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; } return returnedValue; } @@ -145,12 +130,12 @@ struct EGDescription { * @param velocity * @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); float returnedValue { hold + velocity * vel2hold }; for (auto& mod: ccHold) { - returnedValue += state.getCCValue(mod.cc) * mod.data; + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; } return returnedValue; } @@ -161,12 +146,12 @@ struct EGDescription { * @param velocity * @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); float returnedValue { release + velocity * vel2release }; for (auto& mod: ccRelease) { - returnedValue += state.getCCValue(mod.cc) * mod.data; + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; } return returnedValue; } @@ -177,12 +162,12 @@ struct EGDescription { * @param velocity * @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); float returnedValue { start }; for (auto& mod: ccStart) { - returnedValue += state.getCCValue(mod.cc) * mod.data; + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; } return returnedValue; } @@ -193,12 +178,12 @@ struct EGDescription { * @param velocity * @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); float returnedValue { sustain + velocity * vel2sustain }; for (auto& mod: ccSustain) { - returnedValue += state.getCCValue(mod.cc) * mod.data; + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; } return returnedValue; } diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 344d7c6a..de7661a1 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1091,6 +1091,10 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) break; + case_any_eg("dynamic"): + eg.dynamic = opcode.read(Default::egDynamic); + break; + case hash("pitcheg_depth"): getOrCreateConnection( ModKey::createNXYZ(ModId::PitchEG, id), diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 1e200b58..0bf23838 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -71,7 +71,7 @@ Synth::Impl::Impl() genController_.reset(new ControllerSource(resources_, voiceManager_)); genLFO_.reset(new LFOSource(voiceManager_)); genFlexEnvelope_.reset(new FlexEnvelopeSource(voiceManager_)); - genADSREnvelope_.reset(new ADSREnvelopeSource(voiceManager_, midiState)); + genADSREnvelope_.reset(new ADSREnvelopeSource(voiceManager_)); genChannelAftertouch_.reset(new ChannelAftertouchSource(voiceManager_, midiState)); genPolyAftertouch_.reset(new PolyAftertouchSource(voiceManager_, midiState)); } diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index 3e6a87f2..a9d97ed9 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -1085,6 +1085,33 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co client.receive<'f'>(delay, path, region.amplitudeEG.vel2depth); } 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", "") { GET_REGION_OR_BREAK(indices[0]) if (region.notePolyphony) { diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 14f14881..7be76b3b 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -271,7 +271,7 @@ struct Voice::Impl std::unique_ptr lfoPitch_; std::unique_ptr lfoFilter_; - ADSREnvelope egAmplitude_; + ADSREnvelope egAmplitude_ { resources_.getMidiState() }; std::unique_ptr egPitch_; std::unique_ptr egFilter_; @@ -1832,7 +1832,7 @@ void Voice::setPitchEGEnabledPerVoice(bool havePitchEG) { Impl& impl = *impl_; if (havePitchEG) - impl.egPitch_.reset(new ADSREnvelope); + impl.egPitch_.reset(new ADSREnvelope(impl.resources_.getMidiState())); else impl.egPitch_.reset(); } @@ -1841,7 +1841,7 @@ void Voice::setFilterEGEnabledPerVoice(bool haveFilterEG) { Impl& impl = *impl_; if (haveFilterEG) - impl.egFilter_.reset(new ADSREnvelope); + impl.egFilter_.reset(new ADSREnvelope(impl.resources_.getMidiState())); else impl.egFilter_.reset(); } diff --git a/src/sfizz/modulations/sources/ADSREnvelope.cpp b/src/sfizz/modulations/sources/ADSREnvelope.cpp index 0ec5af8e..703b5d43 100644 --- a/src/sfizz/modulations/sources/ADSREnvelope.cpp +++ b/src/sfizz/modulations/sources/ADSREnvelope.cpp @@ -13,8 +13,8 @@ namespace sfz { -ADSREnvelopeSource::ADSREnvelopeSource(VoiceManager& manager, MidiState& state) - : voiceManager_(manager), midiState_(state) +ADSREnvelopeSource::ADSREnvelopeSource(VoiceManager& manager) + : voiceManager_(manager) { } @@ -79,7 +79,7 @@ void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId voiceId, const TriggerEvent& triggerEvent = voice->getTriggerEvent(); 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 voiceId, unsigned delay) diff --git a/src/sfizz/modulations/sources/ADSREnvelope.h b/src/sfizz/modulations/sources/ADSREnvelope.h index 436127a7..f43722b9 100644 --- a/src/sfizz/modulations/sources/ADSREnvelope.h +++ b/src/sfizz/modulations/sources/ADSREnvelope.h @@ -7,14 +7,13 @@ #pragma once #include "../ModGenerator.h" #include "../../VoiceManager.h" -#include "../../MidiState.h" namespace sfz { class Synth; class ADSREnvelopeSource : public ModGenerator { public: - explicit ADSREnvelopeSource(VoiceManager &manager, MidiState& state); + explicit ADSREnvelopeSource(VoiceManager &manager); void init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; void release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; void cancelRelease(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; @@ -22,7 +21,6 @@ public: private: VoiceManager& voiceManager_; - MidiState& midiState_; }; } // namespace sfz diff --git a/tests/EGDescriptionT.cpp b/tests/EGDescriptionT.cpp index ba0902e2..5813ce97 100644 --- a/tests/EGDescriptionT.cpp +++ b/tests/EGDescriptionT.cpp @@ -45,7 +45,7 @@ TEST_CASE("[EGDescription] Delay range") //REQUIRE(eg.getDelay(state, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); 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; //REQUIRE(eg.getDelay(state, 0_norm) == 100.0f); eg.ccDelay[63] = 1.27f; diff --git a/tests/PolyphonyT.cpp b/tests/PolyphonyT.cpp index 0a966da9..3034372a 100644 --- a/tests/PolyphonyT.cpp +++ b/tests/PolyphonyT.cpp @@ -222,8 +222,8 @@ TEST_CASE("[Polyphony] Self-masking") synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( sample=*sine key=64 note_polyphony=2 )"); - synth.noteOn(0, 64, 63 ); - synth.noteOn(1, 64, 62 ); + synth.noteOn(0, 64, 63); + synth.noteOn(1, 64, 62); synth.noteOn(2, 64, 64); synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing diff --git a/tests/RegionValuesT.cpp b/tests/RegionValuesT.cpp index 970a259e..df7e64d6 100644 --- a/tests/RegionValuesT.cpp +++ b/tests/RegionValuesT.cpp @@ -3301,3 +3301,31 @@ TEST_CASE("[Values] Flex EGs CC") }; REQUIRE(messageList == expected); } + +TEST_CASE("[Values] Dynamic EGs") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + 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 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); +} From 788a4301a10e2640dfed6b6d416dceabc768163a Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sun, 27 Jun 2021 19:35:20 +0200 Subject: [PATCH 3/3] Don't update the delay after the phase has passed --- src/sfizz/ADSREnvelope.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sfizz/ADSREnvelope.cpp b/src/sfizz/ADSREnvelope.cpp index cda4ba31..6167b689 100644 --- a/src/sfizz/ADSREnvelope.cpp +++ b/src/sfizz/ADSREnvelope.cpp @@ -43,6 +43,7 @@ void ADSREnvelope::reset(const EGDescription& desc, const Region& region, int de this->sampleRate = sampleRate; desc_ = &desc; triggerVelocity_ = velocity; + currentState = State::Delay; // Has to be before the update updateValues(delay); releaseDelay = 0; shouldRelease = false; @@ -51,12 +52,13 @@ void ADSREnvelope::reset(const EGDescription& desc, const Region& region, int de || (region.loopMode == LoopMode::one_shot && region.isOscillator()) ); currentValue = this->start; - currentState = State::Delay; } void ADSREnvelope::updateValues(int delay) noexcept { - this->delay = delay + secondsToSamples(desc_->getDelay(midiState_, triggerVelocity_, delay)); + if (currentState == State::Delay) + 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));