Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LFO #338

Merged
merged 11 commits into from
Aug 13, 2020
Merged

LFO #338

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions dpf.mk
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

#
# A build file to help using sfizz with the DISTRHO Plugin Framework (DPF)
# ------------------------------------------------------------------------
Expand Down Expand Up @@ -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 \
Expand All @@ -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 \
Expand Down
6 changes: 6 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/sfizz/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 16 additions & 0 deletions src/sfizz/Defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down
308 changes: 308 additions & 0 deletions src/sfizz/LFO.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
// 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 fadePosition_ = 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;

impl.fadePosition_ = (desc.fade > 0) ? 0.0f : 1.0f;
}

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();

const float samplePeriod = 1.0f / impl.sampleRate_;
const float baseFreq = desc.freq;
const float offset = sub.offset;
const float ratio = sub.ratio;
const float scale = sub.scale;
float phase = impl.subPhases_[nth];

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)
paulfd marked this conversation as resolved.
Show resolved Hide resolved
{
Impl& impl = *impl_;
const LFODescription& desc = *impl.desc_;
const LFODescription::Sub& sub = desc.sub[nth];
const size_t numFrames = out.size();

const float samplePeriod = 1.0f / impl.sampleRate_;
const float baseFreq = desc.freq;
const float offset = sub.offset;
const float ratio = sub.ratio;
const float scale = sub.scale;
float sampleHoldValue = impl.sampleHoldMem_[nth];
float phase = impl.subPhases_[nth];

for (size_t i = 0; i < numFrames; ++i) {
out[i] += offset + scale * sampleHoldValue;

// TODO(jpc) lfoN_count: number of repetitions
paulfd marked this conversation as resolved.
Show resolved Hide resolved

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;

const float samplePeriod = 1.0f / impl.sampleRate_;
const float baseFreq = desc.freq;
const float offset = sub.offset;
const float ratio = sub.ratio;
const float scale = sub.scale;
float phase = impl.subPhases_[nth];

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;
}
}

processFadeIn(out);
}

void LFO::processFadeIn(absl::Span<float> out)
{
Impl& impl = *impl_;
const LFODescription& desc = *impl.desc_;
const float samplePeriod = 1.0f / impl.sampleRate_;
size_t numFrames = out.size();

float fadePosition = impl.fadePosition_;
if (fadePosition >= 1.0f)
return;

const float fadeTime = desc.fade;
const float fadeStep = samplePeriod / fadeTime;

for (size_t i = 0; i < numFrames && fadePosition < 1; ++i) {
out[i] *= fadePosition;
fadePosition = std::min(1.0f, fadePosition + fadeStep);
}

impl.fadePosition_ = fadePosition;
}

} // namespace sfz
Loading