diff --git a/dpf.mk b/dpf.mk index 2ee507e8..1c8159fa 100644 --- a/dpf.mk +++ b/dpf.mk @@ -66,6 +66,8 @@ SFIZZ_SOURCES = \ src/sfizz/modulations/ModKeyHash.cpp \ src/sfizz/modulations/ModMatrix.cpp \ src/sfizz/modulations/sources/Controller.cpp \ + src/sfizz/modulations/sources/FlexEGDescription.cpp \ + src/sfizz/modulations/sources/FlexEnvelope.cpp \ src/sfizz/modulations/sources/LFO.cpp \ src/sfizz/effects/Compressor.cpp \ src/sfizz/effects/Disto.cpp \ @@ -91,6 +93,7 @@ SFIZZ_SOURCES = \ src/sfizz/FileMetadata.cpp \ src/sfizz/FilePool.cpp \ src/sfizz/FilterPool.cpp \ + src/sfizz/FlexEnvelope.cpp \ src/sfizz/FloatEnvelopes.cpp \ src/sfizz/Logger.cpp \ src/sfizz/LFO.cpp \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6083c110..4adbe3fb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,6 +35,7 @@ set (SFIZZ_HEADERS sfizz/modulations/ModMatrix.h sfizz/modulations/ModGenerator.h sfizz/modulations/sources/Controller.h + sfizz/modulations/sources/FlexEnvelope.h sfizz/modulations/sources/LFO.h sfizz/effects/impl/ResonantArray.h sfizz/effects/impl/ResonantArrayAVX.h @@ -67,6 +68,8 @@ set (SFIZZ_HEADERS sfizz/FilePool.h sfizz/FilterDescription.h sfizz/FilterPool.h + sfizz/FlexEGDescription.h + sfizz/FlexEnvelope.h sfizz/HistoricalBuffer.h sfizz/Interpolators.h sfizz/Interpolators.hpp @@ -139,11 +142,14 @@ set (SFIZZ_SOURCES sfizz/LFO.cpp sfizz/LFODescription.cpp sfizz/PowerFollower.cpp + sfizz/FlexEGDescription.cpp + sfizz/FlexEnvelope.cpp sfizz/modulations/ModId.cpp sfizz/modulations/ModKey.cpp sfizz/modulations/ModKeyHash.cpp sfizz/modulations/ModMatrix.cpp sfizz/modulations/sources/Controller.cpp + sfizz/modulations/sources/FlexEnvelope.cpp sfizz/modulations/sources/LFO.cpp sfizz/utility/SpinMutex.cpp sfizz/effects/Nothing.cpp diff --git a/src/sfizz/Curve.cpp b/src/sfizz/Curve.cpp index 89320db5..8d204a39 100644 --- a/src/sfizz/Curve.cpp +++ b/src/sfizz/Curve.cpp @@ -147,6 +147,13 @@ Curve Curve::buildBipolar(float v1, float v2) return curve; } +Curve Curve::buildFromPoints(const float points[NumValues]) +{ + Curve curve; + copy(absl::MakeConstSpan(points, NumValues), absl::Span(curve._points)); + return curve; +} + const Curve& Curve::getDefault() { return defaultCurve; diff --git a/src/sfizz/Curve.h b/src/sfizz/Curve.h index 3cf4b2cb..5c0ed361 100644 --- a/src/sfizz/Curve.h +++ b/src/sfizz/Curve.h @@ -21,6 +21,8 @@ struct Opcode; */ class Curve { public: + enum { NumValues = 128 }; + /** * @brief Compute the curve for integral x in domain [0:127] */ @@ -93,14 +95,16 @@ public: */ static Curve buildBipolar(float v1, float v2); + /** + * @brief Build a curve from a table of points + */ + static Curve buildFromPoints(const float points[NumValues]); + /** * @brief Get a linear curve from 0 to 1 */ static const Curve& getDefault(); -private: - enum { NumValues = 128 }; - private: void fill(Interpolator itp, const bool fillStatus[NumValues]); void lerpFill(const bool fillStatus[NumValues]); diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index d53f3772..b12d1b8a 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -244,6 +244,20 @@ namespace Default constexpr Range egOnCCTimeRange { -100.0, 100.0 }; constexpr Range egOnCCPercentRange { -100.0, 100.0 }; + // Flex envelope generators + constexpr int numFlexEGs { 4 }; + constexpr int numFlexEGPoints { 8 }; + constexpr int flexEGDynamic { 0 }; + constexpr int flexEGSustain { 0 }; + constexpr float flexEGPointTime { 0 }; + constexpr float flexEGPointLevel { 0 }; + constexpr float flexEGPointShape { 0 }; + constexpr Range flexEGDynamicRange { 0, 1 }; + constexpr Range flexEGSustainRange { 0, 100 }; + constexpr Range flexEGPointTimeRange { 0.0f, 100.0f }; + constexpr Range flexEGPointLevelRange { -1.0f, 1.0f }; + constexpr Range flexEGPointShapeRange { -100.0f, 100.0f }; + // ***** SFZ v2 ******** constexpr int sampleQuality { 1 }; constexpr int sampleQualityInFreewheelingMode { 10 }; // for future use, possibly excessive diff --git a/src/sfizz/FlexEGDescription.cpp b/src/sfizz/FlexEGDescription.cpp new file mode 100644 index 00000000..18ea92e3 --- /dev/null +++ b/src/sfizz/FlexEGDescription.cpp @@ -0,0 +1,87 @@ +// 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 "FlexEGDescription.h" +#include "Curve.h" +#include +#include + +namespace sfz { + +void FlexEGPoint::setShape(float shape) +{ + shape_ = shape; + shapeCurve_ = FlexEGs::getShapeCurve(shape); +} + +const Curve& FlexEGPoint::curve() const +{ + if (shapeCurve_) + return *shapeCurve_; + else + return Curve::getDefault(); +} + +/// +typedef absl::flat_hash_map> FlexEGShapes; + +static FlexEGShapes& getShapeMap() +{ + static FlexEGShapes shapes; + return shapes; +} + +std::shared_ptr FlexEGs::getShapeCurve(float shape) +{ + static FlexEGShapes& map = getShapeMap(); + + std::weak_ptr& slot = map[shape]; + + std::shared_ptr curve = slot.lock(); + if (curve) + return curve; + + curve.reset(new Curve); + + /// + constexpr unsigned numPoints = Curve::NumValues; + float points[numPoints]; + + if (shape == 0) + *curve = Curve::getDefault(); + else if (shape > 0) { + for (unsigned i = 0; i < numPoints; ++i) { + float x = float(i) / (numPoints - 1); + points[i] = std::pow(x, shape); + } + *curve = Curve::buildFromPoints(points); + } + else if (shape < 0) { + for (unsigned i = 0; i < numPoints; ++i) { + float x = float(i) / (numPoints - 1); + points[i] = 1 - std::pow(1 - x, -shape); + } + *curve = Curve::buildFromPoints(points); + } + + /// + slot = curve; + return curve; +} + +void FlexEGs::clearUnusedCurves() +{ + static FlexEGShapes& map = getShapeMap(); + + for (auto it = map.begin(); it != map.end(); ) { + if (it->second.use_count() == 0) + map.erase(it++); + else + ++it; + } +} + +} // namespace sfz diff --git a/src/sfizz/FlexEGDescription.h b/src/sfizz/FlexEGDescription.h new file mode 100644 index 00000000..fd8e9428 --- /dev/null +++ b/src/sfizz/FlexEGDescription.h @@ -0,0 +1,39 @@ +// 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 "Defaults.h" +#include +#include + +namespace sfz { +class Curve; + +namespace FlexEGs { + std::shared_ptr getShapeCurve(float shape); + void clearUnusedCurves(); +}; + +struct FlexEGPoint { + float time { Default::flexEGPointTime }; // duration until next step (s) + float level { Default::flexEGPointLevel }; // normalized amplitude + + void setShape(float shape); + float shape() const noexcept { return shape_; } + const Curve& curve() const; + +private: + float shape_ { Default::flexEGPointShape }; // 0: linear, positive: exp, negative: log + std::shared_ptr shapeCurve_; +}; + +struct FlexEGDescription { + int dynamic { Default::flexEGDynamic }; // whether parameters can be modulated while EG runs + int sustain { Default::flexEGSustain }; // index of the sustain point (default to 0 in ARIA) + std::vector points; +}; + +} // namespace sfz diff --git a/src/sfizz/FlexEnvelope.cpp b/src/sfizz/FlexEnvelope.cpp new file mode 100644 index 00000000..3810ec73 --- /dev/null +++ b/src/sfizz/FlexEnvelope.cpp @@ -0,0 +1,206 @@ +// 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 + +/* + Note(jpc): implementation status + +- [ ] egN_points (purpose unknown) +- [x] egN_timeX +- [x] egN_levelX +- [x] egN_shapeX +- [x] egN_sustain +- [ ] egN_dynamic +*/ + +#include "FlexEnvelope.h" +#include "FlexEGDescription.h" +#include "Curve.h" +#include "Config.h" +#include "SIMDHelpers.h" +#include + +namespace sfz { + +struct FlexEnvelope::Impl { + const FlexEGDescription* desc_ { nullptr }; + float samplePeriod_ { 1.0 / config::defaultSampleRate }; + size_t delayFramesLeft_ { 0 }; + + // + float stageSourceLevel_ { 0.0 }; + float stageTargetLevel_ { 0.0 }; + float stageTime_ { 0.0 }; + bool stageSustained_ { false }; + const Curve* stageCurve_ { nullptr }; + + // + unsigned currentStageNumber_ { 0 }; + float currentLevel_ { 0.0 }; + float currentTime_ { 0.0 }; + absl::optional currentFramesUntilRelease_ { absl::nullopt }; + bool isReleased_ { false }; + + // + void process(absl::Span out); + bool advanceToNextStage(); +}; + +FlexEnvelope::FlexEnvelope() + : impl_(new Impl) +{ +} + +FlexEnvelope::~FlexEnvelope() +{ +} + +void FlexEnvelope::setSampleRate(double sampleRate) +{ + Impl& impl = *impl_; + impl.samplePeriod_ = 1.0 / sampleRate; +} + +void FlexEnvelope::configure(const FlexEGDescription* desc) +{ + Impl& impl = *impl_; + impl.desc_ = desc; +} + +void FlexEnvelope::start(unsigned triggerDelay) +{ + Impl& impl = *impl_; + const FlexEGDescription& desc = *impl.desc_; + + impl.delayFramesLeft_ = triggerDelay; + + FlexEGPoint point; + if (!desc.points.empty()) + point = desc.points[0]; + + // + impl.stageSourceLevel_ = 0.0; + impl.stageTargetLevel_ = point.level; + impl.stageTime_ = point.time; + impl.stageSustained_ = desc.sustain == 0; + impl.stageCurve_ = &point.curve(); + impl.currentFramesUntilRelease_ = absl::nullopt; + impl.isReleased_ = false; + + // + impl.currentStageNumber_ = 0; + impl.currentLevel_ = 0.0; + impl.currentTime_ = 0.0; +} + +void FlexEnvelope::release(unsigned releaseDelay) +{ + Impl& impl = *impl_; + impl.currentFramesUntilRelease_ = releaseDelay; +} + +void FlexEnvelope::process(absl::Span out) +{ + Impl& impl = *impl_; + impl.process(out); +} + +void FlexEnvelope::Impl::process(absl::Span out) +{ + const FlexEGDescription& desc = *desc_; + size_t numFrames = out.size(); + const float samplePeriod = samplePeriod_; + + // Skip the initial delay, for frame-accurate trigger + size_t skipFrames = std::min(numFrames, delayFramesLeft_); + if (skipFrames > 0) { + delayFramesLeft_ -= skipFrames; + fill(absl::MakeSpan(out.data(), skipFrames), 0.0f); + out.remove_prefix(skipFrames); + numFrames -= skipFrames; + } + + // Envelope finished? + if (currentStageNumber_ >= desc.points.size()) { + fill(out, 0.0f); + return; + } + + size_t frameIndex = 0; + absl::optional framesUntilRelease = currentFramesUntilRelease_; + + while (frameIndex < numFrames) { + // Check for release + if (framesUntilRelease && *framesUntilRelease == 0) { + isReleased_ = true; + framesUntilRelease = absl::nullopt; + } + + // Perform stage transitions + const bool isReleased = isReleased_; + while ((!stageSustained_ && currentTime_ >= stageTime_) || + (stageSustained_ && isReleased)) { + if (!advanceToNextStage()) { + fill(out, 0.0f); + return; + } + } + + // Process without going past the release point, if there is one + size_t maxFrameIndex = numFrames; + if (framesUntilRelease) + maxFrameIndex = std::min(maxFrameIndex, frameIndex + *framesUntilRelease); + + // Process the current stage + float time = currentTime_; + float level = currentLevel_; + const float stageEndTime = stageTime_; + const float sourceLevel = stageSourceLevel_; + const float targetLevel = stageTargetLevel_; + const bool sustained = stageSustained_; + const Curve& curve = *stageCurve_; + size_t framesDone = 0; + while ((time < stageEndTime || sustained) && frameIndex < maxFrameIndex) { + float x = time * (1.0f / stageEndTime); + float c = curve.evalNormalized(x); + level = sourceLevel + c * (targetLevel - sourceLevel); + out[frameIndex++] = level; + time += samplePeriod; + ++framesDone; + } + currentLevel_ = level; + + // Update the counter to release + if (framesUntilRelease) + *framesUntilRelease -= framesDone; + + currentTime_ = time; + } + + currentFramesUntilRelease_ = framesUntilRelease; +} + +bool FlexEnvelope::Impl::advanceToNextStage() +{ + const FlexEGDescription& desc = *desc_; + + unsigned nextStageNo = currentStageNumber_ + 1; + currentStageNumber_ = nextStageNo; + + if (currentStageNumber_ >= desc.points.size()) + return false; + + const FlexEGPoint& point = desc.points[currentStageNumber_]; + stageSourceLevel_ = currentLevel_; + stageTargetLevel_ = point.level; + stageTime_ = point.time; + stageSustained_ = int(nextStageNo) == desc.sustain; + stageCurve_ = &point.curve(); + + currentTime_ = 0; + return true; +}; + +} // namespace sfz diff --git a/src/sfizz/FlexEnvelope.h b/src/sfizz/FlexEnvelope.h new file mode 100644 index 00000000..3316e715 --- /dev/null +++ b/src/sfizz/FlexEnvelope.h @@ -0,0 +1,53 @@ +// 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 +#include + +namespace sfz { +struct FlexEGDescription; + +/** + Flex envelope generator (according to ARIA) + */ +class FlexEnvelope { +public: + FlexEnvelope(); + ~FlexEnvelope(); + + /** + Sets the sample rate. + */ + void setSampleRate(double sampleRate); + + /** + Attach some control parameters to this EG. + The control structure is owned by the caller. + */ + void configure(const FlexEGDescription* desc); + + /** + Start processing an EG as a region is triggered. + */ + void start(unsigned triggerDelay); + + /** + Release the EG. + */ + void release(unsigned releaseDelay); + + /** + Process a cycle of the generator. + */ + void process(absl::Span out); + +private: + struct Impl; + std::unique_ptr impl_; +}; + +} // namespace sfz diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index b64af303..a6cea577 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1046,6 +1046,80 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) } break; + // Modulation: Flex EG (targets) + case hash("eg&_amplitude"): + { + const auto egNumber = opcode.parameters.front(); + if (egNumber == 0) + return false; + if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) { + const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); + const ModKey target = ModKey::createNXYZ(ModId::Amplitude, id); + getOrCreateConnection(source, target).sourceDepth = *value; + } + } + break; + case hash("eg&_pan"): + { + const auto egNumber = opcode.parameters.front(); + if (egNumber == 0) + return false; + if (auto value = readOpcode(opcode.value, Default::panCCRange)) { + const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); + const ModKey target = ModKey::createNXYZ(ModId::Pan, id); + getOrCreateConnection(source, target).sourceDepth = *value; + } + } + break; + case hash("eg&_width"): + { + const auto egNumber = opcode.parameters.front(); + if (egNumber == 0) + return false; + if (auto value = readOpcode(opcode.value, Default::widthCCRange)) { + const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); + const ModKey target = ModKey::createNXYZ(ModId::Width, id); + getOrCreateConnection(source, target).sourceDepth = *value; + } + } + break; + case hash("eg&_position"): // sfizz extension + { + const auto egNumber = opcode.parameters.front(); + if (egNumber == 0) + return false; + if (auto value = readOpcode(opcode.value, Default::positionCCRange)) { + const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); + const ModKey target = ModKey::createNXYZ(ModId::Position, id); + getOrCreateConnection(source, target).sourceDepth = *value; + } + } + break; + case hash("eg&_pitch"): + { + const auto egNumber = opcode.parameters.front(); + if (egNumber == 0) + return false; + if (auto value = readOpcode(opcode.value, Default::tuneCCRange)) { + const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); + const ModKey target = ModKey::createNXYZ(ModId::Pitch, id); + getOrCreateConnection(source, target).sourceDepth = *value; + } + } + break; + case hash("eg&_volume"): + { + const auto egNumber = opcode.parameters.front(); + if (egNumber == 0) + return false; + if (auto value = readOpcode(opcode.value, Default::volumeCCRange)) { + const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); + const ModKey target = ModKey::createNXYZ(ModId::Volume, id); + getOrCreateConnection(source, target).sourceDepth = *value; + } + } + break; + // Amplitude Envelope case hash("ampeg_attack"): setValueFromOpcode(opcode, amplitudeEG.attack, Default::egTimeRange); @@ -1155,6 +1229,73 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) break; + // Flex envelopes + case hash("eg&_dynamic"): + { + const auto egNumber = opcode.parameters.front(); + if (egNumber == 0) + return false; + if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs)) + return false; + auto& eg = flexEGs[egNumber - 1]; + setValueFromOpcode(opcode, eg.dynamic, Default::flexEGDynamicRange); + } + break; + case hash("eg&_sustain"): + { + const auto egNumber = opcode.parameters.front(); + if (egNumber == 0) + return false; + if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs)) + return false; + auto& eg = flexEGs[egNumber - 1]; + setValueFromOpcode(opcode, eg.sustain, Default::flexEGSustainRange); + } + break; + case hash("eg&_time&"): + { + const auto egNumber = opcode.parameters.front(); + if (egNumber == 0) + return false; + if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs)) + return false; + auto& eg = flexEGs[egNumber - 1]; + const auto pointNumber = opcode.parameters[1]; + if (!extendIfNecessary(eg.points, pointNumber + 1, Default::numFlexEGPoints)) + return false; + setValueFromOpcode(opcode, eg.points[pointNumber].time, Default::flexEGPointTimeRange); + } + break; + case hash("eg&_level&"): + { + const auto egNumber = opcode.parameters.front(); + if (egNumber == 0) + return false; + if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs)) + return false; + auto& eg = flexEGs[egNumber - 1]; + const auto pointNumber = opcode.parameters[1]; + if (!extendIfNecessary(eg.points, pointNumber + 1, Default::numFlexEGPoints)) + return false; + setValueFromOpcode(opcode, eg.points[pointNumber].level, Default::flexEGPointLevelRange); + } + break; + case hash("eg&_shape&"): + { + const auto egNumber = opcode.parameters.front(); + if (egNumber == 0) + return false; + if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs)) + return false; + auto& eg = flexEGs[egNumber - 1]; + const auto pointNumber = opcode.parameters[1]; + if (!extendIfNecessary(eg.points, pointNumber + 1, Default::numFlexEGPoints)) + return false; + if (auto value = readOpcode(opcode.value, Default::flexEGPointShapeRange)) + eg.points[pointNumber].setShape(*value); + } + break; + case hash("effect&"): { const auto effectNumber = opcode.parameters.back(); diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 4635f78c..4c9dabba 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -10,6 +10,7 @@ #include "LeakDetector.h" #include "Defaults.h" #include "EGDescription.h" +#include "FlexEGDescription.h" #include "EQDescription.h" #include "FilterDescription.h" #include "LFODescription.h" @@ -379,6 +380,9 @@ struct Region { EGDescription pitchEG; EGDescription filterEG; + // Envelopes + std::vector flexEGs; + // LFOs std::vector lfos; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index beae4e64..e69cd215 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -18,6 +18,7 @@ #include "modulations/ModId.h" #include "modulations/sources/Controller.h" #include "modulations/sources/LFO.h" +#include "modulations/sources/FlexEnvelope.h" #include "pugixml.hpp" #include "absl/algorithm/container.h" #include "absl/memory/memory.h" @@ -46,6 +47,7 @@ sfz::Synth::Synth(int numVoices) // modulation sources genController.reset(new ControllerSource(resources)); genLFO.reset(new LFOSource(*this)); + genFlexEnvelope.reset(new FlexEnvelopeSource(*this)); } sfz::Synth::~Synth() @@ -480,6 +482,9 @@ void sfz::Synth::finalizeSfzLoad() size_t maxFilters { 0 }; size_t maxEQs { 0 }; size_t maxLFOs { 0 }; + size_t maxFlexEGs { 0 }; + + FlexEGs::clearUnusedCurves(); while (currentRegionIndex < currentRegionCount) { auto region = regions[currentRegionIndex].get(); @@ -595,6 +600,7 @@ void sfz::Synth::finalizeSfzLoad() maxFilters = max(maxFilters, region->filters.size()); maxEQs = max(maxEQs, region->equalizers.size()); maxLFOs = max(maxLFOs, region->lfos.size()); + maxFlexEGs = max(maxFlexEGs, region->flexEGs.size()); ++currentRegionIndex; } @@ -605,6 +611,7 @@ void sfz::Synth::finalizeSfzLoad() settingsPerVoice.maxFilters = maxFilters; settingsPerVoice.maxEQs = maxEQs; settingsPerVoice.maxLFOs = maxLFOs; + settingsPerVoice.maxFlexEGs = maxFlexEGs; applySettingsPerVoice(); @@ -1473,6 +1480,7 @@ void sfz::Synth::applySettingsPerVoice() voice->setMaxFiltersPerVoice(settingsPerVoice.maxFilters); voice->setMaxEQsPerVoice(settingsPerVoice.maxEQs); voice->setMaxLFOsPerVoice(settingsPerVoice.maxLFOs); + voice->setMaxFlexEGsPerVoice(settingsPerVoice.maxFlexEGs); } } @@ -1491,6 +1499,9 @@ void sfz::Synth::setupModMatrix() case ModId::LFO: gen = genLFO.get(); break; + case ModId::Envelope: + gen = genFlexEnvelope.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 995bb5e6..fd019dc3 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -28,6 +28,7 @@ namespace sfz { class ControllerSource; class LFOSource; +class FlexEnvelopeSource; /** * @brief This class is the core of the sfizz library. In C++ it is the main point @@ -897,12 +898,14 @@ private: // Modulation source generators std::unique_ptr genController; std::unique_ptr genLFO; + std::unique_ptr genFlexEnvelope; // Settings per voice struct SettingsPerVoice { size_t maxFilters { 0 }; size_t maxEQs { 0 }; size_t maxLFOs { 0 }; + size_t maxFlexEGs { 0 }; }; SettingsPerVoice settingsPerVoice; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index b19c6cb5..cb604633 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -13,6 +13,7 @@ #include "Panning.h" #include "SfzHelpers.h" #include "LFO.h" +#include "FlexEnvelope.h" #include "modulations/ModId.h" #include "modulations/ModKey.h" #include "modulations/ModMatrix.h" @@ -798,6 +799,17 @@ void sfz::Voice::setMaxLFOsPerVoice(size_t numLFOs) } } +void sfz::Voice::setMaxFlexEGsPerVoice(size_t numFlexEGs) +{ + flexEGs.resize(numFlexEGs); + + for (size_t i = 0; i < numFlexEGs; ++i) { + auto eg = absl::make_unique(); + eg->setSampleRate(sampleRate); + flexEGs[i] = std::move(eg); + } +} + void sfz::Voice::setupOscillatorUnison() { int m = region->oscillatorMulti; diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 10e0df2a..8833271a 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -25,6 +25,7 @@ namespace sfz { enum InterpolatorModel : int; class LFO; +class FlexEnvelope; /** * @brief The SFZ voice are the polyphony holders. They get activated by the synth * and tasked to play a given region until the end, stopping on note-offs, off-groups @@ -263,6 +264,12 @@ public: * @param index */ LFO* getLFO(size_t index) { return lfos[index].get(); } + /** + * @brief Get the Flex EG designated by the given index + * + * @param index + */ + FlexEnvelope* getFlexEG(size_t index) { return flexEGs[index].get(); } /** * @brief Set the max number of filters per voice * @@ -281,6 +288,12 @@ public: * @param numLFOs */ void setMaxLFOsPerVoice(size_t numLFOs); + /** + * @brief Set the max number of Flex EGs per voice + * + * @param numFlexEGs + */ + void setMaxFlexEGsPerVoice(size_t numFlexEGs); /** * @brief Release the voice after a given delay * @@ -444,6 +457,7 @@ private: std::vector filters; std::vector equalizers; std::vector> lfos; + std::vector> flexEGs; ADSREnvelope egEnvelope; float bendStepFactor { centsFactor(1) }; diff --git a/src/sfizz/modulations/sources/FlexEnvelope.cpp b/src/sfizz/modulations/sources/FlexEnvelope.cpp new file mode 100644 index 00000000..557dd4db --- /dev/null +++ b/src/sfizz/modulations/sources/FlexEnvelope.cpp @@ -0,0 +1,86 @@ +// 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 "FlexEnvelope.h" +#include "../../FlexEnvelope.h" +#include "../../Synth.h" +#include "../../Voice.h" +#include "../../SIMDHelpers.h" +#include "../../Config.h" +#include "../../Debug.h" + +namespace sfz { + +FlexEnvelopeSource::FlexEnvelopeSource(Synth &synth) + : synth_(&synth) +{ +} + +void FlexEnvelopeSource::init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) +{ + Synth& synth = *synth_; + unsigned egIndex = sourceKey.parameters().N; + + Voice* voice = synth.getVoiceById(voiceId); + if (!voice) { + ASSERTFALSE; + return; + } + + const Region* region = voice->getRegion(); + if (egIndex >= region->flexEGs.size()) { + ASSERTFALSE; + return; + } + + FlexEnvelope* eg = voice->getFlexEG(egIndex); + eg->configure(®ion->flexEGs[egIndex]); + eg->start(delay); +} + +void FlexEnvelopeSource::release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) +{ + Synth& synth = *synth_; + unsigned egIndex = sourceKey.parameters().N; + + Voice* voice = synth.getVoiceById(voiceId); + if (!voice) { + ASSERTFALSE; + return; + } + + const Region* region = voice->getRegion(); + if (egIndex >= region->flexEGs.size()) { + ASSERTFALSE; + return; + } + + FlexEnvelope* eg = voice->getFlexEG(egIndex); + eg->release(delay); +} + +void FlexEnvelopeSource::generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) +{ + Synth& synth = *synth_; + unsigned egIndex = sourceKey.parameters().N; + + Voice* voice = synth.getVoiceById(voiceId); + if (!voice) { + ASSERTFALSE; + return; + } + + const Region* region = voice->getRegion(); + if (egIndex >= region->flexEGs.size()) { + ASSERTFALSE; + return; + } + + FlexEnvelope* eg = voice->getFlexEG(egIndex); + eg->process(buffer); +} + +} // namespace sfz diff --git a/src/sfizz/modulations/sources/FlexEnvelope.h b/src/sfizz/modulations/sources/FlexEnvelope.h new file mode 100644 index 00000000..c4e50fc4 --- /dev/null +++ b/src/sfizz/modulations/sources/FlexEnvelope.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 FlexEnvelopeSource : public ModGenerator { +public: + explicit FlexEnvelopeSource(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