diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 290c1248..f20db102 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -222,6 +222,7 @@ namespace Default constexpr int numLFOSubs { 2 }; constexpr int numLFOSteps { 8 }; constexpr Range lfoFreqRange { 0.0, 100.0 }; + constexpr Range lfoBeatsRange { 0.0, 1000.0 }; constexpr Range lfoPhaseRange { 0.0, 1.0 }; constexpr Range lfoDelayRange { 0.0, 30.0 }; constexpr Range lfoFadeRange { 0.0, 30.0 }; diff --git a/src/sfizz/LFO.cpp b/src/sfizz/LFO.cpp index ce2a2a38..6dd5c168 100644 --- a/src/sfizz/LFO.cpp +++ b/src/sfizz/LFO.cpp @@ -6,6 +6,7 @@ #include "LFO.h" #include "LFODescription.h" +#include "BeatClock.h" #include "BufferPool.h" #include "MathHelpers.h" #include "SIMDHelpers.h" @@ -17,14 +18,16 @@ namespace sfz { struct LFO::Impl { - explicit Impl(BufferPool& bufferPool) + explicit Impl(BufferPool& bufferPool, BeatClock* beatClock) : bufferPool_(bufferPool), + beatClock_(beatClock), sampleRate_(config::defaultSampleRate), desc_(&LFODescription::getDefault()) { } BufferPool& bufferPool_; + BeatClock* beatClock_ = nullptr; float sampleRate_ = 0; // control @@ -38,8 +41,8 @@ struct LFO::Impl { std::array sampleHoldState_ {{}}; }; -LFO::LFO(BufferPool& bufferPool) - : impl_(new Impl(bufferPool)) +LFO::LFO(BufferPool& bufferPool, BeatClock* beatClock) + : impl_(new Impl(bufferPool, beatClock)) { } @@ -299,26 +302,46 @@ void LFO::processFadeIn(absl::Span out) void LFO::generatePhase(unsigned nth, absl::Span phases) { Impl& impl = *impl_; + BeatClock* beatClock = impl.beatClock_; const LFODescription& desc = *impl.desc_; const LFODescription::Sub& sub = desc.sub[nth]; const float samplePeriod = 1.0f / impl.sampleRate_; const float baseFreq = desc.freq; + const float beats = desc.beats; const float phaseOffset = desc.phase0; const float ratio = sub.ratio; float phase = impl.subPhases_[nth]; + const size_t numFrames = phases.size(); - for (size_t i = 0, n = phases.size(); i < n; ++i) { - float withOffset = phase + phaseOffset; - withOffset -= (int)withOffset; + if (beatClock && beatClock->isPlaying() && beats > 0) { + // generate using the beat clock + float beatRatio = (ratio > 0) ? (1.0f / ratio) : 0.0f; + beatClock->calculatePhase(beats * beatRatio, phases.data()); - phases[i] = withOffset; + for (size_t i = 0; i < numFrames; ++i) { + float withOffset = phase + phaseOffset; + withOffset -= (int)withOffset; - // TODO(jpc) lfoN_count: number of repetitions + phases[i] = withOffset; - float incr = ratio * samplePeriod * baseFreq; - phase += incr; - int numWraps = (int)phase; - phase -= numWraps; + // TODO(jpc) lfoN_count: number of repetitions + } + } + else { + // generate using the frequency + for (size_t i = 0; i < numFrames; ++i) { + float withOffset = phase + phaseOffset; + withOffset -= (int)withOffset; + + phases[i] = withOffset; + + // TODO(jpc) lfoN_count: number of repetitions + + float incr = ratio * samplePeriod * baseFreq; + phase += incr; + int numWraps = (int)phase; + phase -= numWraps; + } } impl.subPhases_[nth] = phase; diff --git a/src/sfizz/LFO.h b/src/sfizz/LFO.h index 764cd65a..1a908932 100644 --- a/src/sfizz/LFO.h +++ b/src/sfizz/LFO.h @@ -10,6 +10,7 @@ namespace sfz { class BufferPool; +class BeatClock; enum class LFOWave : int; struct LFODescription; @@ -50,7 +51,9 @@ struct LFODescription; class LFO { public: - explicit LFO(BufferPool& bufferPool); + explicit LFO( + BufferPool& bufferPool, + BeatClock* beatClock = nullptr); ~LFO(); /** diff --git a/src/sfizz/LFODescription.h b/src/sfizz/LFODescription.h index 8f5344c9..2628430f 100644 --- a/src/sfizz/LFODescription.h +++ b/src/sfizz/LFODescription.h @@ -28,6 +28,7 @@ struct LFODescription { ~LFODescription(); static const LFODescription& getDefault(); float freq = 0; // lfoN_freq + float beats = 0; // lfoN_beats float phase0 = 0; // lfoN_phase float delay = 0; // lfoN_delay float fade = 0; // lfoN_fade diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index f131febf..88813c7e 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -866,6 +866,16 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) setValueFromOpcode(opcode, lfos[lfoNumber - 1].freq, Default::lfoFreqRange); } break; + case hash("lfo&_beats"): + { + const auto lfoNumber = opcode.parameters.front(); + if (lfoNumber == 0) + return false; + if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) + return false; + setValueFromOpcode(opcode, lfos[lfoNumber - 1].beats, Default::lfoBeatsRange); + } + break; case hash("lfo&_phase"): { const auto lfoNumber = opcode.parameters.front(); diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 45976aea..e3181121 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -1475,10 +1475,12 @@ void Voice::setMaxEQsPerVoice(size_t numFilters) void Voice::setMaxLFOsPerVoice(size_t numLFOs) { Impl& impl = *impl_; + Resources& resources = impl.resources_; + impl.lfos_.resize(numLFOs); for (size_t i = 0; i < numLFOs; ++i) { - auto lfo = absl::make_unique(impl.resources_.bufferPool); + auto lfo = absl::make_unique(resources.bufferPool, &resources.beatClock); lfo->setSampleRate(impl.sampleRate_); impl.lfos_[i] = std::move(lfo); }