Add SFZv2 LFO and a small set of modulation targets
This commit is contained in:
parent
094107206a
commit
eab53ba05a
16 changed files with 899 additions and 1 deletions
4
dpf.mk
4
dpf.mk
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
295
src/sfizz/LFO.cpp
Normal 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
112
src/sfizz/LFO.h
Normal 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
|
||||
31
src/sfizz/LFODescription.cpp
Normal file
31
src/sfizz/LFODescription.cpp
Normal 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
|
||||
48
src/sfizz/LFODescription.h
Normal file
48
src/sfizz/LFODescription.h
Normal 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
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) };
|
||||
|
|
|
|||
67
src/sfizz/modulations/sources/LFO.cpp
Normal file
67
src/sfizz/modulations/sources/LFO.cpp
Normal 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(®ion->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
|
||||
23
src/sfizz/modulations/sources/LFO.h
Normal file
23
src/sfizz/modulations/sources/LFO.h
Normal 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
|
||||
Loading…
Add table
Reference in a new issue