From c1306f8a71a4134bce8c67ed113914f01405fea9 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sun, 15 Nov 2020 13:23:59 +0100 Subject: [PATCH] lfo: decouple the wave generator and the phase generator --- demos/PlotLFO.cpp | 14 +++-- src/sfizz/BufferPool.h | 1 + src/sfizz/LFO.cpp | 132 +++++++++++++++++++++++------------------ src/sfizz/LFO.h | 14 +++-- src/sfizz/Voice.cpp | 2 +- tests/LFOT.cpp | 13 ++-- 6 files changed, 104 insertions(+), 72 deletions(-) diff --git a/demos/PlotLFO.cpp b/demos/PlotLFO.cpp index c55a192f..61ca0371 100644 --- a/demos/PlotLFO.cpp +++ b/demos/PlotLFO.cpp @@ -108,25 +108,29 @@ int main(int argc, char* argv[]) return 1; } + sfz::BufferPool bufferPool; + size_t numLfos = desc.size(); - std::vector lfos(numLfos); + std::vector> lfos(numLfos); for (size_t l = 0; l < numLfos; ++l) { - lfos[l].setSampleRate(sampleRate); - lfos[l].configure(&desc[l]); + sfz::LFO* lfo = new sfz::LFO(bufferPool); + lfos[l].reset(lfo); + lfo->setSampleRate(sampleRate); + lfo->configure(&desc[l]); } size_t numFrames = (size_t)std::ceil(sampleRate * duration); std::vector outputMemory(numLfos * numFrames); for (size_t l = 0; l < numLfos; ++l) { - lfos[l].start(0); + lfos[l]->start(0); } std::vector> lfoOutputs(numLfos); for (size_t l = 0; l < numLfos; ++l) { lfoOutputs[l] = absl::MakeSpan(&outputMemory[l * numFrames], numFrames); - lfos[l].process(lfoOutputs[l]); + lfos[l]->process(lfoOutputs[l]); } if (saveFlac) { diff --git a/src/sfizz/BufferPool.h b/src/sfizz/BufferPool.h index b9971fcf..e1e253e8 100644 --- a/src/sfizz/BufferPool.h +++ b/src/sfizz/BufferPool.h @@ -9,6 +9,7 @@ #include "Debug.h" #include "Buffer.h" #include "AudioBuffer.h" +#include "AudioSpan.h" #include #include #include diff --git a/src/sfizz/LFO.cpp b/src/sfizz/LFO.cpp index 06c823b5..ce2a2a38 100644 --- a/src/sfizz/LFO.cpp +++ b/src/sfizz/LFO.cpp @@ -6,6 +6,7 @@ #include "LFO.h" #include "LFODescription.h" +#include "BufferPool.h" #include "MathHelpers.h" #include "SIMDHelpers.h" #include "Config.h" @@ -16,6 +17,14 @@ namespace sfz { struct LFO::Impl { + explicit Impl(BufferPool& bufferPool) + : bufferPool_(bufferPool), + sampleRate_(config::defaultSampleRate), + desc_(&LFODescription::getDefault()) + { + } + + BufferPool& bufferPool_; float sampleRate_ = 0; // control @@ -26,13 +35,12 @@ struct LFO::Impl { float fadePosition_ = 0; std::array subPhases_ {{}}; std::array sampleHoldMem_ {{}}; + std::array sampleHoldState_ {{}}; }; -LFO::LFO() - : impl_(new Impl) +LFO::LFO(BufferPool& bufferPool) + : impl_(new Impl(bufferPool)) { - impl_->sampleRate_ = config::defaultSampleRate; - impl_->desc_ = &LFODescription::getDefault(); } LFO::~LFO() @@ -55,8 +63,9 @@ void LFO::start(unsigned triggerDelay) const LFODescription& desc = *impl.desc_; const float sampleRate = impl.sampleRate_; - impl.subPhases_.fill(desc.phase0); + impl.subPhases_.fill(0.0f); impl.sampleHoldMem_.fill(0.0f); + impl.sampleHoldState_.fill(0); const float delay = desc.delay; size_t delayFrames = (delay > 0) ? static_cast(std::ceil(sampleRate * delay)) : 0u; @@ -118,75 +127,57 @@ inline float LFO::eval(float phase) } template -void LFO::processWave(unsigned nth, absl::Span out) +void LFO::processWave(unsigned nth, absl::Span out, const float* phaseIn) { Impl& impl = *impl_; const LFODescription& desc = *impl.desc_; const LFODescription::Sub& sub = desc.sub[nth]; const size_t numFrames = out.size(); - const float samplePeriod = 1.0f / impl.sampleRate_; - const float baseFreq = desc.freq; const float offset = sub.offset; - const float ratio = sub.ratio; const float scale = sub.scale; - float phase = impl.subPhases_[nth]; for (size_t i = 0; i < numFrames; ++i) { + float phase = phaseIn[i]; out[i] += offset + scale * eval(phase); - - // TODO(jpc) lfoN_count: number of repetitions - - float incrPhase = ratio * samplePeriod * baseFreq; - phase += incrPhase; - int numWraps = (int)phase; - phase -= numWraps; } - - impl.subPhases_[nth] = phase; } template -void LFO::processSH(unsigned nth, absl::Span out) +void LFO::processSH(unsigned nth, absl::Span out, const float* phaseIn) { Impl& impl = *impl_; const LFODescription& desc = *impl.desc_; const LFODescription::Sub& sub = desc.sub[nth]; const size_t numFrames = out.size(); - const float samplePeriod = 1.0f / impl.sampleRate_; - const float baseFreq = desc.freq; const float offset = sub.offset; - const float ratio = sub.ratio; const float scale = sub.scale; float sampleHoldValue = impl.sampleHoldMem_[nth]; - float phase = impl.subPhases_[nth]; + int sampleHoldState = impl.sampleHoldState_[nth]; for (size_t i = 0; i < numFrames; ++i) { out[i] += offset + scale * sampleHoldValue; // TODO(jpc) lfoN_count: number of repetitions - float incrPhase = ratio * samplePeriod * baseFreq; + float phase = phaseIn[i]; + + int oldState = sampleHoldState; + sampleHoldState = phase > 0.5f; // value updates twice every period - bool updateValue = (int)(phase * 2.0) != (int)((phase + incrPhase) * 2.0); - - phase += incrPhase; - int numWraps = (int)phase; - phase -= numWraps; - - if (updateValue) { + if (sampleHoldState != oldState) { std::uniform_real_distribution dist(-1.0f, +1.0f); sampleHoldValue = dist(Random::randomGenerator); } } - impl.subPhases_[nth] = phase; impl.sampleHoldMem_[nth] = sampleHoldValue; + impl.sampleHoldState_[nth] = sampleHoldState; } -void LFO::processSteps(absl::Span out) +void LFO::processSteps(absl::Span out, const float* phaseIn) { unsigned nth = 0; Impl& impl = *impl_; @@ -201,26 +192,14 @@ void LFO::processSteps(absl::Span out) if (numSteps <= 0) return; - const float samplePeriod = 1.0f / impl.sampleRate_; - const float baseFreq = desc.freq; const float offset = sub.offset; - const float ratio = sub.ratio; const float scale = sub.scale; - float phase = impl.subPhases_[nth]; for (size_t i = 0; i < numFrames; ++i) { + float phase = phaseIn[i]; float step = steps[static_cast(phase * numSteps)]; out[i] += offset + scale * step; - - // TODO(jpc) lfoN_count: number of repetitions - - float incrPhase = ratio * samplePeriod * baseFreq; - phase += incrPhase; - int numWraps = (int)phase; - phase -= numWraps; } - - impl.subPhases_[nth] = phase; } void LFO::process(absl::Span out) @@ -244,39 +223,50 @@ void LFO::process(absl::Span out) if (countSubs < 1) return; + auto phasesTemp = impl.bufferPool_.getBuffer(numFrames); + if (!phasesTemp) { + ASSERTFALSE; + fill(out, 0.0f); + return; + } + + absl::Span phases = *phasesTemp; + if (desc.seq) { - processSteps(out); + generatePhase(0, phases); + processSteps(out, phases.data()); ++subno; } for (; subno < countSubs; ++subno) { + generatePhase(subno, phases); switch (desc.sub[subno].wave) { case LFOWave::Triangle: - processWave(subno, out); + processWave(subno, out, phases.data()); break; case LFOWave::Sine: - processWave(subno, out); + processWave(subno, out, phases.data()); break; case LFOWave::Pulse75: - processWave(subno, out); + processWave(subno, out, phases.data()); break; case LFOWave::Square: - processWave(subno, out); + processWave(subno, out, phases.data()); break; case LFOWave::Pulse25: - processWave(subno, out); + processWave(subno, out, phases.data()); break; case LFOWave::Pulse12_5: - processWave(subno, out); + processWave(subno, out, phases.data()); break; case LFOWave::Ramp: - processWave(subno, out); + processWave(subno, out, phases.data()); break; case LFOWave::Saw: - processWave(subno, out); + processWave(subno, out, phases.data()); break; case LFOWave::RandomSH: - processSH(subno, out); + processSH(subno, out, phases.data()); break; } } @@ -306,4 +296,32 @@ void LFO::processFadeIn(absl::Span out) impl.fadePosition_ = fadePosition; } +void LFO::generatePhase(unsigned nth, absl::Span phases) +{ + Impl& impl = *impl_; + 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 phaseOffset = desc.phase0; + const float ratio = sub.ratio; + float phase = impl.subPhases_[nth]; + + for (size_t i = 0, n = phases.size(); i < n; ++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; +} + } // namespace sfz diff --git a/src/sfizz/LFO.h b/src/sfizz/LFO.h index 7a43fd13..764cd65a 100644 --- a/src/sfizz/LFO.h +++ b/src/sfizz/LFO.h @@ -9,6 +9,7 @@ #include namespace sfz { +class BufferPool; enum class LFOWave : int; struct LFODescription; @@ -49,7 +50,7 @@ struct LFODescription; class LFO { public: - LFO(); + explicit LFO(BufferPool& bufferPool); ~LFO(); /** @@ -91,24 +92,29 @@ private: on wave type inside the frame loop. */ template - void processWave(unsigned nth, absl::Span out); + void processWave(unsigned nth, absl::Span out, const float* phaseIn); /** Process a sample-and-hold subwaveform, adding to the buffer. */ template - void processSH(unsigned nth, absl::Span out); + void processSH(unsigned nth, absl::Span out, const float* phaseIn); /** Process the step sequencer, adding to the buffer. */ - void processSteps(absl::Span out); + void processSteps(absl::Span out, const float* phaseIn); /** Process the fade in gain, and apply it to the buffer. */ void processFadeIn(absl::Span out); + /** + Generate the phase of the N-th generator + */ + void generatePhase(unsigned nth, absl::Span phases); + private: struct Impl; std::unique_ptr impl_; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 7940908e..45976aea 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -1478,7 +1478,7 @@ void Voice::setMaxLFOsPerVoice(size_t numLFOs) impl.lfos_.resize(numLFOs); for (size_t i = 0; i < numLFOs; ++i) { - auto lfo = absl::make_unique(); + auto lfo = absl::make_unique(impl.resources_.bufferPool); lfo->setSampleRate(impl.sampleRate_); impl.lfos_[i] = std::move(lfo); } diff --git a/tests/LFOT.cpp b/tests/LFOT.cpp index 9a7ac3ab..e2a625f4 100644 --- a/tests/LFOT.cpp +++ b/tests/LFOT.cpp @@ -13,6 +13,7 @@ static bool computeLFO(DataPoints& dp, const fs::path& sfzPath, double sampleRate, size_t numFrames) { sfz::Synth synth; + sfz::Resources& resources = synth.getResources(); if (!synth.loadSfzFile(sfzPath)) return false; @@ -22,23 +23,25 @@ static bool computeLFO(DataPoints& dp, const fs::path& sfzPath, double sampleRat const std::vector& desc = synth.getRegionView(0)->lfos; size_t numLfos = desc.size(); - std::vector lfos(numLfos); + std::vector> lfos(numLfos); for (size_t l = 0; l < numLfos; ++l) { - lfos[l].setSampleRate(sampleRate); - lfos[l].configure(&desc[l]); + sfz::LFO* lfo = new sfz::LFO(resources.bufferPool); + lfos[l].reset(lfo); + lfo->setSampleRate(sampleRate); + lfo->configure(&desc[l]); } std::vector outputMemory(numLfos * numFrames); for (size_t l = 0; l < numLfos; ++l) { - lfos[l].start(0); + lfos[l]->start(0); } std::vector> lfoOutputs(numLfos); for (size_t l = 0; l < numLfos; ++l) { lfoOutputs[l] = absl::MakeSpan(&outputMemory[l * numFrames], numFrames); - lfos[l].process(lfoOutputs[l]); + lfos[l]->process(lfoOutputs[l]); } dp.rows = numFrames;