diff --git a/src/sfizz/ADSREnvelope.cpp b/src/sfizz/ADSREnvelope.cpp index 873069ce..6167b689 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 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/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/Defaults.cpp b/src/sfizz/Defaults.cpp index 6077a72f..bb17b1bf 100644 --- a/src/sfizz/Defaults.cpp +++ b/src/sfizz/Defaults.cpp @@ -151,6 +151,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 b06b0488..dccba31c 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -263,6 +263,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/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 39a387af..2a6c3b25 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) @@ -154,7 +154,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) @@ -268,7 +280,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()) @@ -276,8 +288,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 diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 685e3811..e7481101 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1143,6 +1143,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 fcc11f51..91689c7b 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 36f4bc03..166e10f5 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -1125,6 +1125,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 7841cacd..00bc5840 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -272,7 +272,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_; @@ -1835,7 +1835,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(); } @@ -1844,7 +1844,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 f07e6c91..50e8265a 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 15c7f860..28e5e0a9 100644 --- a/tests/RegionValuesT.cpp +++ b/tests/RegionValuesT.cpp @@ -3374,3 +3374,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); +}