diff --git a/src/sfizz/ADSREnvelope.cpp b/src/sfizz/ADSREnvelope.cpp index 873069ce4..6167b6896 100644 --- a/src/sfizz/ADSREnvelope.cpp +++ b/src/sfizz/ADSREnvelope.cpp @@ -38,30 +38,54 @@ 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; + currentState = State::Delay; // Has to be before the update + updateValues(delay); releaseDelay = 0; - sustainThreshold = this->sustain + config::virtuallyZero; shouldRelease = false; freeRunning = ( (this->sustain <= Float(config::sustainFreeRunningThreshold)) || (region.loopMode == LoopMode::one_shot && region.isOscillator()) ); currentValue = this->start; - currentState = State::Delay; +} + +void ADSREnvelope::updateValues(int delay) noexcept +{ + 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)); + 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 ace1da7ba..a970a1d42 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 @@ class ADSREnvelope { * @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 @@ class ADSREnvelope { 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 @@ class ADSREnvelope { }; 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/Config.h b/src/sfizz/Config.h index 3631e6c7f..a458a8376 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/Defaults.cpp b/src/sfizz/Defaults.cpp index 40ce37c7f..18844897c 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 7d6c54fa9..db73157a5 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 26202ff14..fce5fc757 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/FilePool.cpp b/src/sfizz/FilePool.cpp index 6bd4e35ae..cce2941f9 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 f92e20eb6..98d99d219 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 4c816befb..8965746f4 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 3cbd41617..2d75dd656 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 4abaffb83..4d0d45ebe 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 16197c2fd..eaf947c60 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -167,13 +167,22 @@ class MidiState 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 e109384c2..745a44790 100644 --- a/src/sfizz/Oversampler.h +++ b/src/sfizz/Oversampler.h @@ -39,7 +39,7 @@ class Oversampler * @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 diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 344d7c6a4..de7661a12 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 1e200b58d..0bf23838d 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 3e6a87f2b..a9d97ed9c 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 14f148817..7be76b3b9 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 0ec5af8ed..703b5d433 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 436127a76..f43722b91 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 @@ class ADSREnvelopeSource : public ModGenerator { private: VoiceManager& voiceManager_; - MidiState& midiState_; }; } // namespace sfz diff --git a/tests/EGDescriptionT.cpp b/tests/EGDescriptionT.cpp index ba0902e21..5813ce972 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 0a966da9c..3034372ac 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 970a259ed..df7e64d6b 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); +}