Add the Flex EG
This commit is contained in:
parent
bf0786ca63
commit
b692377f7b
17 changed files with 717 additions and 3 deletions
3
dpf.mk
3
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 \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<float>(curve._points));
|
||||
return curve;
|
||||
}
|
||||
|
||||
const Curve& Curve::getDefault()
|
||||
{
|
||||
return defaultCurve;
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -244,6 +244,20 @@ namespace Default
|
|||
constexpr Range<float> egOnCCTimeRange { -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 ********
|
||||
constexpr int sampleQuality { 1 };
|
||||
constexpr int sampleQualityInFreewheelingMode { 10 }; // for future use, possibly excessive
|
||||
|
|
|
|||
87
src/sfizz/FlexEGDescription.cpp
Normal file
87
src/sfizz/FlexEGDescription.cpp
Normal 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
|
||||
39
src/sfizz/FlexEGDescription.h
Normal file
39
src/sfizz/FlexEGDescription.h
Normal 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
206
src/sfizz/FlexEnvelope.cpp
Normal 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
53
src/sfizz/FlexEnvelope.h
Normal 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
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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<FlexEGDescription> flexEGs;
|
||||
|
||||
// LFOs
|
||||
std::vector<LFODescription> lfos;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<ControllerSource> genController;
|
||||
std::unique_ptr<LFOSource> genLFO;
|
||||
std::unique_ptr<FlexEnvelopeSource> genFlexEnvelope;
|
||||
|
||||
// Settings per voice
|
||||
struct SettingsPerVoice {
|
||||
size_t maxFilters { 0 };
|
||||
size_t maxEQs { 0 };
|
||||
size_t maxLFOs { 0 };
|
||||
size_t maxFlexEGs { 0 };
|
||||
};
|
||||
SettingsPerVoice settingsPerVoice;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<FlexEnvelope>();
|
||||
eg->setSampleRate(sampleRate);
|
||||
flexEGs[i] = std::move(eg);
|
||||
}
|
||||
}
|
||||
|
||||
void sfz::Voice::setupOscillatorUnison()
|
||||
{
|
||||
int m = region->oscillatorMulti;
|
||||
|
|
|
|||
|
|
@ -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<FilterHolderPtr> filters;
|
||||
std::vector<EQHolderPtr> equalizers;
|
||||
std::vector<std::unique_ptr<LFO>> lfos;
|
||||
std::vector<std::unique_ptr<FlexEnvelope>> flexEGs;
|
||||
|
||||
ADSREnvelope<float> egEnvelope;
|
||||
float bendStepFactor { centsFactor(1) };
|
||||
|
|
|
|||
86
src/sfizz/modulations/sources/FlexEnvelope.cpp
Normal file
86
src/sfizz/modulations/sources/FlexEnvelope.cpp
Normal 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(®ion->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
|
||||
24
src/sfizz/modulations/sources/FlexEnvelope.h
Normal file
24
src/sfizz/modulations/sources/FlexEnvelope.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||
// license. You should have receive a LICENSE.md file along with the code.
|
||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#pragma once
|
||||
#include "../ModGenerator.h"
|
||||
|
||||
namespace sfz {
|
||||
class Synth;
|
||||
|
||||
class 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
|
||||
Loading…
Add table
Reference in a new issue