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)
|
# 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/ModKeyHash.cpp \
|
||||||
src/sfizz/modulations/ModMatrix.cpp \
|
src/sfizz/modulations/ModMatrix.cpp \
|
||||||
src/sfizz/modulations/sources/Controller.cpp \
|
src/sfizz/modulations/sources/Controller.cpp \
|
||||||
|
src/sfizz/modulations/sources/LFO.cpp \
|
||||||
src/sfizz/effects/Compressor.cpp \
|
src/sfizz/effects/Compressor.cpp \
|
||||||
src/sfizz/effects/Disto.cpp \
|
src/sfizz/effects/Disto.cpp \
|
||||||
src/sfizz/effects/Eq.cpp \
|
src/sfizz/effects/Eq.cpp \
|
||||||
|
|
@ -91,6 +93,8 @@ SFIZZ_SOURCES = \
|
||||||
src/sfizz/FilterPool.cpp \
|
src/sfizz/FilterPool.cpp \
|
||||||
src/sfizz/FloatEnvelopes.cpp \
|
src/sfizz/FloatEnvelopes.cpp \
|
||||||
src/sfizz/Logger.cpp \
|
src/sfizz/Logger.cpp \
|
||||||
|
src/sfizz/LFO.cpp \
|
||||||
|
src/sfizz/LFODescription.cpp \
|
||||||
src/sfizz/MidiState.cpp \
|
src/sfizz/MidiState.cpp \
|
||||||
src/sfizz/OpcodeCleanup.cpp \
|
src/sfizz/OpcodeCleanup.cpp \
|
||||||
src/sfizz/Opcode.cpp \
|
src/sfizz/Opcode.cpp \
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ set (SFIZZ_HEADERS
|
||||||
sfizz/modulations/ModMatrix.h
|
sfizz/modulations/ModMatrix.h
|
||||||
sfizz/modulations/ModGenerator.h
|
sfizz/modulations/ModGenerator.h
|
||||||
sfizz/modulations/sources/Controller.h
|
sfizz/modulations/sources/Controller.h
|
||||||
|
sfizz/modulations/sources/LFO.h
|
||||||
sfizz/effects/impl/ResonantArray.h
|
sfizz/effects/impl/ResonantArray.h
|
||||||
sfizz/effects/impl/ResonantArrayAVX.h
|
sfizz/effects/impl/ResonantArrayAVX.h
|
||||||
sfizz/effects/impl/ResonantArraySSE.h
|
sfizz/effects/impl/ResonantArraySSE.h
|
||||||
|
|
@ -70,6 +71,8 @@ set (SFIZZ_HEADERS
|
||||||
sfizz/Interpolators.h
|
sfizz/Interpolators.h
|
||||||
sfizz/Interpolators.hpp
|
sfizz/Interpolators.hpp
|
||||||
sfizz/Logger.h
|
sfizz/Logger.h
|
||||||
|
sfizz/LFO.h
|
||||||
|
sfizz/LFODescription.h
|
||||||
sfizz/MathHelpers.h
|
sfizz/MathHelpers.h
|
||||||
sfizz/MidiState.h
|
sfizz/MidiState.h
|
||||||
sfizz/ModifierHelpers.h
|
sfizz/ModifierHelpers.h
|
||||||
|
|
@ -133,11 +136,14 @@ set (SFIZZ_SOURCES
|
||||||
sfizz/RTSemaphore.cpp
|
sfizz/RTSemaphore.cpp
|
||||||
sfizz/Panning.cpp
|
sfizz/Panning.cpp
|
||||||
sfizz/Effects.cpp
|
sfizz/Effects.cpp
|
||||||
|
sfizz/LFO.cpp
|
||||||
|
sfizz/LFODescription.cpp
|
||||||
sfizz/modulations/ModId.cpp
|
sfizz/modulations/ModId.cpp
|
||||||
sfizz/modulations/ModKey.cpp
|
sfizz/modulations/ModKey.cpp
|
||||||
sfizz/modulations/ModKeyHash.cpp
|
sfizz/modulations/ModKeyHash.cpp
|
||||||
sfizz/modulations/ModMatrix.cpp
|
sfizz/modulations/ModMatrix.cpp
|
||||||
sfizz/modulations/sources/Controller.cpp
|
sfizz/modulations/sources/Controller.cpp
|
||||||
|
sfizz/modulations/sources/LFO.cpp
|
||||||
sfizz/effects/Nothing.cpp
|
sfizz/effects/Nothing.cpp
|
||||||
sfizz/effects/Filter.cpp
|
sfizz/effects/Filter.cpp
|
||||||
sfizz/effects/Eq.cpp
|
sfizz/effects/Eq.cpp
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,8 @@ namespace config {
|
||||||
constexpr unsigned int defaultAlignment { 16 };
|
constexpr unsigned int defaultAlignment { 16 };
|
||||||
constexpr int filtersInPool { maxVoices * 2 };
|
constexpr int filtersInPool { maxVoices * 2 };
|
||||||
constexpr int excessFileFrames { 8 };
|
constexpr int excessFileFrames { 8 };
|
||||||
|
constexpr int maxLFOSubs { 8 };
|
||||||
|
constexpr int maxLFOSteps { 128 };
|
||||||
/**
|
/**
|
||||||
* @brief The threshold for age stealing.
|
* @brief The threshold for age stealing.
|
||||||
* In percentage of the voice's max age.
|
* In percentage of the voice's max age.
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,22 @@ namespace Default
|
||||||
constexpr int bendStep { 1 };
|
constexpr int bendStep { 1 };
|
||||||
constexpr uint8_t bendSmooth { 0 };
|
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
|
// Envelope generators
|
||||||
constexpr float attack { 0 };
|
constexpr float attack { 0 };
|
||||||
constexpr float decay { 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);
|
setValueFromOpcode(opcode, bendSmooth, Default::smoothCCRange);
|
||||||
break;
|
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
|
// Amplitude Envelope
|
||||||
case hash("ampeg_attack"):
|
case hash("ampeg_attack"):
|
||||||
setValueFromOpcode(opcode, amplitudeEG.attack, Default::egTimeRange);
|
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);
|
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 "EGDescription.h"
|
||||||
#include "EQDescription.h"
|
#include "EQDescription.h"
|
||||||
#include "FilterDescription.h"
|
#include "FilterDescription.h"
|
||||||
|
#include "LFODescription.h"
|
||||||
#include "Opcode.h"
|
#include "Opcode.h"
|
||||||
#include "AudioBuffer.h"
|
#include "AudioBuffer.h"
|
||||||
#include "MidiState.h"
|
#include "MidiState.h"
|
||||||
|
|
@ -364,6 +365,9 @@ struct Region {
|
||||||
EGDescription pitchEG;
|
EGDescription pitchEG;
|
||||||
EGDescription filterEG;
|
EGDescription filterEG;
|
||||||
|
|
||||||
|
// LFOs
|
||||||
|
std::vector<LFODescription> lfos;
|
||||||
|
|
||||||
bool hasStereoSample { false };
|
bool hasStereoSample { false };
|
||||||
|
|
||||||
// Effects
|
// Effects
|
||||||
|
|
@ -379,6 +383,7 @@ struct Region {
|
||||||
float sourceDepth = 1.0f;
|
float sourceDepth = 1.0f;
|
||||||
};
|
};
|
||||||
std::vector<Connection> connections;
|
std::vector<Connection> connections;
|
||||||
|
Connection& getOrCreateConnection(const ModKey& source, const ModKey& target);
|
||||||
|
|
||||||
// Parent
|
// Parent
|
||||||
RegionSet* parent { nullptr };
|
RegionSet* parent { nullptr };
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
#include "modulations/ModKey.h"
|
#include "modulations/ModKey.h"
|
||||||
#include "modulations/ModId.h"
|
#include "modulations/ModId.h"
|
||||||
#include "modulations/sources/Controller.h"
|
#include "modulations/sources/Controller.h"
|
||||||
|
#include "modulations/sources/LFO.h"
|
||||||
#include "pugixml.hpp"
|
#include "pugixml.hpp"
|
||||||
#include "absl/algorithm/container.h"
|
#include "absl/algorithm/container.h"
|
||||||
#include "absl/memory/memory.h"
|
#include "absl/memory/memory.h"
|
||||||
|
|
@ -42,6 +43,7 @@ sfz::Synth::Synth(int numVoices)
|
||||||
|
|
||||||
// modulation sources
|
// modulation sources
|
||||||
genController.reset(new ControllerSource(resources));
|
genController.reset(new ControllerSource(resources));
|
||||||
|
genLFO.reset(new LFOSource(*this));
|
||||||
}
|
}
|
||||||
|
|
||||||
sfz::Synth::~Synth()
|
sfz::Synth::~Synth()
|
||||||
|
|
@ -452,6 +454,7 @@ void sfz::Synth::finalizeSfzLoad()
|
||||||
|
|
||||||
size_t maxFilters { 0 };
|
size_t maxFilters { 0 };
|
||||||
size_t maxEQs { 0 };
|
size_t maxEQs { 0 };
|
||||||
|
size_t maxLFOs { 0 };
|
||||||
|
|
||||||
while (currentRegionIndex < currentRegionCount) {
|
while (currentRegionIndex < currentRegionCount) {
|
||||||
auto region = regions[currentRegionIndex].get();
|
auto region = regions[currentRegionIndex].get();
|
||||||
|
|
@ -562,6 +565,7 @@ void sfz::Synth::finalizeSfzLoad()
|
||||||
region->registerTempo(2.0f);
|
region->registerTempo(2.0f);
|
||||||
maxFilters = max(maxFilters, region->filters.size());
|
maxFilters = max(maxFilters, region->filters.size());
|
||||||
maxEQs = max(maxEQs, region->equalizers.size());
|
maxEQs = max(maxEQs, region->equalizers.size());
|
||||||
|
maxLFOs = max(maxLFOs, region->lfos.size());
|
||||||
|
|
||||||
++currentRegionIndex;
|
++currentRegionIndex;
|
||||||
}
|
}
|
||||||
|
|
@ -571,6 +575,7 @@ void sfz::Synth::finalizeSfzLoad()
|
||||||
|
|
||||||
settingsPerVoice.maxFilters = maxFilters;
|
settingsPerVoice.maxFilters = maxFilters;
|
||||||
settingsPerVoice.maxEQs = maxEQs;
|
settingsPerVoice.maxEQs = maxEQs;
|
||||||
|
settingsPerVoice.maxLFOs = maxLFOs;
|
||||||
|
|
||||||
applySettingsPerVoice();
|
applySettingsPerVoice();
|
||||||
|
|
||||||
|
|
@ -1346,6 +1351,7 @@ void sfz::Synth::applySettingsPerVoice()
|
||||||
for (auto& voice : voices) {
|
for (auto& voice : voices) {
|
||||||
voice->setMaxFiltersPerVoice(settingsPerVoice.maxFilters);
|
voice->setMaxFiltersPerVoice(settingsPerVoice.maxFilters);
|
||||||
voice->setMaxEQsPerVoice(settingsPerVoice.maxEQs);
|
voice->setMaxEQsPerVoice(settingsPerVoice.maxEQs);
|
||||||
|
voice->setMaxLFOsPerVoice(settingsPerVoice.maxLFOs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1361,6 +1367,9 @@ void sfz::Synth::setupModMatrix()
|
||||||
case ModId::Controller:
|
case ModId::Controller:
|
||||||
gen = genController.get();
|
gen = genController.get();
|
||||||
break;
|
break;
|
||||||
|
case ModId::LFO:
|
||||||
|
gen = genLFO.get();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
DBG("[sfizz] Have unknown type of source generator");
|
DBG("[sfizz] Have unknown type of source generator");
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
class ControllerSource;
|
class ControllerSource;
|
||||||
|
class LFOSource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief This class is the core of the sfizz library. In C++ it is the main point
|
* @brief This class is the core of the sfizz library. In C++ it is the main point
|
||||||
|
|
@ -767,11 +768,13 @@ private:
|
||||||
|
|
||||||
// Modulation source generators
|
// Modulation source generators
|
||||||
std::unique_ptr<ControllerSource> genController;
|
std::unique_ptr<ControllerSource> genController;
|
||||||
|
std::unique_ptr<LFOSource> genLFO;
|
||||||
|
|
||||||
// Settings per voice
|
// Settings per voice
|
||||||
struct SettingsPerVoice {
|
struct SettingsPerVoice {
|
||||||
size_t maxFilters { 0 };
|
size_t maxFilters { 0 };
|
||||||
size_t maxEQs { 0 };
|
size_t maxEQs { 0 };
|
||||||
|
size_t maxLFOs { 0 };
|
||||||
};
|
};
|
||||||
SettingsPerVoice settingsPerVoice;
|
SettingsPerVoice settingsPerVoice;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include "SIMDHelpers.h"
|
#include "SIMDHelpers.h"
|
||||||
#include "Panning.h"
|
#include "Panning.h"
|
||||||
#include "SfzHelpers.h"
|
#include "SfzHelpers.h"
|
||||||
|
#include "LFO.h"
|
||||||
#include "modulations/ModId.h"
|
#include "modulations/ModId.h"
|
||||||
#include "modulations/ModKey.h"
|
#include "modulations/ModKey.h"
|
||||||
#include "modulations/ModMatrix.h"
|
#include "modulations/ModMatrix.h"
|
||||||
|
|
@ -34,6 +35,10 @@ sfz::Voice::Voice(int voiceNumber, sfz::Resources& resources)
|
||||||
filter.setGain(vaGain(config::filteredEnvelopeCutoff, sampleRate));
|
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
|
void sfz::Voice::startVoice(Region* region, int delay, int number, float value, sfz::Voice::TriggerType triggerType) noexcept
|
||||||
{
|
{
|
||||||
ASSERT(value >= 0.0f && value <= 1.0f);
|
ASSERT(value >= 0.0f && value <= 1.0f);
|
||||||
|
|
@ -235,6 +240,9 @@ void sfz::Voice::setSampleRate(float sampleRate) noexcept
|
||||||
|
|
||||||
for (WavetableOscillator& osc : waveOscillators)
|
for (WavetableOscillator& osc : waveOscillators)
|
||||||
osc.init(sampleRate);
|
osc.init(sampleRate);
|
||||||
|
|
||||||
|
for (auto& lfo : lfos)
|
||||||
|
lfo->setSampleRate(sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept
|
void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept
|
||||||
|
|
@ -773,6 +781,17 @@ void sfz::Voice::setMaxEQsPerVoice(size_t numFilters)
|
||||||
equalizers.reserve(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()
|
void sfz::Voice::setupOscillatorUnison()
|
||||||
{
|
{
|
||||||
int m = region->oscillatorMulti;
|
int m = region->oscillatorMulti;
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
enum InterpolatorModel : int;
|
enum InterpolatorModel : int;
|
||||||
|
class LFO;
|
||||||
/**
|
/**
|
||||||
* @brief The SFZ voice are the polyphony holders. They get activated by the synth
|
* @brief The SFZ voice are the polyphony holders. They get activated by the synth
|
||||||
* and tasked to play a given region until the end, stopping on note-offs, off-groups
|
* and tasked to play a given region until the end, stopping on note-offs, off-groups
|
||||||
|
|
@ -38,6 +39,9 @@ public:
|
||||||
* @param midiState
|
* @param midiState
|
||||||
*/
|
*/
|
||||||
Voice(int voiceNumber, Resources& resources);
|
Voice(int voiceNumber, Resources& resources);
|
||||||
|
|
||||||
|
~Voice();
|
||||||
|
|
||||||
enum class TriggerType {
|
enum class TriggerType {
|
||||||
NoteOn,
|
NoteOn,
|
||||||
NoteOff,
|
NoteOff,
|
||||||
|
|
@ -270,6 +274,12 @@ public:
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
const Region* getRegion() const noexcept { return region; }
|
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
|
* @brief Set the max number of filters per voice
|
||||||
*
|
*
|
||||||
|
|
@ -279,9 +289,15 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief Set the max number of EQs per voice
|
* @brief Set the max number of EQs per voice
|
||||||
*
|
*
|
||||||
* @param numFilters
|
* @param numEQs
|
||||||
*/
|
*/
|
||||||
void setMaxEQsPerVoice(size_t 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
|
* @brief Release the voice after a given delay
|
||||||
*
|
*
|
||||||
|
|
@ -433,6 +449,7 @@ private:
|
||||||
|
|
||||||
std::vector<FilterHolderPtr> filters;
|
std::vector<FilterHolderPtr> filters;
|
||||||
std::vector<EQHolderPtr> equalizers;
|
std::vector<EQHolderPtr> equalizers;
|
||||||
|
std::vector<std::unique_ptr<LFO>> lfos;
|
||||||
|
|
||||||
ADSREnvelope<float> egEnvelope;
|
ADSREnvelope<float> egEnvelope;
|
||||||
float bendStepFactor { centsFactor(1) };
|
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