Add SFZv2 LFO and a small set of modulation targets

This commit is contained in:
Jean Pierre Cimalando 2020-07-28 09:39:05 +02:00
parent 094107206a
commit eab53ba05a
16 changed files with 899 additions and 1 deletions

4
dpf.mk
View file

@ -1,3 +1,4 @@
#
# A build file to help using sfizz with the DISTRHO Plugin Framework (DPF)
# ------------------------------------------------------------------------
@ -65,6 +66,7 @@ SFIZZ_SOURCES = \
src/sfizz/modulations/ModKeyHash.cpp \
src/sfizz/modulations/ModMatrix.cpp \
src/sfizz/modulations/sources/Controller.cpp \
src/sfizz/modulations/sources/LFO.cpp \
src/sfizz/effects/Compressor.cpp \
src/sfizz/effects/Disto.cpp \
src/sfizz/effects/Eq.cpp \
@ -91,6 +93,8 @@ SFIZZ_SOURCES = \
src/sfizz/FilterPool.cpp \
src/sfizz/FloatEnvelopes.cpp \
src/sfizz/Logger.cpp \
src/sfizz/LFO.cpp \
src/sfizz/LFODescription.cpp \
src/sfizz/MidiState.cpp \
src/sfizz/OpcodeCleanup.cpp \
src/sfizz/Opcode.cpp \

View file

@ -35,6 +35,7 @@ set (SFIZZ_HEADERS
sfizz/modulations/ModMatrix.h
sfizz/modulations/ModGenerator.h
sfizz/modulations/sources/Controller.h
sfizz/modulations/sources/LFO.h
sfizz/effects/impl/ResonantArray.h
sfizz/effects/impl/ResonantArrayAVX.h
sfizz/effects/impl/ResonantArraySSE.h
@ -70,6 +71,8 @@ set (SFIZZ_HEADERS
sfizz/Interpolators.h
sfizz/Interpolators.hpp
sfizz/Logger.h
sfizz/LFO.h
sfizz/LFODescription.h
sfizz/MathHelpers.h
sfizz/MidiState.h
sfizz/ModifierHelpers.h
@ -133,11 +136,14 @@ set (SFIZZ_SOURCES
sfizz/RTSemaphore.cpp
sfizz/Panning.cpp
sfizz/Effects.cpp
sfizz/LFO.cpp
sfizz/LFODescription.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/LFO.cpp
sfizz/effects/Nothing.cpp
sfizz/effects/Filter.cpp
sfizz/effects/Eq.cpp

View file

@ -64,6 +64,8 @@ namespace config {
constexpr unsigned int defaultAlignment { 16 };
constexpr int filtersInPool { maxVoices * 2 };
constexpr int excessFileFrames { 8 };
constexpr int maxLFOSubs { 8 };
constexpr int maxLFOSteps { 128 };
/**
* @brief The threshold for age stealing.
* In percentage of the voice's max age.

View file

@ -207,6 +207,22 @@ namespace Default
constexpr int bendStep { 1 };
constexpr uint8_t bendSmooth { 0 };
// Modulation: LFO
constexpr int numLFOs { 4 };
constexpr int numLFOSubs { 2 };
constexpr int numLFOSteps { 8 };
constexpr Range<float> lfoFreqRange { 0.0, 100.0 };
constexpr Range<float> lfoPhaseRange { 0.0, 360.0 };
constexpr Range<float> lfoDelayRange { 0.0, 30.0 };
constexpr Range<float> lfoFadeRange { 0.0, 30.0 };
constexpr Range<unsigned> lfoCountRange { 0, 1000 };
constexpr Range<unsigned> lfoStepsRange { 0, static_cast<unsigned>(config::maxLFOSteps) };
constexpr Range<float> lfoStepXRange { -100.0, 100.0 };
constexpr Range<int> lfoWaveRange { 0, 15 };
constexpr Range<float> lfoOffsetRange { -1.0, 1.0 };
constexpr Range<float> lfoRatioRange { 0.0, 100.0 };
constexpr Range<float> lfoScaleRange { 0.0, 1.0 };
// Envelope generators
constexpr float attack { 0 };
constexpr float decay { 0 };

295
src/sfizz/LFO.cpp Normal file
View file

@ -0,0 +1,295 @@
// 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 "LFO.h"
#include "LFODescription.h"
#include "MathHelpers.h"
#include "SIMDHelpers.h"
#include "Config.h"
#include <array>
#include <algorithm>
#include <cmath>
namespace sfz {
struct LFO::Impl {
float sampleRate_ = 0;
// control
const LFODescription* desc_ = nullptr;
// state
size_t delayFramesLeft_ = 0;
float fadeInPole_ = 0;
float fadeInMemory_ = 0;
std::array<float, config::maxLFOSubs> subPhases_ {{}};
std::array<float, config::maxLFOSubs> sampleHoldMem_ {{}};
};
LFO::LFO()
: impl_(new Impl)
{
impl_->sampleRate_ = config::defaultSampleRate;
impl_->desc_ = &LFODescription::getDefault();
}
LFO::~LFO()
{
}
void LFO::setSampleRate(double sampleRate)
{
impl_->sampleRate_ = sampleRate;
}
void LFO::configure(const LFODescription* desc)
{
impl_->desc_ = desc ? desc : &LFODescription::getDefault();
}
void LFO::start()
{
Impl& impl = *impl_;
const LFODescription& desc = *impl.desc_;
const float sampleRate = impl.sampleRate_;
impl.subPhases_.fill(desc.phase0);
impl.sampleHoldMem_.fill(0.0f);
const float delay = desc.delay;
impl.delayFramesLeft_ = (delay > 0) ? static_cast<size_t>(std::ceil(sampleRate * delay)) : 0u;
const float fade = desc.fade;
impl.fadeInPole_ = (fade > 0) ? std::exp(-1.0 / (fade * sampleRate)) : 0.0f;
impl.fadeInMemory_ = 0;
}
template <>
inline float LFO::eval<LFOWave::Triangle>(float phase)
{
float y = -4 * phase + 2;
y = (phase < 0.25f) ? (4 * phase) : y;
y = (phase > 0.75f) ? (4 * phase - 4) : y;
return y;
}
template <>
inline float LFO::eval<LFOWave::Sine>(float phase)
{
float x = phase + phase - 1;
return 4 * x * (1 - std::fabs(x));
}
template <>
inline float LFO::eval<LFOWave::Pulse75>(float phase)
{
return (phase < 0.75f) ? +1.0f : -1.0f;
}
template <>
inline float LFO::eval<LFOWave::Square>(float phase)
{
return (phase < 0.5f) ? +1.0f : -1.0f;
}
template <>
inline float LFO::eval<LFOWave::Pulse25>(float phase)
{
return (phase < 0.25f) ? +1.0f : -1.0f;
}
template <>
inline float LFO::eval<LFOWave::Pulse12_5>(float phase)
{
return (phase < 0.125f) ? +1.0f : -1.0f;
}
template <>
inline float LFO::eval<LFOWave::Ramp>(float phase)
{
return 2 * phase - 1;
}
template <>
inline float LFO::eval<LFOWave::Saw>(float phase)
{
return 1 - 2 * phase;
}
template <LFOWave W>
void LFO::processWave(unsigned nth, absl::Span<float> out)
{
Impl& impl = *impl_;
const LFODescription& desc = *impl.desc_;
const LFODescription::Sub& sub = desc.sub[nth];
const size_t numFrames = out.size();
float samplePeriod = 1.0f / impl.sampleRate_;
float phase = impl.subPhases_[nth];
float baseFreq = desc.freq;
float offset = sub.offset;
float ratio = sub.ratio;
float scale = sub.scale;
for (size_t i = 0; i < numFrames; ++i) {
out[i] += offset + scale * eval<W>(phase);
// TODO(jpc) lfoN_count: number of repetitions
float incrPhase = ratio * samplePeriod * baseFreq;
phase += incrPhase;
int numWraps = (int)phase;
phase -= numWraps;
}
impl.subPhases_[nth] = phase;
}
template <LFOWave W>
void LFO::processSH(unsigned nth, absl::Span<float> out)
{
Impl& impl = *impl_;
const LFODescription& desc = *impl.desc_;
const LFODescription::Sub& sub = desc.sub[nth];
const size_t numFrames = out.size();
float samplePeriod = 1.0f / impl.sampleRate_;
float phase = impl.subPhases_[nth];
float baseFreq = desc.freq;
float offset = sub.offset;
float ratio = sub.ratio;
float scale = sub.scale;
float sampleHoldValue = impl.sampleHoldMem_[nth];
for (size_t i = 0; i < numFrames; ++i) {
out[i] += offset + scale * sampleHoldValue;
// TODO(jpc) lfoN_count: number of repetitions
float incrPhase = ratio * samplePeriod * baseFreq;
// value updates twice every period
bool updateValue = (int)(phase * 2.0) != (int)((phase + incrPhase) * 2.0);
phase += incrPhase;
int numWraps = (int)phase;
phase -= numWraps;
if (updateValue) {
std::uniform_real_distribution<float> dist(-1.0f, +1.0f);
sampleHoldValue = dist(Random::randomGenerator);
}
}
impl.subPhases_[nth] = phase;
impl.sampleHoldMem_[nth] = sampleHoldValue;
}
void LFO::processSteps(absl::Span<float> out)
{
unsigned nth = 0;
Impl& impl = *impl_;
const LFODescription& desc = *impl.desc_;
const LFODescription::Sub& sub = desc.sub[nth];
const size_t numFrames = out.size();
const LFODescription::StepSequence& seq = *desc.seq;
const float* steps = seq.steps.data();
unsigned numSteps = seq.steps.size();
if (numSteps <= 0)
return;
float samplePeriod = 1.0f / impl.sampleRate_;
float phase = impl.subPhases_[nth];
float baseFreq = desc.freq;
float offset = sub.offset;
float ratio = sub.ratio;
float scale = sub.scale;
for (size_t i = 0; i < numFrames; ++i) {
float step = steps[static_cast<int>(phase * numSteps)];
out[i] += offset + scale * step;
// TODO(jpc) lfoN_count: number of repetitions
float incrPhase = ratio * samplePeriod * baseFreq;
phase += incrPhase;
int numWraps = (int)phase;
phase -= numWraps;
}
impl.subPhases_[nth] = phase;
}
void LFO::process(absl::Span<float> out)
{
Impl& impl = *impl_;
const LFODescription& desc = *impl.desc_;
size_t numFrames = out.size();
fill(out, 0.0f);
size_t skipFrames = std::min(numFrames, impl.delayFramesLeft_);
if (skipFrames > 0) {
impl.delayFramesLeft_ -= skipFrames;
out.remove_prefix(skipFrames);
numFrames -= skipFrames;
}
unsigned subno = 0;
const unsigned countSubs = desc.sub.size();
if (countSubs < 1)
return;
if (desc.seq) {
processSteps(out);
++subno;
}
for (; subno < countSubs; ++subno) {
switch (desc.sub[subno].wave) {
case LFOWave::Triangle:
processWave<LFOWave::Triangle>(subno, out);
break;
case LFOWave::Sine:
processWave<LFOWave::Sine>(subno, out);
break;
case LFOWave::Pulse75:
processWave<LFOWave::Pulse75>(subno, out);
break;
case LFOWave::Square:
processWave<LFOWave::Square>(subno, out);
break;
case LFOWave::Pulse25:
processWave<LFOWave::Pulse25>(subno, out);
break;
case LFOWave::Pulse12_5:
processWave<LFOWave::Pulse12_5>(subno, out);
break;
case LFOWave::Ramp:
processWave<LFOWave::Ramp>(subno, out);
break;
case LFOWave::Saw:
processWave<LFOWave::Saw>(subno, out);
break;
case LFOWave::RandomSH:
processSH<LFOWave::RandomSH>(subno, out);
break;
}
}
float fadeIn = impl.fadeInMemory_;
const float fadeInPole = impl.fadeInPole_;
for (size_t i = 0; i < numFrames; ++i) {
out[i] *= fadeIn;
fadeIn = fadeInPole * fadeIn + (1 - fadeInPole);
}
impl.fadeInMemory_ = fadeIn;
}
} // namespace sfz

112
src/sfizz/LFO.h Normal file
View file

@ -0,0 +1,112 @@
// 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 {
enum class LFOWave : int;
struct LFODescription;
/*
* General
lfoN_freq: Base frequency - Allow modulations at A-rate
lfoN_phase: Initial phase
lfoN_delay: Delay
lfoN_fade: Time to fade-in
lfoN_count: Number of repetitions - not implemented in ARIA
lfoN_steps: Length of the step sequence - 1 to 128
lfoN_steps_onccX: ??? TODO(jpc) seen in Rapture
lfoN_stepX: Value of the Xth step of the sequence - -100% to +100%
lfoN_stepX_onccY: ??? TODO(jpc) check this. override/modulate step in sequence?
note: LFO evaluates between -1 to +1
note: make the step sequencer override the main wave when present.
subwaves are ARIA, step sequencer is Cakewalk, so do our own thing
which makes the most sense.
* Subwaveforms
X: - #1/omitted: the main wave
- #2-#8: a subwave
note: if there are gaps in subwaveforms, these subwaveforms which are gaps
will be initialized and processed.
example: lfo1_ratio4=1.0 // instanciate implicitly the subs #2 and #3
lfoN_wave[X]: Wave
lfoN_offset[X]: DC offset - Add to LFO output; not affected by scale.
lfoN_ratio[X]: Sub ratio - Frequency = (Ratio * Base Frequency)
lfoN_scale[X]: Sub scale - Amplitude of sub
*/
class LFO {
public:
LFO();
~LFO();
/**
Sets the sample rate.
*/
void setSampleRate(double sampleRate);
/**
Attach some control parameters to this LFO.
The control structure is owned by the caller.
*/
void configure(const LFODescription* desc);
/**
Start processing a LFO as a region is triggered.
Prepares the delay, phases, fade-in, etc..
*/
void start();
/**
Process a cycle of the oscillator.
TODO(jpc) frequency modulations
*/
void process(absl::Span<float> out);
private:
/**
Evaluate the wave at a given phase.
Phase must be in the range 0 to 1 excluded.
*/
template <LFOWave W>
static float eval(float phase);
/**
Process the nth subwaveform, adding to the buffer.
This definition is duplicated per each wave, a strategy to avoid a switch
on wave type inside the frame loop.
*/
template <LFOWave W>
void processWave(unsigned nth, absl::Span<float> out);
/**
Process a sample-and-hold subwaveform, adding to the buffer.
*/
template <LFOWave W>
void processSH(unsigned nth, absl::Span<float> out);
/**
Process the step sequencer, adding to the buffer.
*/
void processSteps(absl::Span<float> out);
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace sfz

View file

@ -0,0 +1,31 @@
// 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 "LFODescription.h"
namespace sfz {
LFODescription::LFODescription()
{
sub.resize(1);
}
LFODescription::~LFODescription()
{
}
const LFODescription& LFODescription::getDefault()
{
static LFODescription desc = []() -> LFODescription
{
LFODescription desc;
desc.sub.resize(1);
return desc;
}();
return desc;
}
} // namespace sfz

View file

@ -0,0 +1,48 @@
// 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/optional.h>
#include <vector>
namespace sfz {
enum class LFOWave : int {
Triangle,
Sine,
Pulse75,
Square,
Pulse25,
Pulse12_5,
Ramp,
Saw,
// ARIA extra
RandomSH = 12,
};
struct LFODescription {
LFODescription();
~LFODescription();
static const LFODescription& getDefault();
float freq = 0; // lfoN_freq
float phase0 = 0; // lfoN_phase
float delay = 0; // lfoN_delay
float fade = 0; // lfoN_fade
unsigned count = 0; // lfoN_count
struct Sub {
LFOWave wave = LFOWave::Triangle; // lfoN_wave[X]
float offset = 0; // lfoN_offset[X]
float ratio = 1; // lfoN_ratio[X]
float scale = 1; // lfoN_scale[X]
};
struct StepSequence {
std::vector<float> steps {}; // lfoN_stepX - normalized to unity
};
absl::optional<StepSequence> seq;
std::vector<Sub> sub;
};
} // namespace sfz

View file

@ -786,6 +786,228 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
setValueFromOpcode(opcode, bendSmooth, Default::smoothCCRange);
break;
// Modulation: LFO
case hash("lfo&_freq"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
setValueFromOpcode(opcode, lfos[lfoNumber - 1].freq, Default::lfoFreqRange);
}
break;
case hash("lfo&_phase"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
if (auto value = readOpcode(opcode.value, Default::lfoPhaseRange)) {
float normalPhase = *value * (1.0 / 360.0);
normalPhase -= int(normalPhase);
normalPhase += (normalPhase < 0) ? 1 : 0;
lfos[lfoNumber - 1].phase0 = normalPhase;
}
}
break;
case hash("lfo&_delay"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
setValueFromOpcode(opcode, lfos[lfoNumber - 1].delay, Default::lfoDelayRange);
}
break;
case hash("lfo&_fade"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
setValueFromOpcode(opcode, lfos[lfoNumber - 1].fade, Default::lfoFadeRange);
}
break;
case hash("lfo&_count"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
setValueFromOpcode(opcode, lfos[lfoNumber - 1].count, Default::lfoCountRange);
}
break;
case hash("lfo&_steps"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
if (auto value = readOpcode(opcode.value, Default::lfoStepsRange)) {
if (!lfos[lfoNumber - 1].seq)
lfos[lfoNumber - 1].seq = LFODescription::StepSequence();
lfos[lfoNumber - 1].seq->steps.resize(*value);
}
}
break;
case hash("lfo&_step&"):
{
const auto lfoNumber = opcode.parameters.front();
const auto stepNumber = opcode.parameters[1];
if (lfoNumber == 0 || stepNumber == 0 || stepNumber > config::maxLFOSteps)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
if (auto value = readOpcode(opcode.value, Default::lfoStepXRange)) {
if (!lfos[lfoNumber - 1].seq)
lfos[lfoNumber - 1].seq = LFODescription::StepSequence();
if (!extendIfNecessary(lfos[lfoNumber - 1].seq->steps, stepNumber, Default::numLFOSteps))
return false;
lfos[lfoNumber - 1].seq->steps[stepNumber - 1] = *value * 0.01f;
}
}
break;
case hash("lfo&_wave&"): // also lfo&_wave
{
const auto lfoNumber = opcode.parameters.front();
const auto subNumber = opcode.parameters[1];
if (lfoNumber == 0 || subNumber == 0 || subNumber > config::maxLFOSubs)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
if (auto value = readOpcode(opcode.value, Default::lfoWaveRange)) {
if (!extendIfNecessary(lfos[lfoNumber - 1].sub, subNumber, Default::numLFOSubs))
return false;
lfos[lfoNumber - 1].sub[subNumber - 1].wave = static_cast<LFOWave>(*value);
}
}
break;
case hash("lfo&_offset&"): // also lfo&_offset
{
const auto lfoNumber = opcode.parameters.front();
const auto subNumber = opcode.parameters[1];
if (lfoNumber == 0 || subNumber == 0 || subNumber > config::maxLFOSubs)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
if (auto value = readOpcode(opcode.value, Default::lfoOffsetRange)) {
if (!extendIfNecessary(lfos[lfoNumber - 1].sub, subNumber, Default::numLFOSubs))
return false;
lfos[lfoNumber - 1].sub[subNumber - 1].offset = *value;
}
}
break;
case hash("lfo&_ratio&"): // also lfo&_ratio
{
const auto lfoNumber = opcode.parameters.front();
const auto subNumber = opcode.parameters[1];
if (lfoNumber == 0 || subNumber == 0 || subNumber > config::maxLFOSubs)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
if (auto value = readOpcode(opcode.value, Default::lfoRatioRange)) {
if (!extendIfNecessary(lfos[lfoNumber - 1].sub, subNumber, Default::numLFOSubs))
return false;
lfos[lfoNumber - 1].sub[subNumber - 1].ratio = *value;
}
}
break;
case hash("lfo&_scale&"): // also lfo&_scale
{
const auto lfoNumber = opcode.parameters.front();
const auto subNumber = opcode.parameters[1];
if (lfoNumber == 0 || subNumber == 0 || subNumber > config::maxLFOSubs)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
if (auto value = readOpcode(opcode.value, Default::lfoScaleRange)) {
if (!extendIfNecessary(lfos[lfoNumber - 1].sub, subNumber, Default::numLFOSubs))
return false;
lfos[lfoNumber - 1].sub[subNumber - 1].scale = *value;
}
}
break;
// Modulation: LFO (targets)
case hash("lfo&_amplitude"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) {
ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1);
ModKey target = ModKey::createNXYZ(ModId::Amplitude, id);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("lfo&_pan"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::panCCRange)) {
ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1);
ModKey target = ModKey::createNXYZ(ModId::Pan, id);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("lfo&_width"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::widthCCRange)) {
ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1);
ModKey target = ModKey::createNXYZ(ModId::Width, id);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("lfo&_position"): // sfizz extension
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::positionCCRange)) {
ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1);
ModKey target = ModKey::createNXYZ(ModId::Position, id);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("lfo&_pitch"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::tuneCCRange)) {
ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1);
ModKey target = ModKey::createNXYZ(ModId::Pitch, id);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("lfo&_volume"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::volumeCCRange)) {
ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1);
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);
@ -1324,3 +1546,22 @@ float sfz::Region::getBendInCents(float bend) const noexcept
{
return bend > 0.0f ? bend * static_cast<float>(bendUp) : -bend * static_cast<float>(bendDown);
}
sfz::Region::Connection& sfz::Region::getOrCreateConnection(const ModKey& source, const ModKey& target)
{
auto pred = [&source, &target](const Connection& c)
{
return c.source == source && c.target == target;
};
auto it = std::find_if(connections.begin(), connections.end(), pred);
if (it != connections.end())
return *it;
sfz::Region::Connection c;
c.source = source;
c.target = target;
connections.push_back(c);
return connections.back();
}

View file

@ -12,6 +12,7 @@
#include "EGDescription.h"
#include "EQDescription.h"
#include "FilterDescription.h"
#include "LFODescription.h"
#include "Opcode.h"
#include "AudioBuffer.h"
#include "MidiState.h"
@ -364,6 +365,9 @@ struct Region {
EGDescription pitchEG;
EGDescription filterEG;
// LFOs
std::vector<LFODescription> lfos;
bool hasStereoSample { false };
// Effects
@ -379,6 +383,7 @@ struct Region {
float sourceDepth = 1.0f;
};
std::vector<Connection> connections;
Connection& getOrCreateConnection(const ModKey& source, const ModKey& target);
// Parent
RegionSet* parent { nullptr };

View file

@ -16,6 +16,7 @@
#include "modulations/ModKey.h"
#include "modulations/ModId.h"
#include "modulations/sources/Controller.h"
#include "modulations/sources/LFO.h"
#include "pugixml.hpp"
#include "absl/algorithm/container.h"
#include "absl/memory/memory.h"
@ -42,6 +43,7 @@ sfz::Synth::Synth(int numVoices)
// modulation sources
genController.reset(new ControllerSource(resources));
genLFO.reset(new LFOSource(*this));
}
sfz::Synth::~Synth()
@ -452,6 +454,7 @@ void sfz::Synth::finalizeSfzLoad()
size_t maxFilters { 0 };
size_t maxEQs { 0 };
size_t maxLFOs { 0 };
while (currentRegionIndex < currentRegionCount) {
auto region = regions[currentRegionIndex].get();
@ -562,6 +565,7 @@ void sfz::Synth::finalizeSfzLoad()
region->registerTempo(2.0f);
maxFilters = max(maxFilters, region->filters.size());
maxEQs = max(maxEQs, region->equalizers.size());
maxLFOs = max(maxLFOs, region->lfos.size());
++currentRegionIndex;
}
@ -571,6 +575,7 @@ void sfz::Synth::finalizeSfzLoad()
settingsPerVoice.maxFilters = maxFilters;
settingsPerVoice.maxEQs = maxEQs;
settingsPerVoice.maxLFOs = maxLFOs;
applySettingsPerVoice();
@ -1346,6 +1351,7 @@ void sfz::Synth::applySettingsPerVoice()
for (auto& voice : voices) {
voice->setMaxFiltersPerVoice(settingsPerVoice.maxFilters);
voice->setMaxEQsPerVoice(settingsPerVoice.maxEQs);
voice->setMaxLFOsPerVoice(settingsPerVoice.maxLFOs);
}
}
@ -1361,6 +1367,9 @@ void sfz::Synth::setupModMatrix()
case ModId::Controller:
gen = genController.get();
break;
case ModId::LFO:
gen = genLFO.get();
break;
default:
DBG("[sfizz] Have unknown type of source generator");
break;

View file

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

View file

@ -12,6 +12,7 @@
#include "SIMDHelpers.h"
#include "Panning.h"
#include "SfzHelpers.h"
#include "LFO.h"
#include "modulations/ModId.h"
#include "modulations/ModKey.h"
#include "modulations/ModMatrix.h"
@ -34,6 +35,10 @@ sfz::Voice::Voice(int voiceNumber, sfz::Resources& resources)
filter.setGain(vaGain(config::filteredEnvelopeCutoff, sampleRate));
}
sfz::Voice::~Voice()
{
}
void sfz::Voice::startVoice(Region* region, int delay, int number, float value, sfz::Voice::TriggerType triggerType) noexcept
{
ASSERT(value >= 0.0f && value <= 1.0f);
@ -235,6 +240,9 @@ void sfz::Voice::setSampleRate(float sampleRate) noexcept
for (WavetableOscillator& osc : waveOscillators)
osc.init(sampleRate);
for (auto& lfo : lfos)
lfo->setSampleRate(sampleRate);
}
void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept
@ -773,6 +781,17 @@ void sfz::Voice::setMaxEQsPerVoice(size_t numFilters)
equalizers.reserve(numFilters);
}
void sfz::Voice::setMaxLFOsPerVoice(size_t numLFOs)
{
lfos.resize(numLFOs);
for (size_t i = 0; i < numLFOs; ++i) {
auto lfo = absl::make_unique<LFO>();
lfo->setSampleRate(sampleRate);
lfos[i] = std::move(lfo);
}
}
void sfz::Voice::setupOscillatorUnison()
{
int m = region->oscillatorMulti;

View file

@ -22,6 +22,7 @@
namespace sfz {
enum InterpolatorModel : int;
class LFO;
/**
* @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
@ -38,6 +39,9 @@ public:
* @param midiState
*/
Voice(int voiceNumber, Resources& resources);
~Voice();
enum class TriggerType {
NoteOn,
NoteOff,
@ -270,6 +274,12 @@ public:
* @return
*/
const Region* getRegion() const noexcept { return region; }
/**
* @brief Get the LFO designated by the given index
*
* @param index
*/
LFO* getLFO(size_t index) { return lfos[index].get(); }
/**
* @brief Set the max number of filters per voice
*
@ -279,9 +289,15 @@ public:
/**
* @brief Set the max number of EQs per voice
*
* @param numFilters
* @param numEQs
*/
void setMaxEQsPerVoice(size_t numEQs);
/**
* @brief Set the max number of LFOs per voice
*
* @param numLFOs
*/
void setMaxLFOsPerVoice(size_t numLFOs);
/**
* @brief Release the voice after a given delay
*
@ -433,6 +449,7 @@ private:
std::vector<FilterHolderPtr> filters;
std::vector<EQHolderPtr> equalizers;
std::vector<std::unique_ptr<LFO>> lfos;
ADSREnvelope<float> egEnvelope;
float bendStepFactor { centsFactor(1) };

View file

@ -0,0 +1,67 @@
// 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 "LFO.h"
#include "../../LFO.h"
#include "../../Synth.h"
#include "../../Voice.h"
#include "../../SIMDHelpers.h"
#include "../../Config.h"
#include "../../Debug.h"
namespace sfz {
LFOSource::LFOSource(Synth &synth)
: synth_(&synth)
{
}
void LFOSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId)
{
Synth& synth = *synth_;
unsigned lfoIndex = sourceKey.parameters().N;
Voice* voice = synth.getVoiceById(voiceId);
if (!voice) {
ASSERTFALSE;
return;
}
const Region* region = voice->getRegion();
if (lfoIndex >= region->lfos.size()) {
ASSERTFALSE;
return;
}
LFO* lfo = voice->getLFO(lfoIndex);
lfo->configure(&region->lfos[lfoIndex]);
lfo->start();
}
void LFOSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
{
Synth& synth = *synth_;
const unsigned lfoIndex = sourceKey.parameters().N;
Voice* voice = synth.getVoiceById(voiceId);
if (!voice) {
ASSERTFALSE;
fill(buffer, 0.0f);
return;
}
const Region* region = voice->getRegion();
if (lfoIndex >= region->lfos.size()) {
ASSERTFALSE;
fill(buffer, 0.0f);
return;
}
LFO* lfo = voice->getLFO(lfoIndex);
lfo->process(buffer);
}
} // namespace sfz

View file

@ -0,0 +1,23 @@
// 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 LFOSource : public ModGenerator {
public:
explicit LFOSource(Synth &synth);
void init(const ModKey& sourceKey, NumericId<Voice> voiceId) override;
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
private:
Synth* synth_ = nullptr;
};
} // namespace sfz