Add the Flex EG

This commit is contained in:
Jean Pierre Cimalando 2020-08-20 23:37:23 +02:00
parent bf0786ca63
commit b692377f7b
17 changed files with 717 additions and 3 deletions

3
dpf.mk
View file

@ -66,6 +66,8 @@ SFIZZ_SOURCES = \
src/sfizz/modulations/ModKeyHash.cpp \ src/sfizz/modulations/ModKeyHash.cpp \
src/sfizz/modulations/ModMatrix.cpp \ src/sfizz/modulations/ModMatrix.cpp \
src/sfizz/modulations/sources/Controller.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/modulations/sources/LFO.cpp \
src/sfizz/effects/Compressor.cpp \ src/sfizz/effects/Compressor.cpp \
src/sfizz/effects/Disto.cpp \ src/sfizz/effects/Disto.cpp \
@ -91,6 +93,7 @@ SFIZZ_SOURCES = \
src/sfizz/FileMetadata.cpp \ src/sfizz/FileMetadata.cpp \
src/sfizz/FilePool.cpp \ src/sfizz/FilePool.cpp \
src/sfizz/FilterPool.cpp \ src/sfizz/FilterPool.cpp \
src/sfizz/FlexEnvelope.cpp \
src/sfizz/FloatEnvelopes.cpp \ src/sfizz/FloatEnvelopes.cpp \
src/sfizz/Logger.cpp \ src/sfizz/Logger.cpp \
src/sfizz/LFO.cpp \ src/sfizz/LFO.cpp \

View file

@ -35,6 +35,7 @@ set (SFIZZ_HEADERS
sfizz/modulations/ModMatrix.h sfizz/modulations/ModMatrix.h
sfizz/modulations/ModGenerator.h sfizz/modulations/ModGenerator.h
sfizz/modulations/sources/Controller.h sfizz/modulations/sources/Controller.h
sfizz/modulations/sources/FlexEnvelope.h
sfizz/modulations/sources/LFO.h sfizz/modulations/sources/LFO.h
sfizz/effects/impl/ResonantArray.h sfizz/effects/impl/ResonantArray.h
sfizz/effects/impl/ResonantArrayAVX.h sfizz/effects/impl/ResonantArrayAVX.h
@ -67,6 +68,8 @@ set (SFIZZ_HEADERS
sfizz/FilePool.h sfizz/FilePool.h
sfizz/FilterDescription.h sfizz/FilterDescription.h
sfizz/FilterPool.h sfizz/FilterPool.h
sfizz/FlexEGDescription.h
sfizz/FlexEnvelope.h
sfizz/HistoricalBuffer.h sfizz/HistoricalBuffer.h
sfizz/Interpolators.h sfizz/Interpolators.h
sfizz/Interpolators.hpp sfizz/Interpolators.hpp
@ -139,11 +142,14 @@ set (SFIZZ_SOURCES
sfizz/LFO.cpp sfizz/LFO.cpp
sfizz/LFODescription.cpp sfizz/LFODescription.cpp
sfizz/PowerFollower.cpp sfizz/PowerFollower.cpp
sfizz/FlexEGDescription.cpp
sfizz/FlexEnvelope.cpp
sfizz/modulations/ModId.cpp sfizz/modulations/ModId.cpp
sfizz/modulations/ModKey.cpp sfizz/modulations/ModKey.cpp
sfizz/modulations/ModKeyHash.cpp sfizz/modulations/ModKeyHash.cpp
sfizz/modulations/ModMatrix.cpp sfizz/modulations/ModMatrix.cpp
sfizz/modulations/sources/Controller.cpp sfizz/modulations/sources/Controller.cpp
sfizz/modulations/sources/FlexEnvelope.cpp
sfizz/modulations/sources/LFO.cpp sfizz/modulations/sources/LFO.cpp
sfizz/utility/SpinMutex.cpp sfizz/utility/SpinMutex.cpp
sfizz/effects/Nothing.cpp sfizz/effects/Nothing.cpp

View file

@ -147,6 +147,13 @@ Curve Curve::buildBipolar(float v1, float v2)
return curve; return curve;
} }
Curve Curve::buildFromPoints(const float points[NumValues])
{
Curve curve;
copy(absl::MakeConstSpan(points, NumValues), absl::Span<float>(curve._points));
return curve;
}
const Curve& Curve::getDefault() const Curve& Curve::getDefault()
{ {
return defaultCurve; return defaultCurve;

View file

@ -21,6 +21,8 @@ struct Opcode;
*/ */
class Curve { class Curve {
public: public:
enum { NumValues = 128 };
/** /**
* @brief Compute the curve for integral x in domain [0:127] * @brief Compute the curve for integral x in domain [0:127]
*/ */
@ -93,14 +95,16 @@ public:
*/ */
static Curve buildBipolar(float v1, float v2); 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 * @brief Get a linear curve from 0 to 1
*/ */
static const Curve& getDefault(); static const Curve& getDefault();
private:
enum { NumValues = 128 };
private: private:
void fill(Interpolator itp, const bool fillStatus[NumValues]); void fill(Interpolator itp, const bool fillStatus[NumValues]);
void lerpFill(const bool fillStatus[NumValues]); void lerpFill(const bool fillStatus[NumValues]);

View file

@ -244,6 +244,20 @@ namespace Default
constexpr Range<float> egOnCCTimeRange { -100.0, 100.0 }; constexpr Range<float> egOnCCTimeRange { -100.0, 100.0 };
constexpr Range<float> egOnCCPercentRange { -100.0, 100.0 }; constexpr Range<float> egOnCCPercentRange { -100.0, 100.0 };
// 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<int> flexEGDynamicRange { 0, 1 };
constexpr Range<int> flexEGSustainRange { 0, 100 };
constexpr Range<float> flexEGPointTimeRange { 0.0f, 100.0f };
constexpr Range<float> flexEGPointLevelRange { -1.0f, 1.0f };
constexpr Range<float> flexEGPointShapeRange { -100.0f, 100.0f };
// ***** SFZ v2 ******** // ***** SFZ v2 ********
constexpr int sampleQuality { 1 }; constexpr int sampleQuality { 1 };
constexpr int sampleQualityInFreewheelingMode { 10 }; // for future use, possibly excessive constexpr int sampleQualityInFreewheelingMode { 10 }; // for future use, possibly excessive

View file

@ -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 <absl/container/flat_hash_map.h>
#include <cmath>
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<float, std::weak_ptr<Curve>> FlexEGShapes;
static FlexEGShapes& getShapeMap()
{
static FlexEGShapes shapes;
return shapes;
}
std::shared_ptr<Curve> FlexEGs::getShapeCurve(float shape)
{
static FlexEGShapes& map = getShapeMap();
std::weak_ptr<Curve>& slot = map[shape];
std::shared_ptr<Curve> 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

View file

@ -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 <vector>
#include <memory>
namespace sfz {
class Curve;
namespace FlexEGs {
std::shared_ptr<Curve> 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<Curve> 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<FlexEGPoint> points;
};
} // namespace sfz

206
src/sfizz/FlexEnvelope.cpp Normal file
View file

@ -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 <absl/types/optional.h>
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<size_t> currentFramesUntilRelease_ { absl::nullopt };
bool isReleased_ { false };
//
void process(absl::Span<float> 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<float> out)
{
Impl& impl = *impl_;
impl.process(out);
}
void FlexEnvelope::Impl::process(absl::Span<float> 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<size_t> 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

53
src/sfizz/FlexEnvelope.h Normal file
View file

@ -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 <absl/types/span.h>
#include <memory>
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<float> out);
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace sfz

View file

@ -1046,6 +1046,80 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
} }
break; 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 // Amplitude Envelope
case hash("ampeg_attack"): case hash("ampeg_attack"):
setValueFromOpcode(opcode, amplitudeEG.attack, Default::egTimeRange); setValueFromOpcode(opcode, amplitudeEG.attack, Default::egTimeRange);
@ -1155,6 +1229,73 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
break; 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&"): case hash("effect&"):
{ {
const auto effectNumber = opcode.parameters.back(); const auto effectNumber = opcode.parameters.back();

View file

@ -10,6 +10,7 @@
#include "LeakDetector.h" #include "LeakDetector.h"
#include "Defaults.h" #include "Defaults.h"
#include "EGDescription.h" #include "EGDescription.h"
#include "FlexEGDescription.h"
#include "EQDescription.h" #include "EQDescription.h"
#include "FilterDescription.h" #include "FilterDescription.h"
#include "LFODescription.h" #include "LFODescription.h"
@ -379,6 +380,9 @@ struct Region {
EGDescription pitchEG; EGDescription pitchEG;
EGDescription filterEG; EGDescription filterEG;
// Envelopes
std::vector<FlexEGDescription> flexEGs;
// LFOs // LFOs
std::vector<LFODescription> lfos; std::vector<LFODescription> lfos;

View file

@ -18,6 +18,7 @@
#include "modulations/ModId.h" #include "modulations/ModId.h"
#include "modulations/sources/Controller.h" #include "modulations/sources/Controller.h"
#include "modulations/sources/LFO.h" #include "modulations/sources/LFO.h"
#include "modulations/sources/FlexEnvelope.h"
#include "pugixml.hpp" #include "pugixml.hpp"
#include "absl/algorithm/container.h" #include "absl/algorithm/container.h"
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
@ -46,6 +47,7 @@ sfz::Synth::Synth(int numVoices)
// modulation sources // modulation sources
genController.reset(new ControllerSource(resources)); genController.reset(new ControllerSource(resources));
genLFO.reset(new LFOSource(*this)); genLFO.reset(new LFOSource(*this));
genFlexEnvelope.reset(new FlexEnvelopeSource(*this));
} }
sfz::Synth::~Synth() sfz::Synth::~Synth()
@ -480,6 +482,9 @@ void sfz::Synth::finalizeSfzLoad()
size_t maxFilters { 0 }; size_t maxFilters { 0 };
size_t maxEQs { 0 }; size_t maxEQs { 0 };
size_t maxLFOs { 0 }; size_t maxLFOs { 0 };
size_t maxFlexEGs { 0 };
FlexEGs::clearUnusedCurves();
while (currentRegionIndex < currentRegionCount) { while (currentRegionIndex < currentRegionCount) {
auto region = regions[currentRegionIndex].get(); auto region = regions[currentRegionIndex].get();
@ -595,6 +600,7 @@ void sfz::Synth::finalizeSfzLoad()
maxFilters = max(maxFilters, region->filters.size()); maxFilters = max(maxFilters, region->filters.size());
maxEQs = max(maxEQs, region->equalizers.size()); maxEQs = max(maxEQs, region->equalizers.size());
maxLFOs = max(maxLFOs, region->lfos.size()); maxLFOs = max(maxLFOs, region->lfos.size());
maxFlexEGs = max(maxFlexEGs, region->flexEGs.size());
++currentRegionIndex; ++currentRegionIndex;
} }
@ -605,6 +611,7 @@ void sfz::Synth::finalizeSfzLoad()
settingsPerVoice.maxFilters = maxFilters; settingsPerVoice.maxFilters = maxFilters;
settingsPerVoice.maxEQs = maxEQs; settingsPerVoice.maxEQs = maxEQs;
settingsPerVoice.maxLFOs = maxLFOs; settingsPerVoice.maxLFOs = maxLFOs;
settingsPerVoice.maxFlexEGs = maxFlexEGs;
applySettingsPerVoice(); applySettingsPerVoice();
@ -1473,6 +1480,7 @@ void sfz::Synth::applySettingsPerVoice()
voice->setMaxFiltersPerVoice(settingsPerVoice.maxFilters); voice->setMaxFiltersPerVoice(settingsPerVoice.maxFilters);
voice->setMaxEQsPerVoice(settingsPerVoice.maxEQs); voice->setMaxEQsPerVoice(settingsPerVoice.maxEQs);
voice->setMaxLFOsPerVoice(settingsPerVoice.maxLFOs); voice->setMaxLFOsPerVoice(settingsPerVoice.maxLFOs);
voice->setMaxFlexEGsPerVoice(settingsPerVoice.maxFlexEGs);
} }
} }
@ -1491,6 +1499,9 @@ void sfz::Synth::setupModMatrix()
case ModId::LFO: case ModId::LFO:
gen = genLFO.get(); gen = genLFO.get();
break; break;
case ModId::Envelope:
gen = genFlexEnvelope.get();
break;
default: default:
DBG("[sfizz] Have unknown type of source generator"); DBG("[sfizz] Have unknown type of source generator");
break; break;

View file

@ -28,6 +28,7 @@
namespace sfz { namespace sfz {
class ControllerSource; class ControllerSource;
class LFOSource; class LFOSource;
class FlexEnvelopeSource;
/** /**
* @brief This class is the core of the sfizz library. In C++ it is the main point * @brief This class is the core of the sfizz library. In C++ it is the main point
@ -897,12 +898,14 @@ private:
// Modulation source generators // Modulation source generators
std::unique_ptr<ControllerSource> genController; std::unique_ptr<ControllerSource> genController;
std::unique_ptr<LFOSource> genLFO; std::unique_ptr<LFOSource> genLFO;
std::unique_ptr<FlexEnvelopeSource> genFlexEnvelope;
// Settings per voice // Settings per voice
struct SettingsPerVoice { struct SettingsPerVoice {
size_t maxFilters { 0 }; size_t maxFilters { 0 };
size_t maxEQs { 0 }; size_t maxEQs { 0 };
size_t maxLFOs { 0 }; size_t maxLFOs { 0 };
size_t maxFlexEGs { 0 };
}; };
SettingsPerVoice settingsPerVoice; SettingsPerVoice settingsPerVoice;

View file

@ -13,6 +13,7 @@
#include "Panning.h" #include "Panning.h"
#include "SfzHelpers.h" #include "SfzHelpers.h"
#include "LFO.h" #include "LFO.h"
#include "FlexEnvelope.h"
#include "modulations/ModId.h" #include "modulations/ModId.h"
#include "modulations/ModKey.h" #include "modulations/ModKey.h"
#include "modulations/ModMatrix.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<FlexEnvelope>();
eg->setSampleRate(sampleRate);
flexEGs[i] = std::move(eg);
}
}
void sfz::Voice::setupOscillatorUnison() void sfz::Voice::setupOscillatorUnison()
{ {
int m = region->oscillatorMulti; int m = region->oscillatorMulti;

View file

@ -25,6 +25,7 @@
namespace sfz { namespace sfz {
enum InterpolatorModel : int; enum InterpolatorModel : int;
class LFO; class LFO;
class FlexEnvelope;
/** /**
* @brief The SFZ voice are the polyphony holders. They get activated by the synth * @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 * and tasked to play a given region until the end, stopping on note-offs, off-groups
@ -263,6 +264,12 @@ public:
* @param index * @param index
*/ */
LFO* getLFO(size_t index) { return lfos[index].get(); } 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 * @brief Set the max number of filters per voice
* *
@ -281,6 +288,12 @@ public:
* @param numLFOs * @param numLFOs
*/ */
void setMaxLFOsPerVoice(size_t 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 * @brief Release the voice after a given delay
* *
@ -444,6 +457,7 @@ private:
std::vector<FilterHolderPtr> filters; std::vector<FilterHolderPtr> filters;
std::vector<EQHolderPtr> equalizers; std::vector<EQHolderPtr> equalizers;
std::vector<std::unique_ptr<LFO>> lfos; std::vector<std::unique_ptr<LFO>> lfos;
std::vector<std::unique_ptr<FlexEnvelope>> flexEGs;
ADSREnvelope<float> egEnvelope; ADSREnvelope<float> egEnvelope;
float bendStepFactor { centsFactor(1) }; float bendStepFactor { centsFactor(1) };

View file

@ -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<Voice> 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(&region->flexEGs[egIndex]);
eg->start(delay);
}
void FlexEnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> 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<Voice> voiceId, absl::Span<float> 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

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 FlexEnvelopeSource : public ModGenerator {
public:
explicit FlexEnvelopeSource(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