From a1a68e115c00ff598bdbba58a7411d2c16954118 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 23 Sep 2020 22:16:26 +0200 Subject: [PATCH 1/8] Move EG v1 opcode processing in separate routine --- src/sfizz/Region.cpp | 211 +++++++++++++++++++++++++------------------ src/sfizz/Region.h | 10 ++ 2 files changed, 135 insertions(+), 86 deletions(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index a98dac2a..a95734b6 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1164,111 +1164,26 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) // Amplitude Envelope case hash("ampeg_attack"): - setValueFromOpcode(opcode, amplitudeEG.attack, Default::egTimeRange); - break; case hash("ampeg_decay"): - setValueFromOpcode(opcode, amplitudeEG.decay, Default::egTimeRange); - break; case hash("ampeg_delay"): - setValueFromOpcode(opcode, amplitudeEG.delay, Default::egTimeRange); - break; case hash("ampeg_hold"): - setValueFromOpcode(opcode, amplitudeEG.hold, Default::egTimeRange); - break; case hash("ampeg_release"): - setValueFromOpcode(opcode, amplitudeEG.release, Default::egTimeRange); - break; case hash("ampeg_start"): - setValueFromOpcode(opcode, amplitudeEG.start, Default::egPercentRange); - break; case hash("ampeg_sustain"): - setValueFromOpcode(opcode, amplitudeEG.sustain, Default::egPercentRange); - break; case hash("ampeg_vel&attack"): - if (opcode.parameters.front() != 2) - return false; // Was not vel2... - setValueFromOpcode(opcode, amplitudeEG.vel2attack, Default::egOnCCTimeRange); - break; case hash("ampeg_vel&decay"): - if (opcode.parameters.front() != 2) - return false; // Was not vel2... - setValueFromOpcode(opcode, amplitudeEG.vel2decay, Default::egOnCCTimeRange); - break; case hash("ampeg_vel&delay"): - if (opcode.parameters.front() != 2) - return false; // Was not vel2... - setValueFromOpcode(opcode, amplitudeEG.vel2delay, Default::egOnCCTimeRange); - break; case hash("ampeg_vel&hold"): - if (opcode.parameters.front() != 2) - return false; // Was not vel2... - setValueFromOpcode(opcode, amplitudeEG.vel2hold, Default::egOnCCTimeRange); - break; case hash("ampeg_vel&release"): - if (opcode.parameters.front() != 2) - return false; // Was not vel2... - setValueFromOpcode(opcode, amplitudeEG.vel2release, Default::egOnCCTimeRange); - break; case hash("ampeg_vel&sustain"): - if (opcode.parameters.front() != 2) - return false; // Was not vel2... - setValueFromOpcode(opcode, amplitudeEG.vel2sustain, Default::egOnCCPercentRange); - break; case hash("ampeg_attack_oncc&"): // also ampeg_attackcc& - if (opcode.parameters.back() >= config::numCCs) - return false; - - if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange)) - amplitudeEG.ccAttack[opcode.parameters.back()] = *value; - - break; case hash("ampeg_decay_oncc&"): // also ampeg_decaycc& - if (opcode.parameters.back() >= config::numCCs) - return false; - - if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange)) - amplitudeEG.ccDecay[opcode.parameters.back()] = *value; - - break; case hash("ampeg_delay_oncc&"): // also ampeg_delaycc& - if (opcode.parameters.back() >= config::numCCs) - return false; - - if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange)) - amplitudeEG.ccDelay[opcode.parameters.back()] = *value; - - break; case hash("ampeg_hold_oncc&"): // also ampeg_holdcc& - if (opcode.parameters.back() >= config::numCCs) - return false; - - if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange)) - amplitudeEG.ccHold[opcode.parameters.back()] = *value; - - break; case hash("ampeg_release_oncc&"): // also ampeg_releasecc& - if (opcode.parameters.back() >= config::numCCs) - return false; - - if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange)) - amplitudeEG.ccRelease[opcode.parameters.back()] = *value; - - break; case hash("ampeg_start_oncc&"): // also ampeg_startcc& - if (opcode.parameters.back() >= config::numCCs) - return false; - - if (auto value = readOpcode(opcode.value, Default::egOnCCPercentRange)) - amplitudeEG.ccStart[opcode.parameters.back()] = *value; - - break; case hash("ampeg_sustain_oncc&"): // also ampeg_sustaincc& - if (opcode.parameters.back() >= config::numCCs) - return false; - - if (auto value = readOpcode(opcode.value, Default::egOnCCPercentRange)) - amplitudeEG.ccSustain[opcode.parameters.back()] = *value; - + parseEGopcode(opcode, amplitudeEG); break; // Flex envelopes @@ -1369,6 +1284,130 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return true; } +bool sfz::Region::parseEGopcode(const Opcode& opcode, EGDescription& eg) +{ + #define case_any_eg(param) \ + case hash("ampeg_" param): \ + case hash("pitcheg_" param): \ + case hash("fileg_" param) \ + + switch (opcode.lettersOnlyHash) { + case_any_eg("attack"): + setValueFromOpcode(opcode, eg.attack, Default::egTimeRange); + break; + case_any_eg("decay"): + setValueFromOpcode(opcode, eg.decay, Default::egTimeRange); + break; + case_any_eg("delay"): + setValueFromOpcode(opcode, eg.delay, Default::egTimeRange); + break; + case_any_eg("hold"): + setValueFromOpcode(opcode, eg.hold, Default::egTimeRange); + break; + case_any_eg("release"): + setValueFromOpcode(opcode, eg.release, Default::egTimeRange); + break; + case_any_eg("start"): + setValueFromOpcode(opcode, eg.start, Default::egPercentRange); + break; + case_any_eg("sustain"): + setValueFromOpcode(opcode, eg.sustain, Default::egPercentRange); + break; + case_any_eg("vel&attack"): + if (opcode.parameters.front() != 2) + return false; // Was not vel2... + setValueFromOpcode(opcode, eg.vel2attack, Default::egOnCCTimeRange); + break; + case_any_eg("vel&decay"): + if (opcode.parameters.front() != 2) + return false; // Was not vel2... + setValueFromOpcode(opcode, eg.vel2decay, Default::egOnCCTimeRange); + break; + case_any_eg("vel&delay"): + if (opcode.parameters.front() != 2) + return false; // Was not vel2... + setValueFromOpcode(opcode, eg.vel2delay, Default::egOnCCTimeRange); + break; + case_any_eg("vel&hold"): + if (opcode.parameters.front() != 2) + return false; // Was not vel2... + setValueFromOpcode(opcode, eg.vel2hold, Default::egOnCCTimeRange); + break; + case_any_eg("vel&release"): + if (opcode.parameters.front() != 2) + return false; // Was not vel2... + setValueFromOpcode(opcode, eg.vel2release, Default::egOnCCTimeRange); + break; + case_any_eg("vel&sustain"): + if (opcode.parameters.front() != 2) + return false; // Was not vel2... + setValueFromOpcode(opcode, eg.vel2sustain, Default::egOnCCPercentRange); + break; + case_any_eg("attack_oncc&"): // also attackcc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange)) + eg.ccAttack[opcode.parameters.back()] = *value; + + break; + case_any_eg("decay_oncc&"): // also decaycc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange)) + eg.ccDecay[opcode.parameters.back()] = *value; + + break; + case_any_eg("delay_oncc&"): // also delaycc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange)) + eg.ccDelay[opcode.parameters.back()] = *value; + + break; + case_any_eg("hold_oncc&"): // also holdcc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange)) + eg.ccHold[opcode.parameters.back()] = *value; + + break; + case_any_eg("release_oncc&"): // also releasecc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange)) + eg.ccRelease[opcode.parameters.back()] = *value; + + break; + case_any_eg("start_oncc&"): // also startcc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + if (auto value = readOpcode(opcode.value, Default::egOnCCPercentRange)) + eg.ccStart[opcode.parameters.back()] = *value; + + break; + case_any_eg("sustain_oncc&"): // also sustaincc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + if (auto value = readOpcode(opcode.value, Default::egOnCCPercentRange)) + eg.ccSustain[opcode.parameters.back()] = *value; + + break; + default: + return false; + } + + return true; + + #undef case_any_eg +} + bool sfz::Region::processGenericCc(const Opcode& opcode, Range range, const ModKey& target) { if (!opcode.isAnyCcN()) diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index d11e87a9..59439b3d 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -256,6 +256,16 @@ struct Region { * @return false */ bool parseOpcode(const Opcode& opcode); + /** + * @brief Parse a opcode which is specific to a particular SFZv1 EG: + * ampeg, pitcheg, fileg. + * + * @param opcode + * @param eg + * @return true if the opcode was properly read and stored. + * @return false + */ + bool parseEGopcode(const Opcode& opcode, EGDescription& eg); /** * @brief Process a generic CC opcode, and fill the modulation parameters. * From 23b7f507600d47ae59e36aa382c791049cfe9d1b Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 23 Sep 2020 22:36:44 +0200 Subject: [PATCH 2/8] Copy and move assignments for EGDescription --- src/sfizz/CCMap.h | 4 +++- src/sfizz/EGDescription.h | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/sfizz/CCMap.h b/src/sfizz/CCMap.h index dce9813f..4fcc13f8 100644 --- a/src/sfizz/CCMap.h +++ b/src/sfizz/CCMap.h @@ -38,6 +38,8 @@ public: CCMap(CCMap&&) = default; CCMap(const CCMap&) = default; ~CCMap() = default; + CCMap& operator=(CCMap&&) = default; + CCMap& operator=(const CCMap&) = default; /** * @brief Returns the held object at the index, or a default value if not present @@ -105,7 +107,7 @@ private: // typename std::vector>::iterator begin() { return container.begin(); } // typename std::vector>::iterator end() { return container.end(); } - const ValueType defaultValue; + ValueType defaultValue; std::vector> container; LEAK_DETECTOR(CCMap); }; diff --git a/src/sfizz/EGDescription.h b/src/sfizz/EGDescription.h index 683b4d51..5cfe0d94 100644 --- a/src/sfizz/EGDescription.h +++ b/src/sfizz/EGDescription.h @@ -63,6 +63,8 @@ struct EGDescription { EGDescription(const EGDescription&) = default; EGDescription(EGDescription&&) = default; ~EGDescription() = default; + EGDescription& operator=(const EGDescription&) = default; + EGDescription& operator=(EGDescription&&) = default; float attack { Default::attack }; float decay { Default::decay }; From 0f2bb804347e83aded33c3aa531084b77064387a Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 23 Sep 2020 22:41:12 +0200 Subject: [PATCH 3/8] Add pitcheg and fileg opcodes, and modulation key --- src/sfizz/Region.cpp | 65 ++++++++++++++++++++++++++++++++ src/sfizz/Region.h | 16 ++++++-- src/sfizz/Synth.cpp | 32 +++++++++------- src/sfizz/modulations/ModId.cpp | 4 ++ src/sfizz/modulations/ModId.h | 2 + src/sfizz/modulations/ModKey.cpp | 4 ++ 6 files changed, 106 insertions(+), 17 deletions(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index a95734b6..7ca3e928 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1186,6 +1186,58 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) parseEGopcode(opcode, amplitudeEG); break; + case hash("pitcheg_attack"): + case hash("pitcheg_decay"): + case hash("pitcheg_delay"): + case hash("pitcheg_hold"): + case hash("pitcheg_release"): + case hash("pitcheg_start"): + case hash("pitcheg_sustain"): + case hash("pitcheg_vel&attack"): + case hash("pitcheg_vel&decay"): + case hash("pitcheg_vel&delay"): + case hash("pitcheg_vel&hold"): + case hash("pitcheg_vel&release"): + case hash("pitcheg_vel&sustain"): + case hash("pitcheg_attack_oncc&"): // also pitcheg_attackcc& + case hash("pitcheg_decay_oncc&"): // also pitcheg_decaycc& + case hash("pitcheg_delay_oncc&"): // also pitcheg_delaycc& + case hash("pitcheg_hold_oncc&"): // also pitcheg_holdcc& + case hash("pitcheg_release_oncc&"): // also pitcheg_releasecc& + case hash("pitcheg_start_oncc&"): // also pitcheg_startcc& + case hash("pitcheg_sustain_oncc&"): // also pitcheg_sustaincc& + if (parseEGopcode(opcode, pitchEG)) + getOrCreateConnection( + ModKey::createNXYZ(ModId::PitchEG, id), + ModKey::createNXYZ(ModId::Pitch, id)); + break; + + case hash("fileg_attack"): + case hash("fileg_decay"): + case hash("fileg_delay"): + case hash("fileg_hold"): + case hash("fileg_release"): + case hash("fileg_start"): + case hash("fileg_sustain"): + case hash("fileg_vel&attack"): + case hash("fileg_vel&decay"): + case hash("fileg_vel&delay"): + case hash("fileg_vel&hold"): + case hash("fileg_vel&release"): + case hash("fileg_vel&sustain"): + case hash("fileg_attack_oncc&"): // also fileg_attackcc& + case hash("fileg_decay_oncc&"): // also fileg_decaycc& + case hash("fileg_delay_oncc&"): // also fileg_delaycc& + case hash("fileg_hold_oncc&"): // also fileg_holdcc& + case hash("fileg_release_oncc&"): // also fileg_releasecc& + case hash("fileg_start_oncc&"): // also fileg_startcc& + case hash("fileg_sustain_oncc&"): // also fileg_sustaincc& + if (parseEGopcode(opcode, filterEG)) + getOrCreateConnection( + ModKey::createNXYZ(ModId::FilEG, id), + ModKey::createNXYZ(ModId::FilCutoff, id)); + break; + // Flex envelopes case hash("eg&_dynamic"): { @@ -1408,6 +1460,19 @@ bool sfz::Region::parseEGopcode(const Opcode& opcode, EGDescription& eg) #undef case_any_eg } +bool sfz::Region::parseEGopcode(const Opcode& opcode, absl::optional& eg) +{ + bool create = eg == absl::nullopt; + if (create) + eg = EGDescription(); + + bool parsed = parseEGopcode(opcode, *eg); + if (!parsed && create) + eg = absl::nullopt; + + return parsed; +} + bool sfz::Region::processGenericCc(const Opcode& opcode, Range range, const ModKey& target) { if (!opcode.isAnyCcN()) diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 59439b3d..6bb15849 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -266,6 +266,16 @@ struct Region { * @return false */ bool parseEGopcode(const Opcode& opcode, EGDescription& eg); + /** + * @brief Parse a opcode which is specific to a particular SFZv1 EG: + * ampeg, pitcheg, fileg. + * + * @param opcode + * @param eg + * @return true if the opcode was properly read and stored. + * @return false + */ + bool parseEGopcode(const Opcode& opcode, absl::optional& eg); /** * @brief Process a generic CC opcode, and fill the modulation parameters. * @@ -410,8 +420,8 @@ struct Region { // Envelopes EGDescription amplitudeEG; - EGDescription pitchEG; - EGDescription filterEG; + absl::optional pitchEG; + absl::optional filterEG; // Envelopes std::vector flexEGs; @@ -431,7 +441,7 @@ struct Region { struct Connection { ModKey source; ModKey target; - float sourceDepth = 1.0f; + float sourceDepth = 0.0f; }; std::vector connections; Connection& getOrCreateConnection(const ModKey& source, const ModKey& target); diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index c08dde96..c58340f8 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -1702,20 +1702,24 @@ void sfz::Synth::updateUsedCCsFromRegion(std::bitset& usedC updateUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccHold); updateUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccStart); updateUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccSustain); - updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccAttack); - updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccRelease); - updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccDecay); - updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccDelay); - updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccHold); - updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccStart); - updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccSustain); - updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccAttack); - updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccRelease); - updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccDecay); - updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccDelay); - updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccHold); - updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccStart); - updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccSustain); + if (region.pitchEG) { + updateUsedCCsFromCCMap(usedCCs, region.pitchEG->ccAttack); + updateUsedCCsFromCCMap(usedCCs, region.pitchEG->ccRelease); + updateUsedCCsFromCCMap(usedCCs, region.pitchEG->ccDecay); + updateUsedCCsFromCCMap(usedCCs, region.pitchEG->ccDelay); + updateUsedCCsFromCCMap(usedCCs, region.pitchEG->ccHold); + updateUsedCCsFromCCMap(usedCCs, region.pitchEG->ccStart); + updateUsedCCsFromCCMap(usedCCs, region.pitchEG->ccSustain); + } + if (region.filterEG) { + updateUsedCCsFromCCMap(usedCCs, region.filterEG->ccAttack); + updateUsedCCsFromCCMap(usedCCs, region.filterEG->ccRelease); + updateUsedCCsFromCCMap(usedCCs, region.filterEG->ccDecay); + updateUsedCCsFromCCMap(usedCCs, region.filterEG->ccDelay); + updateUsedCCsFromCCMap(usedCCs, region.filterEG->ccHold); + updateUsedCCsFromCCMap(usedCCs, region.filterEG->ccStart); + updateUsedCCsFromCCMap(usedCCs, region.filterEG->ccSustain); + } updateUsedCCsFromCCMap(usedCCs, region.ccConditions); updateUsedCCsFromCCMap(usedCCs, region.ccTriggers); updateUsedCCsFromCCMap(usedCCs, region.crossfadeCCInRange); diff --git a/src/sfizz/modulations/ModId.cpp b/src/sfizz/modulations/ModId.cpp index f294a0bc..427d7511 100644 --- a/src/sfizz/modulations/ModId.cpp +++ b/src/sfizz/modulations/ModId.cpp @@ -30,6 +30,10 @@ int ModIds::flags(ModId id) noexcept return kModIsPerVoice; case ModId::LFO: return kModIsPerVoice; + case ModId::PitchEG: + return kModIsPerVoice; + case ModId::FilEG: + return kModIsPerVoice; // targets case ModId::Amplitude: diff --git a/src/sfizz/modulations/ModId.h b/src/sfizz/modulations/ModId.h index daf7f72f..ccd25b98 100644 --- a/src/sfizz/modulations/ModId.h +++ b/src/sfizz/modulations/ModId.h @@ -23,6 +23,8 @@ enum class ModId : int { Controller = _SourcesStart, Envelope, LFO, + PitchEG, + FilEG, _SourcesEnd, diff --git a/src/sfizz/modulations/ModKey.cpp b/src/sfizz/modulations/ModKey.cpp index efb6c8a0..80c3accb 100644 --- a/src/sfizz/modulations/ModKey.cpp +++ b/src/sfizz/modulations/ModKey.cpp @@ -74,6 +74,10 @@ std::string ModKey::toString() const return absl::StrCat("EG ", 1 + params_.N, " {", region_.number(), "}"); case ModId::LFO: return absl::StrCat("LFO ", 1 + params_.N, " {", region_.number(), "}"); + case ModId::PitchEG: + return absl::StrCat("PitchEG {", region_.number(), "}"); + case ModId::FilEG: + return absl::StrCat("FilterEG {", region_.number(), "}"); case ModId::Amplitude: return absl::StrCat("Amplitude {", region_.number(), "}"); From 2f12206c4c77ac98f12ea41445372e527a5f7e9a Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 23 Sep 2020 22:58:53 +0200 Subject: [PATCH 4/8] Rename `egEnvelope` to `egAmplitude` --- src/sfizz/Voice.cpp | 20 ++++++++++---------- src/sfizz/Voice.h | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index d165d426..81f7c158 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -130,7 +130,7 @@ void sfz::Voice::startVoice(Region* region, int delay, const TriggerEvent& event bendStepFactor = centsFactor(region->bendStep); bendSmoother.setSmoothing(region->bendSmooth, sampleRate); bendSmoother.reset(centsFactor(region->getBendInCents(resources.midiState.getPitchBend()))); - egEnvelope.reset(region->amplitudeEG, *region, resources.midiState, delay, triggerEvent.value, sampleRate); + egAmplitude.reset(region->amplitudeEG, *region, resources.midiState, delay, triggerEvent.value, sampleRate); resources.modMatrix.initVoice(id, region->getId(), delay); saveModulationTargets(region); @@ -152,10 +152,10 @@ void sfz::Voice::release(int delay) noexcept if (state != State::playing) return; - if (egEnvelope.getRemainingDelay() > delay) { + if (egAmplitude.getRemainingDelay() > delay) { switchState(State::cleanMeUp); } else { - egEnvelope.startRelease(delay); + egAmplitude.startRelease(delay); } resources.modMatrix.releaseVoice(id, region->getId(), delay); @@ -164,9 +164,9 @@ void sfz::Voice::release(int delay) noexcept void sfz::Voice::off(int delay) noexcept { if (region->offMode == SfzOffMode::fast) { - egEnvelope.setReleaseTime( Default::offTime ); + egAmplitude.setReleaseTime( Default::offTime ); } else if (region->offMode == SfzOffMode::time) { - egEnvelope.setReleaseTime(region->offTime); + egAmplitude.setReleaseTime(region->offTime); } release(delay); @@ -286,7 +286,7 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept panStageMono(buffer); } - if (!egEnvelope.isSmoothing()) + if (!egAmplitude.isSmoothing()) switchState(State::cleanMeUp); powerFollower.process(buffer); @@ -368,7 +368,7 @@ void sfz::Voice::amplitudeEnvelope(absl::Span modulationSpan) noexcept ModMatrix& mm = resources.modMatrix; // AmpEG envelope - egEnvelope.getBlock(modulationSpan); + egAmplitude.getBlock(modulationSpan); // Amplitude envelope applyGain1(baseGain, modulationSpan); @@ -572,8 +572,8 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept << " for sample " << region->sampleId); } #endif - egEnvelope.setReleaseTime(0.0f); - egEnvelope.startRelease(i); + egAmplitude.setReleaseTime(0.0f); + egAmplitude.startRelease(i); fill(indices->subspan(i), sampleEnd); fill(coeffs->subspan(i), 1.0f); break; @@ -853,7 +853,7 @@ float sfz::Voice::getAveragePower() const noexcept bool sfz::Voice::releasedOrFree() const noexcept { - return state != State::playing || egEnvelope.isReleased(); + return state != State::playing || egAmplitude.isReleased(); } uint32_t sfz::Voice::getSourcePosition() const noexcept diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 348022f6..02062752 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -461,7 +461,7 @@ private: std::vector> lfos; std::vector> flexEGs; - ADSREnvelope egEnvelope; + ADSREnvelope egAmplitude; float bendStepFactor { centsFactor(1) }; WavetableOscillator waveOscillators[config::oscillatorsPerVoice]; From b99b40b131fa986c4dbd8edb3c7de55cb74e9afd Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 23 Sep 2020 23:32:47 +0200 Subject: [PATCH 5/8] Implement fileg and pitcheg in the matrix --- dpf.mk | 1 + src/CMakeLists.txt | 2 + src/sfizz/Defaults.h | 2 + src/sfizz/Region.cpp | 16 +++ src/sfizz/Synth.cpp | 14 +++ src/sfizz/Synth.h | 5 + src/sfizz/Voice.cpp | 16 +++ src/sfizz/Voice.h | 28 +++++ .../modulations/sources/ADSREnvelope.cpp | 117 ++++++++++++++++++ src/sfizz/modulations/sources/ADSREnvelope.h | 24 ++++ 10 files changed, 225 insertions(+) create mode 100644 src/sfizz/modulations/sources/ADSREnvelope.cpp create mode 100644 src/sfizz/modulations/sources/ADSREnvelope.h diff --git a/dpf.mk b/dpf.mk index 1c8159fa..71d4555f 100644 --- a/dpf.mk +++ b/dpf.mk @@ -65,6 +65,7 @@ SFIZZ_SOURCES = \ src/sfizz/modulations/ModKey.cpp \ src/sfizz/modulations/ModKeyHash.cpp \ src/sfizz/modulations/ModMatrix.cpp \ + src/sfizz/modulations/sources/ADSREnvelope.cpp \ src/sfizz/modulations/sources/Controller.cpp \ src/sfizz/modulations/sources/FlexEGDescription.cpp \ src/sfizz/modulations/sources/FlexEnvelope.cpp \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2f65ea3c..11d48265 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,6 +35,7 @@ set (SFIZZ_HEADERS sfizz/modulations/ModKeyHash.h sfizz/modulations/ModMatrix.h sfizz/modulations/ModGenerator.h + sfizz/modulations/sources/ADSREnvelope.h sfizz/modulations/sources/Controller.h sfizz/modulations/sources/FlexEnvelope.h sfizz/modulations/sources/LFO.h @@ -151,6 +152,7 @@ set (SFIZZ_SOURCES sfizz/modulations/ModMatrix.cpp sfizz/modulations/sources/Controller.cpp sfizz/modulations/sources/FlexEnvelope.cpp + sfizz/modulations/sources/ADSREnvelope.cpp sfizz/modulations/sources/LFO.cpp sfizz/utility/SpinMutex.cpp sfizz/effects/Nothing.cpp diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 27b7c240..1550e749 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -250,6 +250,8 @@ namespace Default constexpr Range egDepthRange { -12000, 12000 }; constexpr Range egOnCCTimeRange { -100.0, 100.0 }; constexpr Range egOnCCPercentRange { -100.0, 100.0 }; + constexpr Range pitchEgDepthRange { -12000.0, 12000.0 }; + constexpr Range filterEgDepthRange { -12000.0, 12000.0 }; // Flex envelope generators constexpr int numFlexEGs { 4 }; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 7ca3e928..967e92d9 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1238,6 +1238,22 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) ModKey::createNXYZ(ModId::FilCutoff, id)); break; + case hash("pitcheg_depth"): + if (auto value = readOpcode(opcode.value, Default::pitchEgDepthRange)) + getOrCreateConnection( + ModKey::createNXYZ(ModId::PitchEG, id), + ModKey::createNXYZ(ModId::Pitch, id)).sourceDepth = *value; + break; + case hash("fileg_depth"): + if (auto value = readOpcode(opcode.value, Default::filterEgDepthRange)) + getOrCreateConnection( + ModKey::createNXYZ(ModId::FilEG, id), + ModKey::createNXYZ(ModId::FilCutoff, id)).sourceDepth = *value; + break; + + // TODO(jpc): pitcheg_vel2depth + // TODO(jpc): fileg_vel2depth + // Flex envelopes case hash("eg&_dynamic"): { diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index c58340f8..c928f48d 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -19,6 +19,7 @@ #include "modulations/sources/Controller.h" #include "modulations/sources/LFO.h" #include "modulations/sources/FlexEnvelope.h" +#include "modulations/sources/ADSREnvelope.h" #include "utility/XmlHelpers.h" #include "pugixml.hpp" #include "absl/algorithm/container.h" @@ -49,6 +50,7 @@ sfz::Synth::Synth(int numVoices) genController.reset(new ControllerSource(resources)); genLFO.reset(new LFOSource(*this)); genFlexEnvelope.reset(new FlexEnvelopeSource(*this)); + genADSREnvelope.reset(new ADSREnvelopeSource(*this)); } sfz::Synth::~Synth() @@ -489,6 +491,8 @@ void sfz::Synth::finalizeSfzLoad() size_t maxEQs { 0 }; size_t maxLFOs { 0 }; size_t maxFlexEGs { 0 }; + bool havePitchEG { false }; + bool haveFilterEG { false }; FlexEGs::clearUnusedCurves(); @@ -613,6 +617,8 @@ void sfz::Synth::finalizeSfzLoad() maxEQs = max(maxEQs, region->equalizers.size()); maxLFOs = max(maxLFOs, region->lfos.size()); maxFlexEGs = max(maxFlexEGs, region->flexEGs.size()); + havePitchEG = havePitchEG || region->pitchEG != absl::nullopt; + haveFilterEG = haveFilterEG || region->filterEG != absl::nullopt; ++currentRegionIndex; } @@ -624,6 +630,8 @@ void sfz::Synth::finalizeSfzLoad() settingsPerVoice.maxEQs = maxEQs; settingsPerVoice.maxLFOs = maxLFOs; settingsPerVoice.maxFlexEGs = maxFlexEGs; + settingsPerVoice.havePitchEG = havePitchEG; + settingsPerVoice.haveFilterEG = haveFilterEG; applySettingsPerVoice(); @@ -1499,6 +1507,8 @@ void sfz::Synth::applySettingsPerVoice() voice->setMaxEQsPerVoice(settingsPerVoice.maxEQs); voice->setMaxLFOsPerVoice(settingsPerVoice.maxLFOs); voice->setMaxFlexEGsPerVoice(settingsPerVoice.maxFlexEGs); + voice->setPitchEGEnabledPerVoice(settingsPerVoice.havePitchEG); + voice->setFilterEGEnabledPerVoice(settingsPerVoice.haveFilterEG); } } @@ -1520,6 +1530,10 @@ void sfz::Synth::setupModMatrix() case ModId::Envelope: gen = genFlexEnvelope.get(); break; + case ModId::PitchEG: + case ModId::FilEG: + gen = genADSREnvelope.get(); + break; default: DBG("[sfizz] Have unknown type of source generator"); break; diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index c308666e..880726cf 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -29,6 +29,7 @@ namespace sfz { class ControllerSource; class LFOSource; class FlexEnvelopeSource; +class ADSREnvelopeSource; /** * @brief This class is the core of the sfizz library. In C++ it is the main point @@ -550,6 +551,7 @@ public: */ void disableFreeWheeling() noexcept; + Resources& getResources() noexcept { return resources; } const Resources& getResources() const noexcept { return resources; } /** @@ -922,6 +924,7 @@ private: std::unique_ptr genController; std::unique_ptr genLFO; std::unique_ptr genFlexEnvelope; + std::unique_ptr genADSREnvelope; // Settings per voice struct SettingsPerVoice { @@ -929,6 +932,8 @@ private: size_t maxEQs { 0 }; size_t maxLFOs { 0 }; size_t maxFlexEGs { 0 }; + bool havePitchEG { false }; + bool haveFilterEG { false }; }; SettingsPerVoice settingsPerVoice; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 81f7c158..a60b1af7 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -903,6 +903,22 @@ void sfz::Voice::setMaxFlexEGsPerVoice(size_t numFlexEGs) } } +void sfz::Voice::setPitchEGEnabledPerVoice(bool havePitchEG) +{ + if (havePitchEG) + egPitch.reset(new ADSREnvelope); + else + egPitch.reset(); +} + +void sfz::Voice::setFilterEGEnabledPerVoice(bool haveFilterEG) +{ + if (haveFilterEG) + egFilter.reset(new ADSREnvelope); + else + egFilter.reset(); +} + void sfz::Voice::setupOscillatorUnison() { const int m = region->oscillatorMulti; diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 02062752..419c6584 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -296,6 +296,18 @@ public: * @param numFlexEGs */ void setMaxFlexEGsPerVoice(size_t numFlexEGs); + /** + * @brief Set whether SFZv1 pitch EG is enabled on this voice + * + * @param havePitchEG + */ + void setPitchEGEnabledPerVoice(bool havePitchEG); + /** + * @brief Set whether SFZv1 filter EG is enabled on this voice + * + * @param haveFilterEG + */ + void setFilterEGEnabledPerVoice(bool haveFilterEG); /** * @brief Release the voice after a given delay * @@ -324,6 +336,20 @@ public: Duration getLastFilterDuration() const noexcept { return filterDuration; } Duration getLastPanningDuration() const noexcept { return panningDuration; } + /** + * @brief Get the SFZv1 pitch EG, if existing + */ + ADSREnvelope* getPitchEG() { return egPitch.get(); } + /** + * @brief Get the SFZv1 filter EG, if existing + */ + ADSREnvelope* getFilterEG() { return egFilter.get(); } + + /** + * @brief Get the trigger event + */ + const TriggerEvent& getTriggerEvent() { return triggerEvent; } + private: /** * @brief Fill a span with data from a file source. This is the first step @@ -462,6 +488,8 @@ private: std::vector> flexEGs; ADSREnvelope egAmplitude; + std::unique_ptr> egPitch; + std::unique_ptr> egFilter; float bendStepFactor { centsFactor(1) }; WavetableOscillator waveOscillators[config::oscillatorsPerVoice]; diff --git a/src/sfizz/modulations/sources/ADSREnvelope.cpp b/src/sfizz/modulations/sources/ADSREnvelope.cpp new file mode 100644 index 00000000..7128b216 --- /dev/null +++ b/src/sfizz/modulations/sources/ADSREnvelope.cpp @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "ADSREnvelope.h" +#include "../../ADSREnvelope.h" +#include "../../Synth.h" +#include "../../Voice.h" +#include "../../Config.h" +#include "../../Debug.h" + +// TODO(jpc): also matrix the ampeg + +namespace sfz { + +ADSREnvelopeSource::ADSREnvelopeSource(Synth &synth) + : synth_(&synth) +{ +} + +void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) +{ + Synth& synth = *synth_; + + Voice* voice = synth.getVoiceById(voiceId); + if (!voice) { + ASSERTFALSE; + return; + } + + const Region* region = voice->getRegion(); + ADSREnvelope* eg = nullptr; + const EGDescription* desc = nullptr; + + switch (sourceKey.id()) { + case ModId::PitchEG: + eg = voice->getPitchEG(); + ASSERT(eg); + desc = &*region->pitchEG; + break; + case ModId::FilEG: + eg = voice->getFilterEG(); + ASSERT(eg); + desc = &*region->filterEG; + break; + default: + ASSERTFALSE; + return; + } + + Resources& resources = synth.getResources(); + const TriggerEvent& triggerEvent = voice->getTriggerEvent(); + const float sampleRate = voice->getSampleRate(); + eg->reset(*desc, *region, resources.midiState, delay, triggerEvent.value, sampleRate); +} + +void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) +{ + Synth& synth = *synth_; + + Voice* voice = synth.getVoiceById(voiceId); + if (!voice) { + ASSERTFALSE; + return; + } + + ADSREnvelope* eg = nullptr; + + switch (sourceKey.id()) { + case ModId::PitchEG: + eg = voice->getPitchEG(); + ASSERT(eg); + break; + case ModId::FilEG: + eg = voice->getFilterEG(); + ASSERT(eg); + break; + default: + ASSERTFALSE; + return; + } + + eg->startRelease(delay); +} + +void ADSREnvelopeSource::generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) +{ + Synth& synth = *synth_; + + Voice* voice = synth.getVoiceById(voiceId); + if (!voice) { + ASSERTFALSE; + return; + } + + ADSREnvelope* eg = nullptr; + + switch (sourceKey.id()) { + case ModId::PitchEG: + eg = voice->getPitchEG(); + ASSERT(eg); + break; + case ModId::FilEG: + eg = voice->getFilterEG(); + ASSERT(eg); + break; + default: + ASSERTFALSE; + return; + } + + eg->getBlock(buffer); +} + +} // namespace sfz diff --git a/src/sfizz/modulations/sources/ADSREnvelope.h b/src/sfizz/modulations/sources/ADSREnvelope.h new file mode 100644 index 00000000..b2644df9 --- /dev/null +++ b/src/sfizz/modulations/sources/ADSREnvelope.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once +#include "../ModGenerator.h" + +namespace sfz { +class Synth; + +class ADSREnvelopeSource : public ModGenerator { +public: + explicit ADSREnvelopeSource(Synth &synth); + void init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; + void release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; + void generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) override; + +private: + Synth* synth_ = nullptr; +}; + +} // namespace sfz From 4a998cf6e70422e4159a1fff9786851e282aa95c Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 23 Sep 2020 23:54:31 +0200 Subject: [PATCH 6/8] Forgot to update source depths after changing default to 0 --- src/sfizz/Synth.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index c928f48d..7f6b6d10 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -154,10 +154,10 @@ void sfz::Synth::buildRegion(const std::vector& regionOpcodes) constexpr unsigned defaultSmoothness = 10; lastRegion->getOrCreateConnection( ModKey::createCC(7, 4, defaultSmoothness, 100, 0), - ModKey::createNXYZ(ModId::Amplitude, lastRegion->id)); + ModKey::createNXYZ(ModId::Amplitude, lastRegion->id)).sourceDepth = 1.0f; lastRegion->getOrCreateConnection( ModKey::createCC(10, 1, defaultSmoothness, 100, 0), - ModKey::createNXYZ(ModId::Pan, lastRegion->id)); + ModKey::createNXYZ(ModId::Pan, lastRegion->id)).sourceDepth = 1.0f; // auto parseOpcodes = [&](const std::vector& opcodes) { From 897477e1401dce549670192b77a0c15a1aef5ce8 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 24 Sep 2020 15:12:58 +0200 Subject: [PATCH 7/8] Just a function renamed --- src/sfizz/Region.cpp | 12 ++++++------ src/sfizz/Region.h | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 967e92d9..a16c91a5 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1183,7 +1183,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) case hash("ampeg_release_oncc&"): // also ampeg_releasecc& case hash("ampeg_start_oncc&"): // also ampeg_startcc& case hash("ampeg_sustain_oncc&"): // also ampeg_sustaincc& - parseEGopcode(opcode, amplitudeEG); + parseEGOpcode(opcode, amplitudeEG); break; case hash("pitcheg_attack"): @@ -1206,7 +1206,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) case hash("pitcheg_release_oncc&"): // also pitcheg_releasecc& case hash("pitcheg_start_oncc&"): // also pitcheg_startcc& case hash("pitcheg_sustain_oncc&"): // also pitcheg_sustaincc& - if (parseEGopcode(opcode, pitchEG)) + if (parseEGOpcode(opcode, pitchEG)) getOrCreateConnection( ModKey::createNXYZ(ModId::PitchEG, id), ModKey::createNXYZ(ModId::Pitch, id)); @@ -1232,7 +1232,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) case hash("fileg_release_oncc&"): // also fileg_releasecc& case hash("fileg_start_oncc&"): // also fileg_startcc& case hash("fileg_sustain_oncc&"): // also fileg_sustaincc& - if (parseEGopcode(opcode, filterEG)) + if (parseEGOpcode(opcode, filterEG)) getOrCreateConnection( ModKey::createNXYZ(ModId::FilEG, id), ModKey::createNXYZ(ModId::FilCutoff, id)); @@ -1352,7 +1352,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return true; } -bool sfz::Region::parseEGopcode(const Opcode& opcode, EGDescription& eg) +bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) { #define case_any_eg(param) \ case hash("ampeg_" param): \ @@ -1476,13 +1476,13 @@ bool sfz::Region::parseEGopcode(const Opcode& opcode, EGDescription& eg) #undef case_any_eg } -bool sfz::Region::parseEGopcode(const Opcode& opcode, absl::optional& eg) +bool sfz::Region::parseEGOpcode(const Opcode& opcode, absl::optional& eg) { bool create = eg == absl::nullopt; if (create) eg = EGDescription(); - bool parsed = parseEGopcode(opcode, *eg); + bool parsed = parseEGOpcode(opcode, *eg); if (!parsed && create) eg = absl::nullopt; diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 6bb15849..ca55827f 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -265,7 +265,7 @@ struct Region { * @return true if the opcode was properly read and stored. * @return false */ - bool parseEGopcode(const Opcode& opcode, EGDescription& eg); + bool parseEGOpcode(const Opcode& opcode, EGDescription& eg); /** * @brief Parse a opcode which is specific to a particular SFZv1 EG: * ampeg, pitcheg, fileg. @@ -275,7 +275,7 @@ struct Region { * @return true if the opcode was properly read and stored. * @return false */ - bool parseEGopcode(const Opcode& opcode, absl::optional& eg); + bool parseEGOpcode(const Opcode& opcode, absl::optional& eg); /** * @brief Process a generic CC opcode, and fill the modulation parameters. * From 48ddb4ad60db0a8ff7ba29747109d1f28e91f70f Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 24 Sep 2020 16:12:52 +0200 Subject: [PATCH 8/8] Add vel2depth --- src/sfizz/Region.cpp | 18 ++++++++++++++++-- src/sfizz/Region.h | 1 + src/sfizz/Synth.cpp | 4 ++-- src/sfizz/modulations/ModMatrix.cpp | 20 +++++++++++++++++--- src/sfizz/modulations/ModMatrix.h | 6 ++++-- 5 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index a16c91a5..7d69d202 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1251,8 +1251,22 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) ModKey::createNXYZ(ModId::FilCutoff, id)).sourceDepth = *value; break; - // TODO(jpc): pitcheg_vel2depth - // TODO(jpc): fileg_vel2depth + case hash("pitcheg_vel&depth"): + if (opcode.parameters.front() != 2) + return false; // Was not vel2... + if (auto value = readOpcode(opcode.value, Default::pitchEgDepthRange)) + getOrCreateConnection( + ModKey::createNXYZ(ModId::PitchEG, id), + ModKey::createNXYZ(ModId::Pitch, id)).velToDepth = *value; + break; + case hash("fileg_vel&depth"): + if (opcode.parameters.front() != 2) + return false; // Was not vel2... + if (auto value = readOpcode(opcode.value, Default::filterEgDepthRange)) + getOrCreateConnection( + ModKey::createNXYZ(ModId::FilEG, id), + ModKey::createNXYZ(ModId::FilCutoff, id)).velToDepth = *value; + break; // Flex envelopes case hash("eg&_dynamic"): diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index ca55827f..fabe91c4 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -442,6 +442,7 @@ struct Region { ModKey source; ModKey target; float sourceDepth = 0.0f; + float velToDepth = 0.0f; }; std::vector connections; Connection& getOrCreateConnection(const ModKey& source, const ModKey& target); diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 7f6b6d10..b191d841 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -820,7 +820,7 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept if (voice->isFree()) continue; - mm.beginVoice(voice->getId(), voice->getRegion()->getId()); + mm.beginVoice(voice->getId(), voice->getRegion()->getId(), voice->getTriggerEvent().value); activeVoices++; renderVoiceToOutputs(*voice, *tempSpan); @@ -1558,7 +1558,7 @@ void sfz::Synth::setupModMatrix() continue; } - if (!mm.connect(source, target, conn.sourceDepth)) { + if (!mm.connect(source, target, conn.sourceDepth, conn.velToDepth)) { DBG("[sfizz] Failed to connect modulation source and target"); ASSERTFALSE; } diff --git a/src/sfizz/modulations/ModMatrix.cpp b/src/sfizz/modulations/ModMatrix.cpp index d7ddc444..ffba3f6a 100644 --- a/src/sfizz/modulations/ModMatrix.cpp +++ b/src/sfizz/modulations/ModMatrix.cpp @@ -27,6 +27,8 @@ struct ModMatrix::Impl { NumericId currentVoiceId_ {}; NumericId currentRegionId_ {}; + float currentVoiceTriggerValue_ {}; + struct Source { ModKey key; ModGenerator* gen {}; @@ -36,6 +38,7 @@ struct ModMatrix::Impl { struct ConnectionData { float sourceDepth_ {}; + float velToDepth_ {}; }; struct Target { @@ -190,7 +193,7 @@ ModMatrix::TargetId ModMatrix::findTarget(const ModKey& key) const return TargetId(it->second); } -bool ModMatrix::connect(SourceId sourceId, TargetId targetId, float sourceDepth) +bool ModMatrix::connect(SourceId sourceId, TargetId targetId, float sourceDepth, float velToDepth) { Impl& impl = *impl_; unsigned sourceIndex = sourceId.number(); @@ -202,6 +205,7 @@ bool ModMatrix::connect(SourceId sourceId, TargetId targetId, float sourceDepth) Impl::Target& target = impl.targets_[targetIndex]; Impl::ConnectionData& conn = target.connectedSources[sourceIndex]; conn.sourceDepth_ = sourceDepth; + conn.velToDepth_ = velToDepth; return true; } @@ -302,13 +306,15 @@ void ModMatrix::endCycle() impl.numFrames_ = 0; } -void ModMatrix::beginVoice(NumericId voiceId, NumericId regionId) +void ModMatrix::beginVoice(NumericId voiceId, NumericId regionId, float triggerValue) { Impl& impl = *impl_; impl.currentVoiceId_ = voiceId; impl.currentRegionId_ = regionId; + impl.currentVoiceTriggerValue_ = triggerValue; + ASSERT(regionId); const auto idNumber = static_cast(regionId.number()); @@ -345,6 +351,8 @@ void ModMatrix::endVoice() impl.currentVoiceId_ = {}; impl.currentRegionId_ = {}; + + impl.currentVoiceTriggerValue_ = 0.0f; } float* ModMatrix::getModulation(TargetId targetId) @@ -354,6 +362,7 @@ float* ModMatrix::getModulation(TargetId targetId) Impl& impl = *impl_; const NumericId regionId = impl.currentRegionId_; + const float triggerValue = impl.currentVoiceTriggerValue_; const uint32_t targetIndex = targetId.number(); Impl::Target &target = impl.targets_[targetIndex]; const int targetFlags = target.key.flags(); @@ -381,7 +390,6 @@ float* ModMatrix::getModulation(TargetId targetId) // then add or multiply, depending on target flags while (sourcesPos != sourcesEnd) { Impl::Source &source = impl.sources_[sourcesPos->first]; - const float sourceDepth = sourcesPos->second.sourceDepth_; const int sourceFlags = source.key.flags(); // only accept per-voice sources of the same region @@ -398,6 +406,12 @@ float* ModMatrix::getModulation(TargetId targetId) source.bufferReady = true; } + float sourceDepth = sourcesPos->second.sourceDepth_; + if (sourceFlags & kModIsPerVoice) { + const float velToDepth = sourcesPos->second.velToDepth_; + sourceDepth += triggerValue * velToDepth; + } + if (isFirstSource) { if (sourceDepth != 1) { for (uint32_t i = 0; i < numFrames; ++i) diff --git a/src/sfizz/modulations/ModMatrix.h b/src/sfizz/modulations/ModMatrix.h index 0f0fb471..0a01dd5b 100644 --- a/src/sfizz/modulations/ModMatrix.h +++ b/src/sfizz/modulations/ModMatrix.h @@ -92,9 +92,10 @@ public: * @param sourceId source of the connection * @param targetId target of the connection * @param sourceDepth amount which multiplies the source output + * @param velToDepth amount which full velocity adds to the source depth * @return true if the connection was successfully made, otherwise false */ - bool connect(SourceId sourceId, TargetId targetId, float sourceDepth); + bool connect(SourceId sourceId, TargetId targetId, float sourceDepth, float velToDepth = 0.0f); /** * @brief Reinitialize modulation sources overall. @@ -134,8 +135,9 @@ public: * * @param voiceId the identifier of the current voice * @param regionId the identifier of the region of the current voice + * @param triggerValue the velocity of the current voice */ - void beginVoice(NumericId voiceId, NumericId regionId); + void beginVoice(NumericId voiceId, NumericId regionId, float triggerValue); /** * @brief End modulation processing for a given voice.