Add lfoN_fade_oncc, lfoN_delay_oncc, lfoN_phase_oncc

The phase modifier is actually an  "audio rate" modifier with curve, step and smoothcc options.
The v2 spec only had oncc, which would limit it to a set value with the LFO starts, but having it at audio rate makes sense if you want to tweak the phase while the LFO is running.
This commit is contained in:
Paul Fd 2021-03-30 10:05:11 +02:00
parent 9e5d27af3c
commit a4dbf89766
11 changed files with 87 additions and 47 deletions

View file

@ -119,8 +119,11 @@ FloatSpec lfoFreqMod { 0.0f, {-100.0f, 100.0f}, kPermissiveBounds };
FloatSpec lfoBeats { 0.0f, {0.0f, 1000.0f}, kPermissiveBounds };
FloatSpec lfoBeatsMod { 0.0f, {-1000.0f, 1000.0f}, kPermissiveBounds };
FloatSpec lfoPhase { 0.0f, {0.0f, 1.0f}, kWrapPhase|kPermissiveBounds };
FloatSpec lfoPhaseMod { 0.0f, {0.0f, 1.0f}, kPermissiveBounds };
FloatSpec lfoDelay { 0.0f, {0.0f, 30.0f}, kPermissiveBounds };
FloatSpec lfoDelayMod { 0.0f, {0.0f, 30.0f}, kPermissiveBounds };
FloatSpec lfoFade { 0.0f, {0.0f, 30.0f}, kPermissiveBounds };
FloatSpec lfoFadeMod { 0.0f, {0.0f, 30.0f}, kPermissiveBounds };
UInt32Spec lfoCount { 0, {0, 1000}, kEnforceLowerBound|kPermissiveUpperBound };
UInt32Spec lfoSteps { 0, {0, static_cast<unsigned>(config::maxLFOSteps)}, kEnforceBounds };
FloatSpec lfoStepX { 0.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds };

View file

@ -112,6 +112,12 @@ struct OpcodeSpec
return input;
}
template<>
typename bool normalizeInput(bool input) const
{
return input;
}
operator T() const { return normalizeInput(defaultInputValue); }
};
@ -219,8 +225,11 @@ namespace Default
extern const OpcodeSpec<float> lfoBeats;
extern const OpcodeSpec<float> lfoBeatsMod;
extern const OpcodeSpec<float> lfoPhase;
extern const OpcodeSpec<float> lfoPhaseMod;
extern const OpcodeSpec<float> lfoDelay;
extern const OpcodeSpec<float> lfoDelayMod;
extern const OpcodeSpec<float> lfoFade;
extern const OpcodeSpec<float> lfoFadeMod;
extern const OpcodeSpec<uint32_t> lfoCount;
extern const OpcodeSpec<uint32_t> lfoSteps;
extern const OpcodeSpec<float> lfoStepX;

View file

@ -6,12 +6,10 @@
#include "LFO.h"
#include "LFODescription.h"
#include "BeatClock.h"
#include "BufferPool.h"
#include "MathHelpers.h"
#include "SIMDHelpers.h"
#include "Config.h"
#include "modulations/ModMatrix.h"
#include "Resources.h"
#include "modulations/ModKey.h"
#include "modulations/ModId.h"
#include <array>
@ -21,18 +19,14 @@
namespace sfz {
struct LFO::Impl {
explicit Impl(BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix)
: bufferPool_(bufferPool),
beatClock_(beatClock),
modMatrix_(modMatrix),
explicit Impl(Resources& resources)
: resources_(resources),
sampleRate_(config::defaultSampleRate),
desc_(&LFODescription::getDefault())
{
}
BufferPool& bufferPool_;
BeatClock* beatClock_ = nullptr;
ModMatrix* modMatrix_ = nullptr;
Resources& resources_;
float sampleRate_ = 0;
// control
@ -40,14 +34,15 @@ struct LFO::Impl {
// state
size_t delayFramesLeft_ = 0;
float fadeTime_ = 0;
float fadePosition_ = 0;
std::array<float, config::maxLFOSubs> subPhases_ {{}};
std::array<float, config::maxLFOSubs> sampleHoldMem_ {{}};
std::array<int, config::maxLFOSubs> sampleHoldState_ {{}};
};
LFO::LFO(BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix)
: impl_(new Impl(bufferPool, beatClock, modMatrix))
LFO::LFO(Resources& resources)
: impl_(new Impl(resources))
{
}
@ -70,16 +65,25 @@ void LFO::start(unsigned triggerDelay)
Impl& impl = *impl_;
const LFODescription& desc = *impl.desc_;
const float sampleRate = impl.sampleRate_;
const MidiState& state = impl.resources_.midiState;
impl.subPhases_.fill(0.0f);
impl.sampleHoldMem_.fill(0.0f);
impl.sampleHoldState_.fill(0);
const float delay = desc.delay;
float delay = desc.delay;
for (const auto& mod: desc.delayCC)
delay += mod.data * state.getCCValue(mod.cc);
size_t delayFrames = (delay > 0) ? static_cast<size_t>(std::ceil(sampleRate * delay)) : 0u;
impl.delayFramesLeft_ = triggerDelay + delayFrames;
impl.fadePosition_ = (desc.fade > 0) ? 0.0f : 1.0f;
float fade = desc.fade;
for (const auto& mod: desc.fadeCC)
fade += mod.data * state.getCCValue(mod.cc);
impl.fadeTime_ = fade;
impl.fadePosition_ = (fade > 0) ? 0.0f : 1.0f;
}
template <>
@ -218,6 +222,7 @@ void LFO::process(absl::Span<float> out)
{
Impl& impl = *impl_;
const LFODescription& desc = *impl.desc_;
BufferPool& pool = impl.resources_.bufferPool;
size_t numFrames = out.size();
fill(out, 0.0f);
@ -235,7 +240,7 @@ void LFO::process(absl::Span<float> out)
if (countSubs < 1)
return;
auto phasesTemp = impl.bufferPool_.getBuffer(numFrames);
auto phasesTemp = pool.getBuffer(numFrames);
if (!phasesTemp) {
ASSERTFALSE;
fill(out, 0.0f);
@ -289,7 +294,6 @@ void LFO::process(absl::Span<float> 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();
@ -297,7 +301,7 @@ void LFO::processFadeIn(absl::Span<float> out)
if (fadePosition >= 1.0f)
return;
const float fadeTime = desc.fade;
const float fadeTime = impl.fadeTime_;
const float fadeStep = samplePeriod / fadeTime;
for (size_t i = 0; i < numFrames && fadePosition < 1; ++i) {
@ -311,9 +315,9 @@ void LFO::processFadeIn(absl::Span<float> out)
void LFO::generatePhase(unsigned nth, absl::Span<float> phases)
{
Impl& impl = *impl_;
BufferPool& bufferPool = impl.bufferPool_;
BeatClock* beatClock = impl.beatClock_;
ModMatrix* modMatrix = impl.modMatrix_;
BufferPool& bufferPool = impl.resources_.bufferPool;
BeatClock& beatClock = impl.resources_.beatClock;
ModMatrix& modMatrix = impl.resources_.modMatrix;
const LFODescription& desc = *impl.desc_;
const LFODescription::Sub& sub = desc.sub[nth];
const float samplePeriod = 1.0f / impl.sampleRate_;
@ -329,39 +333,39 @@ void LFO::generatePhase(unsigned nth, absl::Span<float> phases)
// modulations
const float* beatsMod = nullptr;
const float* freqMod = nullptr;
if (modMatrix) {
// Note(jpc) we might switch between beats and frequency, if host
// switches play state on and off; continually generate both.
beatsMod = modMatrix->getModulationByKey(desc.beatsKey);
freqMod = modMatrix->getModulationByKey(desc.freqKey);
}
const float* phaseMod = nullptr;
// Note(jpc) we might switch between beats and frequency, if host
// switches play state on and off; continually generate both.
beatsMod = modMatrix.getModulationByKey(desc.beatsKey);
freqMod = modMatrix.getModulationByKey(desc.freqKey);
phaseMod = modMatrix.getModulationByKey(desc.phaseKey);
if (beatClock && beatClock->isPlaying() && beats > 0) {
if (beatClock.isPlaying() && beats > 0) {
// generate using the beat clock
float beatRatio = (ratio > 0) ? (1.0f / ratio) : 0.0f;
if (!beatsMod)
beatClock->calculatePhase(beats * beatRatio, phases.data());
beatClock.calculatePhase(beats * beatRatio, phases.data());
else {
auto temp = bufferPool.getBuffer(numFrames);
if (!temp) {
ASSERTFALSE;
beatClock->calculatePhase(beats * beatRatio, phases.data());
beatClock.calculatePhase(beats * beatRatio, phases.data());
}
else {
fill(*temp, beats);
add(absl::MakeConstSpan(beatsMod, numFrames), *temp);
applyGain1(beatRatio, *temp);
beatClock->calculatePhaseModulated(temp->data(), phases.data());
beatClock.calculatePhaseModulated(temp->data(), phases.data());
}
}
}
else {
// generate using the frequency
if (!freqMod) {
float incr = ratio * samplePeriod * baseFreq;
for (size_t i = 0; i < numFrames; ++i) {
phases[i] = phase;
float incr = ratio * samplePeriod * baseFreq;
phase = wrapPhase(phase + incr);
}
}
@ -372,13 +376,16 @@ void LFO::generatePhase(unsigned nth, absl::Span<float> phases)
phase = wrapPhase(phase + incr);
}
}
}
// apply phase offsets
for (size_t i = 0; i < numFrames; ++i) {
float withOffset = phases[i] + phaseOffset;
withOffset -= (int)withOffset;
phases[i] = withOffset;
if (!phaseMod) {
for (size_t i = 0; i < numFrames; ++i)
phases[i] = wrapPhase(phases[i] + phaseOffset);
} else {
for (size_t i = 0; i < numFrames; ++i)
phases[i] = wrapPhase(phases[i] + phaseOffset + phaseMod[i]);
}
impl.subPhases_[nth] = phase;

View file

@ -10,9 +10,7 @@
#include <memory>
namespace sfz {
class BufferPool;
class BeatClock;
class ModMatrix;
class Resources;
struct Region;
enum class LFOWave : int;
@ -54,10 +52,7 @@ struct LFODescription;
class LFO {
public:
explicit LFO(
BufferPool& bufferPool,
BeatClock* beatClock = nullptr,
ModMatrix* modMatrix = nullptr);
explicit LFO(Resources& resources);
~LFO();
/**

View file

@ -6,6 +6,7 @@
#pragma once
#include "Defaults.h"
#include "CCMap.h"
#include "modulations/ModKey.h"
#include <absl/types/optional.h>
#include <vector>
@ -19,8 +20,11 @@ struct LFODescription {
float freq { Default::lfoFreq }; // lfoN_freq
float beats { Default::lfoBeats }; // lfoN_beats
float phase0 { Default::lfoPhase }; // lfoN_phase
CCMap<float> phaseCC { Default::lfoPhaseMod }; // lfoN_phase_cc
float delay { Default::lfoDelay }; // lfoN_delay
CCMap<float> delayCC { Default::lfoDelayMod }; // lfoN_phase_cc
float fade { Default::lfoFade }; // lfoN_fade
CCMap<float> fadeCC { Default::lfoFadeMod }; // lfoN_phase_cc
unsigned count { Default::lfoCount }; // lfoN_count
struct Sub {
LFOWave wave { Default::lfoWave }; // lfoN_wave[X]
@ -37,6 +41,7 @@ struct LFODescription {
// modulations
ModKey beatsKey;
ModKey freqKey;
ModKey phaseKey;
};
} // namespace sfz

View file

@ -1123,6 +1123,7 @@ bool sfz::Region::parseLFOOpcodeV2(const Opcode& opcode)
//
lfo.beatsKey = ModKey::createNXYZ(ModId::LFOBeats, id, lfoNumber);
lfo.freqKey = ModKey::createNXYZ(ModId::LFOFrequency, id, lfoNumber);
lfo.phaseKey = ModKey::createNXYZ(ModId::LFOPhase, id, lfoNumber);
//
auto getOrCreateLFOStep = [&opcode, &lfo]() -> float* {
@ -1172,12 +1173,27 @@ bool sfz::Region::parseLFOOpcodeV2(const Opcode& opcode)
case hash("lfo&_phase"):
lfo.phase0 = opcode.read(Default::lfoPhase);
break;
case_any_ccN("lfo&_phase"):
processGenericCc(opcode, Default::lfoPhaseMod, ModKey::createNXYZ(ModId::LFOPhase, id, lfoNumber));
break;
case hash("lfo&_delay"):
lfo.delay = opcode.read(Default::lfoDelay);
break;
case hash("lfo&_delay_oncc&"):
if (opcode.parameters.back() > config::numCCs)
return false;
lfo.delayCC[opcode.parameters.back()] = opcode.read(Default::lfoDelayMod);
break;
case hash("lfo&_fade"):
lfo.fade = opcode.read(Default::lfoFade);
break;
case hash("lfo&_fade_oncc&"):
if (opcode.parameters.back() > config::numCCs)
return false;
lfo.fadeCC[opcode.parameters.back()] = opcode.read(Default::lfoFadeMod);
break;
case hash("lfo&_count"):
lfo.count = opcode.read(Default::lfoCount);
break;

View file

@ -1671,7 +1671,7 @@ void Voice::setMaxLFOsPerVoice(size_t numLFOs)
impl.lfos_.resize(numLFOs);
for (size_t i = 0; i < numLFOs; ++i) {
auto lfo = absl::make_unique<LFO>(resources.bufferPool, &resources.beatClock, &resources.modMatrix);
auto lfo = absl::make_unique<LFO>(resources);
lfo->setSampleRate(impl.sampleRate_);
impl.lfos_[i] = std::move(lfo);
}
@ -1712,7 +1712,7 @@ void Voice::setAmplitudeLFOEnabledPerVoice(bool haveAmplitudeLFO)
Impl& impl = *impl_;
Resources& res = impl.resources_;
if (haveAmplitudeLFO) {
LFO* lfo = new LFO(res.bufferPool, &res.beatClock, &res.modMatrix);
LFO* lfo = new LFO(res);
impl.lfoAmplitude_.reset(lfo);
lfo->setSampleRate(impl.sampleRate_);
}
@ -1725,7 +1725,7 @@ void Voice::setPitchLFOEnabledPerVoice(bool havePitchLFO)
Impl& impl = *impl_;
Resources& res = impl.resources_;
if (havePitchLFO) {
LFO* lfo = new LFO(res.bufferPool, &res.beatClock, &res.modMatrix);
LFO* lfo = new LFO(res);
impl.lfoPitch_.reset(lfo);
lfo->setSampleRate(impl.sampleRate_);
}
@ -1738,7 +1738,7 @@ void Voice::setFilterLFOEnabledPerVoice(bool haveFilterLFO)
Impl& impl = *impl_;
Resources& res = impl.resources_;
if (haveFilterLFO) {
LFO* lfo = new LFO(res.bufferPool, &res.beatClock, &res.modMatrix);
LFO* lfo = new LFO(res);
impl.lfoFilter_.reset(lfo);
lfo->setSampleRate(impl.sampleRate_);
}

View file

@ -96,6 +96,8 @@ int ModIds::flags(ModId id) noexcept
return kModIsPerVoice|kModIsAdditive;
case ModId::LFOBeats:
return kModIsPerVoice|kModIsAdditive;
case ModId::LFOPhase:
return kModIsPerVoice|kModIsAdditive;
// unknown
default:

View file

@ -63,6 +63,7 @@ enum class ModId : int {
FilLFOFrequency,
LFOFrequency,
LFOBeats,
LFOPhase,
_TargetsEnd,
// [/targets] --------------------------------------------------------------

View file

@ -164,6 +164,8 @@ std::string ModKey::toString() const
return absl::StrCat("LFOFrequency {", region_.number(), ", N=", 1 + params_.N, "}");
case ModId::LFOBeats:
return absl::StrCat("LFOBeats {", region_.number(), ", N=", 1 + params_.N, "}");
case ModId::LFOPhase:
return absl::StrCat("LFOPhase {", region_.number(), ", N=", 1 + params_.N, "}");
default:
return {};

View file

@ -28,7 +28,7 @@ static bool computeLFO(DataPoints& dp, const fs::path& sfzPath, double sampleRat
std::vector<std::unique_ptr<sfz::LFO>> lfos(numLfos);
for (size_t l = 0; l < numLfos; ++l) {
sfz::LFO* lfo = new sfz::LFO(resources.bufferPool);
sfz::LFO* lfo = new sfz::LFO(resources);
lfos[l].reset(lfo);
lfo->setSampleRate(sampleRate);
lfo->configure(&desc[l]);