diff --git a/dpf.mk b/dpf.mk index 1c8159fa..71d4555f 100644 --- a/dpf.mk +++ b/dpf.mk @@ -65,6 +65,7 @@ SFIZZ_SOURCES = \ src/sfizz/modulations/ModKey.cpp \ src/sfizz/modulations/ModKeyHash.cpp \ src/sfizz/modulations/ModMatrix.cpp \ + src/sfizz/modulations/sources/ADSREnvelope.cpp \ src/sfizz/modulations/sources/Controller.cpp \ src/sfizz/modulations/sources/FlexEGDescription.cpp \ src/sfizz/modulations/sources/FlexEnvelope.cpp \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2f65ea3c..11d48265 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,6 +35,7 @@ set (SFIZZ_HEADERS sfizz/modulations/ModKeyHash.h sfizz/modulations/ModMatrix.h sfizz/modulations/ModGenerator.h + sfizz/modulations/sources/ADSREnvelope.h sfizz/modulations/sources/Controller.h sfizz/modulations/sources/FlexEnvelope.h sfizz/modulations/sources/LFO.h @@ -151,6 +152,7 @@ set (SFIZZ_SOURCES sfizz/modulations/ModMatrix.cpp sfizz/modulations/sources/Controller.cpp sfizz/modulations/sources/FlexEnvelope.cpp + sfizz/modulations/sources/ADSREnvelope.cpp sfizz/modulations/sources/LFO.cpp sfizz/utility/SpinMutex.cpp sfizz/effects/Nothing.cpp diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 27b7c240..1550e749 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -250,6 +250,8 @@ namespace Default constexpr Range egDepthRange { -12000, 12000 }; constexpr Range egOnCCTimeRange { -100.0, 100.0 }; constexpr Range egOnCCPercentRange { -100.0, 100.0 }; + constexpr Range pitchEgDepthRange { -12000.0, 12000.0 }; + constexpr Range filterEgDepthRange { -12000.0, 12000.0 }; // Flex envelope generators constexpr int numFlexEGs { 4 }; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 7ca3e928..967e92d9 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1238,6 +1238,22 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) ModKey::createNXYZ(ModId::FilCutoff, id)); break; + case hash("pitcheg_depth"): + if (auto value = readOpcode(opcode.value, Default::pitchEgDepthRange)) + getOrCreateConnection( + ModKey::createNXYZ(ModId::PitchEG, id), + ModKey::createNXYZ(ModId::Pitch, id)).sourceDepth = *value; + break; + case hash("fileg_depth"): + if (auto value = readOpcode(opcode.value, Default::filterEgDepthRange)) + getOrCreateConnection( + ModKey::createNXYZ(ModId::FilEG, id), + ModKey::createNXYZ(ModId::FilCutoff, id)).sourceDepth = *value; + break; + + // TODO(jpc): pitcheg_vel2depth + // TODO(jpc): fileg_vel2depth + // Flex envelopes case hash("eg&_dynamic"): { diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index c58340f8..c928f48d 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -19,6 +19,7 @@ #include "modulations/sources/Controller.h" #include "modulations/sources/LFO.h" #include "modulations/sources/FlexEnvelope.h" +#include "modulations/sources/ADSREnvelope.h" #include "utility/XmlHelpers.h" #include "pugixml.hpp" #include "absl/algorithm/container.h" @@ -49,6 +50,7 @@ sfz::Synth::Synth(int numVoices) genController.reset(new ControllerSource(resources)); genLFO.reset(new LFOSource(*this)); genFlexEnvelope.reset(new FlexEnvelopeSource(*this)); + genADSREnvelope.reset(new ADSREnvelopeSource(*this)); } sfz::Synth::~Synth() @@ -489,6 +491,8 @@ void sfz::Synth::finalizeSfzLoad() size_t maxEQs { 0 }; size_t maxLFOs { 0 }; size_t maxFlexEGs { 0 }; + bool havePitchEG { false }; + bool haveFilterEG { false }; FlexEGs::clearUnusedCurves(); @@ -613,6 +617,8 @@ void sfz::Synth::finalizeSfzLoad() maxEQs = max(maxEQs, region->equalizers.size()); maxLFOs = max(maxLFOs, region->lfos.size()); maxFlexEGs = max(maxFlexEGs, region->flexEGs.size()); + havePitchEG = havePitchEG || region->pitchEG != absl::nullopt; + haveFilterEG = haveFilterEG || region->filterEG != absl::nullopt; ++currentRegionIndex; } @@ -624,6 +630,8 @@ void sfz::Synth::finalizeSfzLoad() settingsPerVoice.maxEQs = maxEQs; settingsPerVoice.maxLFOs = maxLFOs; settingsPerVoice.maxFlexEGs = maxFlexEGs; + settingsPerVoice.havePitchEG = havePitchEG; + settingsPerVoice.haveFilterEG = haveFilterEG; applySettingsPerVoice(); @@ -1499,6 +1507,8 @@ void sfz::Synth::applySettingsPerVoice() voice->setMaxEQsPerVoice(settingsPerVoice.maxEQs); voice->setMaxLFOsPerVoice(settingsPerVoice.maxLFOs); voice->setMaxFlexEGsPerVoice(settingsPerVoice.maxFlexEGs); + voice->setPitchEGEnabledPerVoice(settingsPerVoice.havePitchEG); + voice->setFilterEGEnabledPerVoice(settingsPerVoice.haveFilterEG); } } @@ -1520,6 +1530,10 @@ void sfz::Synth::setupModMatrix() case ModId::Envelope: gen = genFlexEnvelope.get(); break; + case ModId::PitchEG: + case ModId::FilEG: + gen = genADSREnvelope.get(); + break; default: DBG("[sfizz] Have unknown type of source generator"); break; diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index c308666e..880726cf 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -29,6 +29,7 @@ namespace sfz { class ControllerSource; class LFOSource; class FlexEnvelopeSource; +class ADSREnvelopeSource; /** * @brief This class is the core of the sfizz library. In C++ it is the main point @@ -550,6 +551,7 @@ public: */ void disableFreeWheeling() noexcept; + Resources& getResources() noexcept { return resources; } const Resources& getResources() const noexcept { return resources; } /** @@ -922,6 +924,7 @@ private: std::unique_ptr genController; std::unique_ptr genLFO; std::unique_ptr genFlexEnvelope; + std::unique_ptr genADSREnvelope; // Settings per voice struct SettingsPerVoice { @@ -929,6 +932,8 @@ private: size_t maxEQs { 0 }; size_t maxLFOs { 0 }; size_t maxFlexEGs { 0 }; + bool havePitchEG { false }; + bool haveFilterEG { false }; }; SettingsPerVoice settingsPerVoice; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 81f7c158..a60b1af7 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -903,6 +903,22 @@ void sfz::Voice::setMaxFlexEGsPerVoice(size_t numFlexEGs) } } +void sfz::Voice::setPitchEGEnabledPerVoice(bool havePitchEG) +{ + if (havePitchEG) + egPitch.reset(new ADSREnvelope); + else + egPitch.reset(); +} + +void sfz::Voice::setFilterEGEnabledPerVoice(bool haveFilterEG) +{ + if (haveFilterEG) + egFilter.reset(new ADSREnvelope); + else + egFilter.reset(); +} + void sfz::Voice::setupOscillatorUnison() { const int m = region->oscillatorMulti; diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 02062752..419c6584 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -296,6 +296,18 @@ public: * @param numFlexEGs */ void setMaxFlexEGsPerVoice(size_t numFlexEGs); + /** + * @brief Set whether SFZv1 pitch EG is enabled on this voice + * + * @param havePitchEG + */ + void setPitchEGEnabledPerVoice(bool havePitchEG); + /** + * @brief Set whether SFZv1 filter EG is enabled on this voice + * + * @param haveFilterEG + */ + void setFilterEGEnabledPerVoice(bool haveFilterEG); /** * @brief Release the voice after a given delay * @@ -324,6 +336,20 @@ public: Duration getLastFilterDuration() const noexcept { return filterDuration; } Duration getLastPanningDuration() const noexcept { return panningDuration; } + /** + * @brief Get the SFZv1 pitch EG, if existing + */ + ADSREnvelope* getPitchEG() { return egPitch.get(); } + /** + * @brief Get the SFZv1 filter EG, if existing + */ + ADSREnvelope* getFilterEG() { return egFilter.get(); } + + /** + * @brief Get the trigger event + */ + const TriggerEvent& getTriggerEvent() { return triggerEvent; } + private: /** * @brief Fill a span with data from a file source. This is the first step @@ -462,6 +488,8 @@ private: std::vector> flexEGs; ADSREnvelope egAmplitude; + std::unique_ptr> egPitch; + std::unique_ptr> egFilter; float bendStepFactor { centsFactor(1) }; WavetableOscillator waveOscillators[config::oscillatorsPerVoice]; diff --git a/src/sfizz/modulations/sources/ADSREnvelope.cpp b/src/sfizz/modulations/sources/ADSREnvelope.cpp new file mode 100644 index 00000000..7128b216 --- /dev/null +++ b/src/sfizz/modulations/sources/ADSREnvelope.cpp @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "ADSREnvelope.h" +#include "../../ADSREnvelope.h" +#include "../../Synth.h" +#include "../../Voice.h" +#include "../../Config.h" +#include "../../Debug.h" + +// TODO(jpc): also matrix the ampeg + +namespace sfz { + +ADSREnvelopeSource::ADSREnvelopeSource(Synth &synth) + : synth_(&synth) +{ +} + +void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) +{ + Synth& synth = *synth_; + + Voice* voice = synth.getVoiceById(voiceId); + if (!voice) { + ASSERTFALSE; + return; + } + + const Region* region = voice->getRegion(); + ADSREnvelope* eg = nullptr; + const EGDescription* desc = nullptr; + + switch (sourceKey.id()) { + case ModId::PitchEG: + eg = voice->getPitchEG(); + ASSERT(eg); + desc = &*region->pitchEG; + break; + case ModId::FilEG: + eg = voice->getFilterEG(); + ASSERT(eg); + desc = &*region->filterEG; + break; + default: + ASSERTFALSE; + return; + } + + Resources& resources = synth.getResources(); + const TriggerEvent& triggerEvent = voice->getTriggerEvent(); + const float sampleRate = voice->getSampleRate(); + eg->reset(*desc, *region, resources.midiState, delay, triggerEvent.value, sampleRate); +} + +void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) +{ + Synth& synth = *synth_; + + Voice* voice = synth.getVoiceById(voiceId); + if (!voice) { + ASSERTFALSE; + return; + } + + ADSREnvelope* eg = nullptr; + + switch (sourceKey.id()) { + case ModId::PitchEG: + eg = voice->getPitchEG(); + ASSERT(eg); + break; + case ModId::FilEG: + eg = voice->getFilterEG(); + ASSERT(eg); + break; + default: + ASSERTFALSE; + return; + } + + eg->startRelease(delay); +} + +void ADSREnvelopeSource::generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) +{ + Synth& synth = *synth_; + + Voice* voice = synth.getVoiceById(voiceId); + if (!voice) { + ASSERTFALSE; + return; + } + + ADSREnvelope* eg = nullptr; + + switch (sourceKey.id()) { + case ModId::PitchEG: + eg = voice->getPitchEG(); + ASSERT(eg); + break; + case ModId::FilEG: + eg = voice->getFilterEG(); + ASSERT(eg); + break; + default: + ASSERTFALSE; + return; + } + + eg->getBlock(buffer); +} + +} // namespace sfz diff --git a/src/sfizz/modulations/sources/ADSREnvelope.h b/src/sfizz/modulations/sources/ADSREnvelope.h new file mode 100644 index 00000000..b2644df9 --- /dev/null +++ b/src/sfizz/modulations/sources/ADSREnvelope.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once +#include "../ModGenerator.h" + +namespace sfz { +class Synth; + +class ADSREnvelopeSource : public ModGenerator { +public: + explicit ADSREnvelopeSource(Synth &synth); + void init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; + void release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; + void generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) override; + +private: + Synth* synth_ = nullptr; +}; + +} // namespace sfz