From a4dbf8976672e1c7e6567cbb1aeee37cc93a5bae Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Tue, 30 Mar 2021 10:05:11 +0200 Subject: [PATCH] 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. --- src/sfizz/Defaults.cpp | 3 ++ src/sfizz/Defaults.h | 9 ++++ src/sfizz/LFO.cpp | 77 +++++++++++++++++--------------- src/sfizz/LFO.h | 9 +--- src/sfizz/LFODescription.h | 5 +++ src/sfizz/Region.cpp | 16 +++++++ src/sfizz/Voice.cpp | 8 ++-- src/sfizz/modulations/ModId.cpp | 2 + src/sfizz/modulations/ModId.h | 1 + src/sfizz/modulations/ModKey.cpp | 2 + tests/LFOT.cpp | 2 +- 11 files changed, 87 insertions(+), 47 deletions(-) diff --git a/src/sfizz/Defaults.cpp b/src/sfizz/Defaults.cpp index 2d2886f0..5d649c87 100644 --- a/src/sfizz/Defaults.cpp +++ b/src/sfizz/Defaults.cpp @@ -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(config::maxLFOSteps)}, kEnforceBounds }; FloatSpec lfoStepX { 0.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds }; diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 70735abf..8bb62ee3 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -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 lfoBeats; extern const OpcodeSpec lfoBeatsMod; extern const OpcodeSpec lfoPhase; + extern const OpcodeSpec lfoPhaseMod; extern const OpcodeSpec lfoDelay; + extern const OpcodeSpec lfoDelayMod; extern const OpcodeSpec lfoFade; + extern const OpcodeSpec lfoFadeMod; extern const OpcodeSpec lfoCount; extern const OpcodeSpec lfoSteps; extern const OpcodeSpec lfoStepX; diff --git a/src/sfizz/LFO.cpp b/src/sfizz/LFO.cpp index 3b23dfb3..334d2379 100644 --- a/src/sfizz/LFO.cpp +++ b/src/sfizz/LFO.cpp @@ -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 @@ -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 subPhases_ {{}}; std::array sampleHoldMem_ {{}}; std::array 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(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 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 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 out) void LFO::processFadeIn(absl::Span 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 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 out) void LFO::generatePhase(unsigned nth, absl::Span 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 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 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; diff --git a/src/sfizz/LFO.h b/src/sfizz/LFO.h index ab2f415b..226fd679 100644 --- a/src/sfizz/LFO.h +++ b/src/sfizz/LFO.h @@ -10,9 +10,7 @@ #include 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(); /** diff --git a/src/sfizz/LFODescription.h b/src/sfizz/LFODescription.h index a4abfa62..29c07a1e 100644 --- a/src/sfizz/LFODescription.h +++ b/src/sfizz/LFODescription.h @@ -6,6 +6,7 @@ #pragma once #include "Defaults.h" +#include "CCMap.h" #include "modulations/ModKey.h" #include #include @@ -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 phaseCC { Default::lfoPhaseMod }; // lfoN_phase_cc float delay { Default::lfoDelay }; // lfoN_delay + CCMap delayCC { Default::lfoDelayMod }; // lfoN_phase_cc float fade { Default::lfoFade }; // lfoN_fade + CCMap 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 diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 90f4b046..f759ee84 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -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; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 1e65d160..0ee9863e 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -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(resources.bufferPool, &resources.beatClock, &resources.modMatrix); + auto lfo = absl::make_unique(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_); } diff --git a/src/sfizz/modulations/ModId.cpp b/src/sfizz/modulations/ModId.cpp index c207b59f..a293b267 100644 --- a/src/sfizz/modulations/ModId.cpp +++ b/src/sfizz/modulations/ModId.cpp @@ -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: diff --git a/src/sfizz/modulations/ModId.h b/src/sfizz/modulations/ModId.h index cef95c50..cd64b130 100644 --- a/src/sfizz/modulations/ModId.h +++ b/src/sfizz/modulations/ModId.h @@ -63,6 +63,7 @@ enum class ModId : int { FilLFOFrequency, LFOFrequency, LFOBeats, + LFOPhase, _TargetsEnd, // [/targets] -------------------------------------------------------------- diff --git a/src/sfizz/modulations/ModKey.cpp b/src/sfizz/modulations/ModKey.cpp index 6abc40c5..7f73a25e 100644 --- a/src/sfizz/modulations/ModKey.cpp +++ b/src/sfizz/modulations/ModKey.cpp @@ -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 {}; diff --git a/tests/LFOT.cpp b/tests/LFOT.cpp index 486a8cc6..77776baf 100644 --- a/tests/LFOT.cpp +++ b/tests/LFOT.cpp @@ -28,7 +28,7 @@ static bool computeLFO(DataPoints& dp, const fs::path& sfzPath, double sampleRat std::vector> 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]);