Merge pull request #438 from jpcima/egv1-opcode-proc
Pitch and Filter EG v1
This commit is contained in:
commit
dde743af76
18 changed files with 526 additions and 128 deletions
1
dpf.mk
1
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 \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<std::pair<int, ValueType>>::iterator begin() { return container.begin(); }
|
||||
// typename std::vector<std::pair<int, ValueType>>::iterator end() { return container.end(); }
|
||||
|
||||
const ValueType defaultValue;
|
||||
ValueType defaultValue;
|
||||
std::vector<CCData<ValueType>> container;
|
||||
LEAK_DETECTOR(CCMap);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -250,6 +250,8 @@ namespace Default
|
|||
constexpr Range<int> egDepthRange { -12000, 12000 };
|
||||
constexpr Range<float> egOnCCTimeRange { -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
|
||||
constexpr int numFlexEGs { 4 };
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -1164,111 +1164,108 @@ 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"):
|
||||
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)
|
||||
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;
|
||||
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;
|
||||
|
||||
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
|
||||
|
|
@ -1369,6 +1366,143 @@ 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::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)
|
||||
{
|
||||
if (!opcode.isAnyCcN())
|
||||
|
|
|
|||
|
|
@ -256,6 +256,26 @@ 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 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.
|
||||
*
|
||||
|
|
@ -400,8 +420,8 @@ struct Region {
|
|||
|
||||
// Envelopes
|
||||
EGDescription amplitudeEG;
|
||||
EGDescription pitchEG;
|
||||
EGDescription filterEG;
|
||||
absl::optional<EGDescription> pitchEG;
|
||||
absl::optional<EGDescription> filterEG;
|
||||
|
||||
// Envelopes
|
||||
std::vector<FlexEGDescription> flexEGs;
|
||||
|
|
@ -421,7 +441,8 @@ struct Region {
|
|||
struct Connection {
|
||||
ModKey source;
|
||||
ModKey target;
|
||||
float sourceDepth = 1.0f;
|
||||
float sourceDepth = 0.0f;
|
||||
float velToDepth = 0.0f;
|
||||
};
|
||||
std::vector<Connection> connections;
|
||||
Connection& getOrCreateConnection(const ModKey& source, const ModKey& target);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -152,10 +154,10 @@ void sfz::Synth::buildRegion(const std::vector<Opcode>& 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<Opcode>& opcodes) {
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
@ -812,7 +820,7 @@ void sfz::Synth::renderBlock(AudioSpan<float> 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);
|
||||
|
|
@ -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;
|
||||
|
|
@ -1544,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;
|
||||
}
|
||||
|
|
@ -1702,20 +1716,24 @@ void sfz::Synth::updateUsedCCsFromRegion(std::bitset<sfz::config::numCCs>& 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);
|
||||
|
|
|
|||
|
|
@ -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<ControllerSource> genController;
|
||||
std::unique_ptr<LFOSource> genLFO;
|
||||
std::unique_ptr<FlexEnvelopeSource> genFlexEnvelope;
|
||||
std::unique_ptr<ADSREnvelopeSource> 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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<float> 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<float> modulationSpan) noexcept
|
|||
ModMatrix& mm = resources.modMatrix;
|
||||
|
||||
// AmpEG envelope
|
||||
egEnvelope.getBlock(modulationSpan);
|
||||
egAmplitude.getBlock(modulationSpan);
|
||||
|
||||
// Amplitude envelope
|
||||
applyGain1<float>(baseGain, modulationSpan);
|
||||
|
|
@ -572,8 +572,8 @@ void sfz::Voice::fillWithData(AudioSpan<float> buffer) noexcept
|
|||
<< " for sample " << region->sampleId);
|
||||
}
|
||||
#endif
|
||||
egEnvelope.setReleaseTime(0.0f);
|
||||
egEnvelope.startRelease(i);
|
||||
egAmplitude.setReleaseTime(0.0f);
|
||||
egAmplitude.startRelease(i);
|
||||
fill<int>(indices->subspan(i), sampleEnd);
|
||||
fill<float>(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
|
||||
|
|
@ -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()
|
||||
{
|
||||
const int m = region->oscillatorMulti;
|
||||
|
|
|
|||
|
|
@ -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<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:
|
||||
/**
|
||||
* @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<FlexEnvelope>> flexEGs;
|
||||
|
||||
ADSREnvelope<float> egEnvelope;
|
||||
ADSREnvelope<float> egAmplitude;
|
||||
std::unique_ptr<ADSREnvelope<float>> egPitch;
|
||||
std::unique_ptr<ADSREnvelope<float>> egFilter;
|
||||
float bendStepFactor { centsFactor(1) };
|
||||
|
||||
WavetableOscillator waveOscillators[config::oscillatorsPerVoice];
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ enum class ModId : int {
|
|||
Controller = _SourcesStart,
|
||||
Envelope,
|
||||
LFO,
|
||||
PitchEG,
|
||||
FilEG,
|
||||
|
||||
_SourcesEnd,
|
||||
|
||||
|
|
|
|||
|
|
@ -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(), "}");
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ struct ModMatrix::Impl {
|
|||
NumericId<Voice> currentVoiceId_ {};
|
||||
NumericId<Region> 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<Voice> voiceId, NumericId<Region> regionId)
|
||||
void ModMatrix::beginVoice(NumericId<Voice> voiceId, NumericId<Region> regionId, float triggerValue)
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
|
||||
impl.currentVoiceId_ = voiceId;
|
||||
impl.currentRegionId_ = regionId;
|
||||
|
||||
impl.currentVoiceTriggerValue_ = triggerValue;
|
||||
|
||||
ASSERT(regionId);
|
||||
|
||||
const auto idNumber = static_cast<size_t>(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<Region> 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)
|
||||
|
|
|
|||
|
|
@ -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<Voice> voiceId, NumericId<Region> regionId);
|
||||
void beginVoice(NumericId<Voice> voiceId, NumericId<Region> regionId, float triggerValue);
|
||||
|
||||
/**
|
||||
* @brief End modulation processing for a given voice.
|
||||
|
|
|
|||
117
src/sfizz/modulations/sources/ADSREnvelope.cpp
Normal file
117
src/sfizz/modulations/sources/ADSREnvelope.cpp
Normal 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
|
||||
24
src/sfizz/modulations/sources/ADSREnvelope.h
Normal file
24
src/sfizz/modulations/sources/ADSREnvelope.h
Normal 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
|
||||
Loading…
Add table
Reference in a new issue