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/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 \

View file

@ -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

View file

@ -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);
};

View file

@ -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 };

View file

@ -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 };

View file

@ -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())

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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];

View file

@ -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:

View file

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

View file

@ -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(), "}");

View file

@ -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)

View file

@ -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.

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