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 lfoBeats { 0.0f, {0.0f, 1000.0f}, kPermissiveBounds };
FloatSpec lfoBeatsMod { 0.0f, {-1000.0f, 1000.0f}, kPermissiveBounds }; FloatSpec lfoBeatsMod { 0.0f, {-1000.0f, 1000.0f}, kPermissiveBounds };
FloatSpec lfoPhase { 0.0f, {0.0f, 1.0f}, kWrapPhase|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 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 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 lfoCount { 0, {0, 1000}, kEnforceLowerBound|kPermissiveUpperBound };
UInt32Spec lfoSteps { 0, {0, static_cast<unsigned>(config::maxLFOSteps)}, kEnforceBounds }; UInt32Spec lfoSteps { 0, {0, static_cast<unsigned>(config::maxLFOSteps)}, kEnforceBounds };
FloatSpec lfoStepX { 0.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds }; FloatSpec lfoStepX { 0.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds };

View file

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

View file

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

View file

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

View file

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

View file

@ -1123,6 +1123,7 @@ bool sfz::Region::parseLFOOpcodeV2(const Opcode& opcode)
// //
lfo.beatsKey = ModKey::createNXYZ(ModId::LFOBeats, id, lfoNumber); lfo.beatsKey = ModKey::createNXYZ(ModId::LFOBeats, id, lfoNumber);
lfo.freqKey = ModKey::createNXYZ(ModId::LFOFrequency, id, lfoNumber); lfo.freqKey = ModKey::createNXYZ(ModId::LFOFrequency, id, lfoNumber);
lfo.phaseKey = ModKey::createNXYZ(ModId::LFOPhase, id, lfoNumber);
// //
auto getOrCreateLFOStep = [&opcode, &lfo]() -> float* { auto getOrCreateLFOStep = [&opcode, &lfo]() -> float* {
@ -1172,12 +1173,27 @@ bool sfz::Region::parseLFOOpcodeV2(const Opcode& opcode)
case hash("lfo&_phase"): case hash("lfo&_phase"):
lfo.phase0 = opcode.read(Default::lfoPhase); lfo.phase0 = opcode.read(Default::lfoPhase);
break; break;
case_any_ccN("lfo&_phase"):
processGenericCc(opcode, Default::lfoPhaseMod, ModKey::createNXYZ(ModId::LFOPhase, id, lfoNumber));
break;
case hash("lfo&_delay"): case hash("lfo&_delay"):
lfo.delay = opcode.read(Default::lfoDelay); lfo.delay = opcode.read(Default::lfoDelay);
break; 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"): case hash("lfo&_fade"):
lfo.fade = opcode.read(Default::lfoFade); lfo.fade = opcode.read(Default::lfoFade);
break; 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"): case hash("lfo&_count"):
lfo.count = opcode.read(Default::lfoCount); lfo.count = opcode.read(Default::lfoCount);
break; break;

View file

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

View file

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

View file

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

View file

@ -164,6 +164,8 @@ std::string ModKey::toString() const
return absl::StrCat("LFOFrequency {", region_.number(), ", N=", 1 + params_.N, "}"); return absl::StrCat("LFOFrequency {", region_.number(), ", N=", 1 + params_.N, "}");
case ModId::LFOBeats: case ModId::LFOBeats:
return absl::StrCat("LFOBeats {", region_.number(), ", N=", 1 + params_.N, "}"); return absl::StrCat("LFOBeats {", region_.number(), ", N=", 1 + params_.N, "}");
case ModId::LFOPhase:
return absl::StrCat("LFOPhase {", region_.number(), ", N=", 1 + params_.N, "}");
default: default:
return {}; 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); std::vector<std::unique_ptr<sfz::LFO>> lfos(numLfos);
for (size_t l = 0; l < numLfos; ++l) { 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); lfos[l].reset(lfo);
lfo->setSampleRate(sampleRate); lfo->setSampleRate(sampleRate);
lfo->configure(&desc[l]); lfo->configure(&desc[l]);