Merge pull request #438 from jpcima/egv1-opcode-proc

Pitch and Filter EG v1
This commit is contained in:
JP Cimalando 2020-09-25 19:05:11 +02:00 committed by GitHub
commit dde743af76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 526 additions and 128 deletions

1
dpf.mk
View file

@ -65,6 +65,7 @@ SFIZZ_SOURCES = \
src/sfizz/modulations/ModKey.cpp \ src/sfizz/modulations/ModKey.cpp \
src/sfizz/modulations/ModKeyHash.cpp \ src/sfizz/modulations/ModKeyHash.cpp \
src/sfizz/modulations/ModMatrix.cpp \ src/sfizz/modulations/ModMatrix.cpp \
src/sfizz/modulations/sources/ADSREnvelope.cpp \
src/sfizz/modulations/sources/Controller.cpp \ src/sfizz/modulations/sources/Controller.cpp \
src/sfizz/modulations/sources/FlexEGDescription.cpp \ src/sfizz/modulations/sources/FlexEGDescription.cpp \
src/sfizz/modulations/sources/FlexEnvelope.cpp \ src/sfizz/modulations/sources/FlexEnvelope.cpp \

View file

@ -35,6 +35,7 @@ set (SFIZZ_HEADERS
sfizz/modulations/ModKeyHash.h sfizz/modulations/ModKeyHash.h
sfizz/modulations/ModMatrix.h sfizz/modulations/ModMatrix.h
sfizz/modulations/ModGenerator.h sfizz/modulations/ModGenerator.h
sfizz/modulations/sources/ADSREnvelope.h
sfizz/modulations/sources/Controller.h sfizz/modulations/sources/Controller.h
sfizz/modulations/sources/FlexEnvelope.h sfizz/modulations/sources/FlexEnvelope.h
sfizz/modulations/sources/LFO.h sfizz/modulations/sources/LFO.h
@ -151,6 +152,7 @@ set (SFIZZ_SOURCES
sfizz/modulations/ModMatrix.cpp sfizz/modulations/ModMatrix.cpp
sfizz/modulations/sources/Controller.cpp sfizz/modulations/sources/Controller.cpp
sfizz/modulations/sources/FlexEnvelope.cpp sfizz/modulations/sources/FlexEnvelope.cpp
sfizz/modulations/sources/ADSREnvelope.cpp
sfizz/modulations/sources/LFO.cpp sfizz/modulations/sources/LFO.cpp
sfizz/utility/SpinMutex.cpp sfizz/utility/SpinMutex.cpp
sfizz/effects/Nothing.cpp sfizz/effects/Nothing.cpp

View file

@ -38,6 +38,8 @@ public:
CCMap(CCMap&&) = default; CCMap(CCMap&&) = default;
CCMap(const CCMap&) = default; CCMap(const CCMap&) = default;
~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 * @brief Returns the held object at the index, or a default value if not present
@ -105,7 +107,7 @@ private:
// typename std::vector<std::pair<int, ValueType>>::iterator begin() { return container.begin(); } // typename std::vector<std::pair<int, ValueType>>::iterator begin() { return container.begin(); }
// typename std::vector<std::pair<int, ValueType>>::iterator end() { return container.end(); } // typename std::vector<std::pair<int, ValueType>>::iterator end() { return container.end(); }
const ValueType defaultValue; ValueType defaultValue;
std::vector<CCData<ValueType>> container; std::vector<CCData<ValueType>> container;
LEAK_DETECTOR(CCMap); LEAK_DETECTOR(CCMap);
}; };

View file

@ -250,6 +250,8 @@ namespace Default
constexpr Range<int> egDepthRange { -12000, 12000 }; constexpr Range<int> egDepthRange { -12000, 12000 };
constexpr Range<float> egOnCCTimeRange { -100.0, 100.0 }; constexpr Range<float> egOnCCTimeRange { -100.0, 100.0 };
constexpr Range<float> egOnCCPercentRange { -100.0, 100.0 }; constexpr Range<float> egOnCCPercentRange { -100.0, 100.0 };
constexpr Range<float> pitchEgDepthRange { -12000.0, 12000.0 };
constexpr Range<float> filterEgDepthRange { -12000.0, 12000.0 };
// Flex envelope generators // Flex envelope generators
constexpr int numFlexEGs { 4 }; constexpr int numFlexEGs { 4 };

View file

@ -63,6 +63,8 @@ struct EGDescription {
EGDescription(const EGDescription&) = default; EGDescription(const EGDescription&) = default;
EGDescription(EGDescription&&) = default; EGDescription(EGDescription&&) = default;
~EGDescription() = default; ~EGDescription() = default;
EGDescription& operator=(const EGDescription&) = default;
EGDescription& operator=(EGDescription&&) = default;
float attack { Default::attack }; float attack { Default::attack };
float decay { Default::decay }; float decay { Default::decay };

View file

@ -1164,111 +1164,108 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
// Amplitude Envelope // Amplitude Envelope
case hash("ampeg_attack"): case hash("ampeg_attack"):
setValueFromOpcode(opcode, amplitudeEG.attack, Default::egTimeRange);
break;
case hash("ampeg_decay"): case hash("ampeg_decay"):
setValueFromOpcode(opcode, amplitudeEG.decay, Default::egTimeRange);
break;
case hash("ampeg_delay"): case hash("ampeg_delay"):
setValueFromOpcode(opcode, amplitudeEG.delay, Default::egTimeRange);
break;
case hash("ampeg_hold"): case hash("ampeg_hold"):
setValueFromOpcode(opcode, amplitudeEG.hold, Default::egTimeRange);
break;
case hash("ampeg_release"): case hash("ampeg_release"):
setValueFromOpcode(opcode, amplitudeEG.release, Default::egTimeRange);
break;
case hash("ampeg_start"): case hash("ampeg_start"):
setValueFromOpcode(opcode, amplitudeEG.start, Default::egPercentRange);
break;
case hash("ampeg_sustain"): case hash("ampeg_sustain"):
setValueFromOpcode(opcode, amplitudeEG.sustain, Default::egPercentRange);
break;
case hash("ampeg_vel&attack"): 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"): 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"): 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"): 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"): 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"): case hash("ampeg_vel&sustain"):
case hash("ampeg_attack_oncc&"): // also ampeg_attackcc&
case hash("ampeg_decay_oncc&"): // also ampeg_decaycc&
case hash("ampeg_delay_oncc&"): // also ampeg_delaycc&
case hash("ampeg_hold_oncc&"): // also ampeg_holdcc&
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);
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;
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;
case hash("pitcheg_vel&depth"):
if (opcode.parameters.front() != 2) if (opcode.parameters.front() != 2)
return false; // Was not vel2... return false; // Was not vel2...
setValueFromOpcode(opcode, amplitudeEG.vel2sustain, Default::egOnCCPercentRange); if (auto value = readOpcode(opcode.value, Default::pitchEgDepthRange))
getOrCreateConnection(
ModKey::createNXYZ(ModId::PitchEG, id),
ModKey::createNXYZ(ModId::Pitch, id)).velToDepth = *value;
break; break;
case hash("ampeg_attack_oncc&"): // also ampeg_attackcc& case hash("fileg_vel&depth"):
if (opcode.parameters.back() >= config::numCCs) if (opcode.parameters.front() != 2)
return false; return false; // Was not vel2...
if (auto value = readOpcode(opcode.value, Default::filterEgDepthRange))
if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange)) getOrCreateConnection(
amplitudeEG.ccAttack[opcode.parameters.back()] = *value; ModKey::createNXYZ(ModId::FilEG, id),
ModKey::createNXYZ(ModId::FilCutoff, id)).velToDepth = *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;
break; break;
// Flex envelopes // Flex envelopes
@ -1369,6 +1366,143 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
return true; 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::parseEGOpcode(const Opcode& opcode, absl::optional<EGDescription>& 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<float> range, const ModKey& target) bool sfz::Region::processGenericCc(const Opcode& opcode, Range<float> range, const ModKey& target)
{ {
if (!opcode.isAnyCcN()) if (!opcode.isAnyCcN())

View file

@ -256,6 +256,26 @@ struct Region {
* @return false * @return false
*/ */
bool parseOpcode(const Opcode& opcode); 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 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<EGDescription>& eg);
/** /**
* @brief Process a generic CC opcode, and fill the modulation parameters. * @brief Process a generic CC opcode, and fill the modulation parameters.
* *
@ -400,8 +420,8 @@ struct Region {
// Envelopes // Envelopes
EGDescription amplitudeEG; EGDescription amplitudeEG;
EGDescription pitchEG; absl::optional<EGDescription> pitchEG;
EGDescription filterEG; absl::optional<EGDescription> filterEG;
// Envelopes // Envelopes
std::vector<FlexEGDescription> flexEGs; std::vector<FlexEGDescription> flexEGs;
@ -421,7 +441,8 @@ struct Region {
struct Connection { struct Connection {
ModKey source; ModKey source;
ModKey target; ModKey target;
float sourceDepth = 1.0f; float sourceDepth = 0.0f;
float velToDepth = 0.0f;
}; };
std::vector<Connection> connections; std::vector<Connection> connections;
Connection& getOrCreateConnection(const ModKey& source, const ModKey& target); Connection& getOrCreateConnection(const ModKey& source, const ModKey& target);

View file

@ -19,6 +19,7 @@
#include "modulations/sources/Controller.h" #include "modulations/sources/Controller.h"
#include "modulations/sources/LFO.h" #include "modulations/sources/LFO.h"
#include "modulations/sources/FlexEnvelope.h" #include "modulations/sources/FlexEnvelope.h"
#include "modulations/sources/ADSREnvelope.h"
#include "utility/XmlHelpers.h" #include "utility/XmlHelpers.h"
#include "pugixml.hpp" #include "pugixml.hpp"
#include "absl/algorithm/container.h" #include "absl/algorithm/container.h"
@ -49,6 +50,7 @@ sfz::Synth::Synth(int numVoices)
genController.reset(new ControllerSource(resources)); genController.reset(new ControllerSource(resources));
genLFO.reset(new LFOSource(*this)); genLFO.reset(new LFOSource(*this));
genFlexEnvelope.reset(new FlexEnvelopeSource(*this)); genFlexEnvelope.reset(new FlexEnvelopeSource(*this));
genADSREnvelope.reset(new ADSREnvelopeSource(*this));
} }
sfz::Synth::~Synth() sfz::Synth::~Synth()
@ -152,10 +154,10 @@ void sfz::Synth::buildRegion(const std::vector<Opcode>& regionOpcodes)
constexpr unsigned defaultSmoothness = 10; constexpr unsigned defaultSmoothness = 10;
lastRegion->getOrCreateConnection( lastRegion->getOrCreateConnection(
ModKey::createCC(7, 4, defaultSmoothness, 100, 0), ModKey::createCC(7, 4, defaultSmoothness, 100, 0),
ModKey::createNXYZ(ModId::Amplitude, lastRegion->id)); ModKey::createNXYZ(ModId::Amplitude, lastRegion->id)).sourceDepth = 1.0f;
lastRegion->getOrCreateConnection( lastRegion->getOrCreateConnection(
ModKey::createCC(10, 1, defaultSmoothness, 100, 0), 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<Opcode>& opcodes) { auto parseOpcodes = [&](const std::vector<Opcode>& opcodes) {
@ -489,6 +491,8 @@ void sfz::Synth::finalizeSfzLoad()
size_t maxEQs { 0 }; size_t maxEQs { 0 };
size_t maxLFOs { 0 }; size_t maxLFOs { 0 };
size_t maxFlexEGs { 0 }; size_t maxFlexEGs { 0 };
bool havePitchEG { false };
bool haveFilterEG { false };
FlexEGs::clearUnusedCurves(); FlexEGs::clearUnusedCurves();
@ -613,6 +617,8 @@ void sfz::Synth::finalizeSfzLoad()
maxEQs = max(maxEQs, region->equalizers.size()); maxEQs = max(maxEQs, region->equalizers.size());
maxLFOs = max(maxLFOs, region->lfos.size()); maxLFOs = max(maxLFOs, region->lfos.size());
maxFlexEGs = max(maxFlexEGs, region->flexEGs.size()); maxFlexEGs = max(maxFlexEGs, region->flexEGs.size());
havePitchEG = havePitchEG || region->pitchEG != absl::nullopt;
haveFilterEG = haveFilterEG || region->filterEG != absl::nullopt;
++currentRegionIndex; ++currentRegionIndex;
} }
@ -624,6 +630,8 @@ void sfz::Synth::finalizeSfzLoad()
settingsPerVoice.maxEQs = maxEQs; settingsPerVoice.maxEQs = maxEQs;
settingsPerVoice.maxLFOs = maxLFOs; settingsPerVoice.maxLFOs = maxLFOs;
settingsPerVoice.maxFlexEGs = maxFlexEGs; settingsPerVoice.maxFlexEGs = maxFlexEGs;
settingsPerVoice.havePitchEG = havePitchEG;
settingsPerVoice.haveFilterEG = haveFilterEG;
applySettingsPerVoice(); applySettingsPerVoice();
@ -812,7 +820,7 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
if (voice->isFree()) if (voice->isFree())
continue; continue;
mm.beginVoice(voice->getId(), voice->getRegion()->getId()); mm.beginVoice(voice->getId(), voice->getRegion()->getId(), voice->getTriggerEvent().value);
activeVoices++; activeVoices++;
renderVoiceToOutputs(*voice, *tempSpan); renderVoiceToOutputs(*voice, *tempSpan);
@ -1499,6 +1507,8 @@ void sfz::Synth::applySettingsPerVoice()
voice->setMaxEQsPerVoice(settingsPerVoice.maxEQs); voice->setMaxEQsPerVoice(settingsPerVoice.maxEQs);
voice->setMaxLFOsPerVoice(settingsPerVoice.maxLFOs); voice->setMaxLFOsPerVoice(settingsPerVoice.maxLFOs);
voice->setMaxFlexEGsPerVoice(settingsPerVoice.maxFlexEGs); voice->setMaxFlexEGsPerVoice(settingsPerVoice.maxFlexEGs);
voice->setPitchEGEnabledPerVoice(settingsPerVoice.havePitchEG);
voice->setFilterEGEnabledPerVoice(settingsPerVoice.haveFilterEG);
} }
} }
@ -1520,6 +1530,10 @@ void sfz::Synth::setupModMatrix()
case ModId::Envelope: case ModId::Envelope:
gen = genFlexEnvelope.get(); gen = genFlexEnvelope.get();
break; break;
case ModId::PitchEG:
case ModId::FilEG:
gen = genADSREnvelope.get();
break;
default: default:
DBG("[sfizz] Have unknown type of source generator"); DBG("[sfizz] Have unknown type of source generator");
break; break;
@ -1544,7 +1558,7 @@ void sfz::Synth::setupModMatrix()
continue; 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"); DBG("[sfizz] Failed to connect modulation source and target");
ASSERTFALSE; ASSERTFALSE;
} }
@ -1702,20 +1716,24 @@ void sfz::Synth::updateUsedCCsFromRegion(std::bitset<sfz::config::numCCs>& usedC
updateUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccHold); updateUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccHold);
updateUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccStart); updateUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccStart);
updateUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccSustain); updateUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccSustain);
updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccAttack); if (region.pitchEG) {
updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccRelease); updateUsedCCsFromCCMap(usedCCs, region.pitchEG->ccAttack);
updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccDecay); updateUsedCCsFromCCMap(usedCCs, region.pitchEG->ccRelease);
updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccDelay); updateUsedCCsFromCCMap(usedCCs, region.pitchEG->ccDecay);
updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccHold); updateUsedCCsFromCCMap(usedCCs, region.pitchEG->ccDelay);
updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccStart); updateUsedCCsFromCCMap(usedCCs, region.pitchEG->ccHold);
updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccSustain); updateUsedCCsFromCCMap(usedCCs, region.pitchEG->ccStart);
updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccAttack); updateUsedCCsFromCCMap(usedCCs, region.pitchEG->ccSustain);
updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccRelease); }
updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccDecay); if (region.filterEG) {
updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccDelay); updateUsedCCsFromCCMap(usedCCs, region.filterEG->ccAttack);
updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccHold); updateUsedCCsFromCCMap(usedCCs, region.filterEG->ccRelease);
updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccStart); updateUsedCCsFromCCMap(usedCCs, region.filterEG->ccDecay);
updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccSustain); 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.ccConditions);
updateUsedCCsFromCCMap(usedCCs, region.ccTriggers); updateUsedCCsFromCCMap(usedCCs, region.ccTriggers);
updateUsedCCsFromCCMap(usedCCs, region.crossfadeCCInRange); updateUsedCCsFromCCMap(usedCCs, region.crossfadeCCInRange);

View file

@ -29,6 +29,7 @@ namespace sfz {
class ControllerSource; class ControllerSource;
class LFOSource; class LFOSource;
class FlexEnvelopeSource; class FlexEnvelopeSource;
class ADSREnvelopeSource;
/** /**
* @brief This class is the core of the sfizz library. In C++ it is the main point * @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; void disableFreeWheeling() noexcept;
Resources& getResources() noexcept { return resources; }
const Resources& getResources() const noexcept { return resources; } const Resources& getResources() const noexcept { return resources; }
/** /**
@ -922,6 +924,7 @@ private:
std::unique_ptr<ControllerSource> genController; std::unique_ptr<ControllerSource> genController;
std::unique_ptr<LFOSource> genLFO; std::unique_ptr<LFOSource> genLFO;
std::unique_ptr<FlexEnvelopeSource> genFlexEnvelope; std::unique_ptr<FlexEnvelopeSource> genFlexEnvelope;
std::unique_ptr<ADSREnvelopeSource> genADSREnvelope;
// Settings per voice // Settings per voice
struct SettingsPerVoice { struct SettingsPerVoice {
@ -929,6 +932,8 @@ private:
size_t maxEQs { 0 }; size_t maxEQs { 0 };
size_t maxLFOs { 0 }; size_t maxLFOs { 0 };
size_t maxFlexEGs { 0 }; size_t maxFlexEGs { 0 };
bool havePitchEG { false };
bool haveFilterEG { false };
}; };
SettingsPerVoice settingsPerVoice; SettingsPerVoice settingsPerVoice;

View file

@ -130,7 +130,7 @@ void sfz::Voice::startVoice(Region* region, int delay, const TriggerEvent& event
bendStepFactor = centsFactor(region->bendStep); bendStepFactor = centsFactor(region->bendStep);
bendSmoother.setSmoothing(region->bendSmooth, sampleRate); bendSmoother.setSmoothing(region->bendSmooth, sampleRate);
bendSmoother.reset(centsFactor(region->getBendInCents(resources.midiState.getPitchBend()))); 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); resources.modMatrix.initVoice(id, region->getId(), delay);
saveModulationTargets(region); saveModulationTargets(region);
@ -152,10 +152,10 @@ void sfz::Voice::release(int delay) noexcept
if (state != State::playing) if (state != State::playing)
return; return;
if (egEnvelope.getRemainingDelay() > delay) { if (egAmplitude.getRemainingDelay() > delay) {
switchState(State::cleanMeUp); switchState(State::cleanMeUp);
} else { } else {
egEnvelope.startRelease(delay); egAmplitude.startRelease(delay);
} }
resources.modMatrix.releaseVoice(id, region->getId(), 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 void sfz::Voice::off(int delay) noexcept
{ {
if (region->offMode == SfzOffMode::fast) { if (region->offMode == SfzOffMode::fast) {
egEnvelope.setReleaseTime( Default::offTime ); egAmplitude.setReleaseTime( Default::offTime );
} else if (region->offMode == SfzOffMode::time) { } else if (region->offMode == SfzOffMode::time) {
egEnvelope.setReleaseTime(region->offTime); egAmplitude.setReleaseTime(region->offTime);
} }
release(delay); release(delay);
@ -286,7 +286,7 @@ void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
panStageMono(buffer); panStageMono(buffer);
} }
if (!egEnvelope.isSmoothing()) if (!egAmplitude.isSmoothing())
switchState(State::cleanMeUp); switchState(State::cleanMeUp);
powerFollower.process(buffer); powerFollower.process(buffer);
@ -368,7 +368,7 @@ void sfz::Voice::amplitudeEnvelope(absl::Span<float> modulationSpan) noexcept
ModMatrix& mm = resources.modMatrix; ModMatrix& mm = resources.modMatrix;
// AmpEG envelope // AmpEG envelope
egEnvelope.getBlock(modulationSpan); egAmplitude.getBlock(modulationSpan);
// Amplitude envelope // Amplitude envelope
applyGain1<float>(baseGain, modulationSpan); applyGain1<float>(baseGain, modulationSpan);
@ -572,8 +572,8 @@ void sfz::Voice::fillWithData(AudioSpan<float> buffer) noexcept
<< " for sample " << region->sampleId); << " for sample " << region->sampleId);
} }
#endif #endif
egEnvelope.setReleaseTime(0.0f); egAmplitude.setReleaseTime(0.0f);
egEnvelope.startRelease(i); egAmplitude.startRelease(i);
fill<int>(indices->subspan(i), sampleEnd); fill<int>(indices->subspan(i), sampleEnd);
fill<float>(coeffs->subspan(i), 1.0f); fill<float>(coeffs->subspan(i), 1.0f);
break; break;
@ -853,7 +853,7 @@ float sfz::Voice::getAveragePower() const noexcept
bool sfz::Voice::releasedOrFree() 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 uint32_t sfz::Voice::getSourcePosition() const noexcept
@ -903,6 +903,22 @@ void sfz::Voice::setMaxFlexEGsPerVoice(size_t numFlexEGs)
} }
} }
void sfz::Voice::setPitchEGEnabledPerVoice(bool havePitchEG)
{
if (havePitchEG)
egPitch.reset(new ADSREnvelope<float>);
else
egPitch.reset();
}
void sfz::Voice::setFilterEGEnabledPerVoice(bool haveFilterEG)
{
if (haveFilterEG)
egFilter.reset(new ADSREnvelope<float>);
else
egFilter.reset();
}
void sfz::Voice::setupOscillatorUnison() void sfz::Voice::setupOscillatorUnison()
{ {
const int m = region->oscillatorMulti; const int m = region->oscillatorMulti;

View file

@ -296,6 +296,18 @@ public:
* @param numFlexEGs * @param numFlexEGs
*/ */
void setMaxFlexEGsPerVoice(size_t 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 * @brief Release the voice after a given delay
* *
@ -324,6 +336,20 @@ public:
Duration getLastFilterDuration() const noexcept { return filterDuration; } Duration getLastFilterDuration() const noexcept { return filterDuration; }
Duration getLastPanningDuration() const noexcept { return panningDuration; } Duration getLastPanningDuration() const noexcept { return panningDuration; }
/**
* @brief Get the SFZv1 pitch EG, if existing
*/
ADSREnvelope<float>* getPitchEG() { return egPitch.get(); }
/**
* @brief Get the SFZv1 filter EG, if existing
*/
ADSREnvelope<float>* getFilterEG() { return egFilter.get(); }
/**
* @brief Get the trigger event
*/
const TriggerEvent& getTriggerEvent() { return triggerEvent; }
private: private:
/** /**
* @brief Fill a span with data from a file source. This is the first step * @brief Fill a span with data from a file source. This is the first step
@ -461,7 +487,9 @@ private:
std::vector<std::unique_ptr<LFO>> lfos; std::vector<std::unique_ptr<LFO>> lfos;
std::vector<std::unique_ptr<FlexEnvelope>> flexEGs; std::vector<std::unique_ptr<FlexEnvelope>> flexEGs;
ADSREnvelope<float> egEnvelope; ADSREnvelope<float> egAmplitude;
std::unique_ptr<ADSREnvelope<float>> egPitch;
std::unique_ptr<ADSREnvelope<float>> egFilter;
float bendStepFactor { centsFactor(1) }; float bendStepFactor { centsFactor(1) };
WavetableOscillator waveOscillators[config::oscillatorsPerVoice]; WavetableOscillator waveOscillators[config::oscillatorsPerVoice];

View file

@ -30,6 +30,10 @@ int ModIds::flags(ModId id) noexcept
return kModIsPerVoice; return kModIsPerVoice;
case ModId::LFO: case ModId::LFO:
return kModIsPerVoice; return kModIsPerVoice;
case ModId::PitchEG:
return kModIsPerVoice;
case ModId::FilEG:
return kModIsPerVoice;
// targets // targets
case ModId::Amplitude: case ModId::Amplitude:

View file

@ -23,6 +23,8 @@ enum class ModId : int {
Controller = _SourcesStart, Controller = _SourcesStart,
Envelope, Envelope,
LFO, LFO,
PitchEG,
FilEG,
_SourcesEnd, _SourcesEnd,

View file

@ -74,6 +74,10 @@ std::string ModKey::toString() const
return absl::StrCat("EG ", 1 + params_.N, " {", region_.number(), "}"); return absl::StrCat("EG ", 1 + params_.N, " {", region_.number(), "}");
case ModId::LFO: case ModId::LFO:
return absl::StrCat("LFO ", 1 + params_.N, " {", region_.number(), "}"); 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: case ModId::Amplitude:
return absl::StrCat("Amplitude {", region_.number(), "}"); return absl::StrCat("Amplitude {", region_.number(), "}");

View file

@ -27,6 +27,8 @@ struct ModMatrix::Impl {
NumericId<Voice> currentVoiceId_ {}; NumericId<Voice> currentVoiceId_ {};
NumericId<Region> currentRegionId_ {}; NumericId<Region> currentRegionId_ {};
float currentVoiceTriggerValue_ {};
struct Source { struct Source {
ModKey key; ModKey key;
ModGenerator* gen {}; ModGenerator* gen {};
@ -36,6 +38,7 @@ struct ModMatrix::Impl {
struct ConnectionData { struct ConnectionData {
float sourceDepth_ {}; float sourceDepth_ {};
float velToDepth_ {};
}; };
struct Target { struct Target {
@ -190,7 +193,7 @@ ModMatrix::TargetId ModMatrix::findTarget(const ModKey& key) const
return TargetId(it->second); 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_; Impl& impl = *impl_;
unsigned sourceIndex = sourceId.number(); unsigned sourceIndex = sourceId.number();
@ -202,6 +205,7 @@ bool ModMatrix::connect(SourceId sourceId, TargetId targetId, float sourceDepth)
Impl::Target& target = impl.targets_[targetIndex]; Impl::Target& target = impl.targets_[targetIndex];
Impl::ConnectionData& conn = target.connectedSources[sourceIndex]; Impl::ConnectionData& conn = target.connectedSources[sourceIndex];
conn.sourceDepth_ = sourceDepth; conn.sourceDepth_ = sourceDepth;
conn.velToDepth_ = velToDepth;
return true; return true;
} }
@ -302,13 +306,15 @@ void ModMatrix::endCycle()
impl.numFrames_ = 0; impl.numFrames_ = 0;
} }
void ModMatrix::beginVoice(NumericId<Voice> voiceId, NumericId<Region> regionId) void ModMatrix::beginVoice(NumericId<Voice> voiceId, NumericId<Region> regionId, float triggerValue)
{ {
Impl& impl = *impl_; Impl& impl = *impl_;
impl.currentVoiceId_ = voiceId; impl.currentVoiceId_ = voiceId;
impl.currentRegionId_ = regionId; impl.currentRegionId_ = regionId;
impl.currentVoiceTriggerValue_ = triggerValue;
ASSERT(regionId); ASSERT(regionId);
const auto idNumber = static_cast<size_t>(regionId.number()); const auto idNumber = static_cast<size_t>(regionId.number());
@ -345,6 +351,8 @@ void ModMatrix::endVoice()
impl.currentVoiceId_ = {}; impl.currentVoiceId_ = {};
impl.currentRegionId_ = {}; impl.currentRegionId_ = {};
impl.currentVoiceTriggerValue_ = 0.0f;
} }
float* ModMatrix::getModulation(TargetId targetId) float* ModMatrix::getModulation(TargetId targetId)
@ -354,6 +362,7 @@ float* ModMatrix::getModulation(TargetId targetId)
Impl& impl = *impl_; Impl& impl = *impl_;
const NumericId<Region> regionId = impl.currentRegionId_; const NumericId<Region> regionId = impl.currentRegionId_;
const float triggerValue = impl.currentVoiceTriggerValue_;
const uint32_t targetIndex = targetId.number(); const uint32_t targetIndex = targetId.number();
Impl::Target &target = impl.targets_[targetIndex]; Impl::Target &target = impl.targets_[targetIndex];
const int targetFlags = target.key.flags(); const int targetFlags = target.key.flags();
@ -381,7 +390,6 @@ float* ModMatrix::getModulation(TargetId targetId)
// then add or multiply, depending on target flags // then add or multiply, depending on target flags
while (sourcesPos != sourcesEnd) { while (sourcesPos != sourcesEnd) {
Impl::Source &source = impl.sources_[sourcesPos->first]; Impl::Source &source = impl.sources_[sourcesPos->first];
const float sourceDepth = sourcesPos->second.sourceDepth_;
const int sourceFlags = source.key.flags(); const int sourceFlags = source.key.flags();
// only accept per-voice sources of the same region // only accept per-voice sources of the same region
@ -398,6 +406,12 @@ float* ModMatrix::getModulation(TargetId targetId)
source.bufferReady = true; source.bufferReady = true;
} }
float sourceDepth = sourcesPos->second.sourceDepth_;
if (sourceFlags & kModIsPerVoice) {
const float velToDepth = sourcesPos->second.velToDepth_;
sourceDepth += triggerValue * velToDepth;
}
if (isFirstSource) { if (isFirstSource) {
if (sourceDepth != 1) { if (sourceDepth != 1) {
for (uint32_t i = 0; i < numFrames; ++i) for (uint32_t i = 0; i < numFrames; ++i)

View file

@ -92,9 +92,10 @@ public:
* @param sourceId source of the connection * @param sourceId source of the connection
* @param targetId target of the connection * @param targetId target of the connection
* @param sourceDepth amount which multiplies the source output * @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 * @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. * @brief Reinitialize modulation sources overall.
@ -134,8 +135,9 @@ public:
* *
* @param voiceId the identifier of the current voice * @param voiceId the identifier of the current voice
* @param regionId the identifier of the region 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<Voice> voiceId, NumericId<Region> regionId); void beginVoice(NumericId<Voice> voiceId, NumericId<Region> regionId, float triggerValue);
/** /**
* @brief End modulation processing for a given voice. * @brief End modulation processing for a given voice.

View file

@ -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<Voice> voiceId, unsigned delay)
{
Synth& synth = *synth_;
Voice* voice = synth.getVoiceById(voiceId);
if (!voice) {
ASSERTFALSE;
return;
}
const Region* region = voice->getRegion();
ADSREnvelope<float>* 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<Voice> voiceId, unsigned delay)
{
Synth& synth = *synth_;
Voice* voice = synth.getVoiceById(voiceId);
if (!voice) {
ASSERTFALSE;
return;
}
ADSREnvelope<float>* 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<Voice> voiceId, absl::Span<float> buffer)
{
Synth& synth = *synth_;
Voice* voice = synth.getVoiceById(voiceId);
if (!voice) {
ASSERTFALSE;
return;
}
ADSREnvelope<float>* 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

View file

@ -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<Voice> voiceId, unsigned delay) override;
void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
private:
Synth* synth_ = nullptr;
};
} // namespace sfz