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:
parent
9e5d27af3c
commit
a4dbf89766
11 changed files with 87 additions and 47 deletions
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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_);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ enum class ModId : int {
|
|||
FilLFOFrequency,
|
||||
LFOFrequency,
|
||||
LFOBeats,
|
||||
LFOPhase,
|
||||
|
||||
_TargetsEnd,
|
||||
// [/targets] --------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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 {};
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue