diff --git a/common.mk b/common.mk index 99c0881c..4f3289fa 100644 --- a/common.mk +++ b/common.mk @@ -48,6 +48,7 @@ SFIZZ_CXX_FLAGS = $(SFIZZ_C_FLAGS) SFIZZ_SOURCES = \ src/sfizz/ADSREnvelope.cpp \ src/sfizz/AudioReader.cpp \ + src/sfizz/BeatClock.cpp \ src/sfizz/Curve.cpp \ src/sfizz/effects/Apan.cpp \ src/sfizz/Effects.cpp \ @@ -90,6 +91,7 @@ SFIZZ_SOURCES = \ src/sfizz/LFO.cpp \ src/sfizz/LFODescription.cpp \ src/sfizz/Messaging.cpp \ + src/sfizz/Metronome.cpp \ src/sfizz/MidiState.cpp \ src/sfizz/OpcodeCleanup.cpp \ src/sfizz/Opcode.cpp \ diff --git a/demos/PlotLFO.cpp b/demos/PlotLFO.cpp index c55a192f..6f77f2f5 100644 --- a/demos/PlotLFO.cpp +++ b/demos/PlotLFO.cpp @@ -108,25 +108,30 @@ 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]); + const NumericId id { static_cast(l) }; + sfz::LFO* lfo = new sfz::LFO(id, 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/lv2/sfizz.c b/lv2/sfizz.c index 0ebe5313..57d80846 100644 --- a/lv2/sfizz.c +++ b/lv2/sfizz.c @@ -175,11 +175,11 @@ typedef struct // Timing data int bar; - float bar_beat; + double bar_beat; int beats_per_bar; int beat_unit; - float bpm_tempo; - float speed; + double bpm_tempo; + double speed; // Paths char bundle_path[MAX_BUNDLE_PATH_SIZE]; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6e68d803..ca0ebed0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,6 +21,7 @@ set (SFIZZ_HEADERS sfizz/AudioBuffer.h sfizz/AudioReader.h sfizz/AudioSpan.h + sfizz/BeatClock.h sfizz/Buffer.h sfizz/BufferPool.h sfizz/CCMap.h @@ -79,6 +80,7 @@ set (SFIZZ_HEADERS sfizz/LFO.h sfizz/LFODescription.h sfizz/MathHelpers.h + sfizz/Metronome.h sfizz/MidiState.h sfizz/ModifierHelpers.h sfizz/OnePoleFilter.h @@ -149,6 +151,8 @@ set (SFIZZ_SOURCES sfizz/PowerFollower.cpp sfizz/FlexEGDescription.cpp sfizz/FlexEnvelope.cpp + sfizz/BeatClock.cpp + sfizz/Metronome.cpp sfizz/SynthMessaging.cpp sfizz/modulations/ModId.cpp sfizz/modulations/ModKey.cpp diff --git a/src/sfizz.h b/src/sfizz.h index b2e74c62..5deaea5f 100644 --- a/src/sfizz.h +++ b/src/sfizz.h @@ -392,7 +392,7 @@ SFIZZ_EXPORTED_API void sfizz_send_time_signature(sfizz_synth_t* synth, int dela * @param bar The current bar. * @param bar_beat The fractional position of the current beat within the bar. */ -SFIZZ_EXPORTED_API void sfizz_send_time_position(sfizz_synth_t* synth, int delay, int bar, float bar_beat); +SFIZZ_EXPORTED_API void sfizz_send_time_position(sfizz_synth_t* synth, int delay, int bar, double bar_beat); /** * @brief Send the playback state. diff --git a/src/sfizz.hpp b/src/sfizz.hpp index 426795e4..e6b34daf 100644 --- a/src/sfizz.hpp +++ b/src/sfizz.hpp @@ -359,7 +359,7 @@ public: * @param bar The current bar. * @param barBeat The fractional position of the current beat within the bar. */ - void timePosition(int delay, int bar, float barBeat); + void timePosition(int delay, int bar, double barBeat); /** * @brief Send the playback state. diff --git a/src/sfizz/BeatClock.cpp b/src/sfizz/BeatClock.cpp new file mode 100644 index 00000000..70ef5856 --- /dev/null +++ b/src/sfizz/BeatClock.cpp @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "BeatClock.h" +#include "SIMDHelpers.h" +#include "Config.h" +#include "Debug.h" +#include +#include + +namespace sfz { + +bool TimeSignature::operator==(const TimeSignature& other) const +{ + return beatsPerBar == other.beatsPerBar && beatUnit == other.beatUnit; +} + +bool TimeSignature::operator!=(const TimeSignature& other) const +{ + return !operator==(other); +} + +/// +BBT BBT::toSignature(TimeSignature oldSig, TimeSignature newSig) const +{ + double beatsInOldSig = toBeats(oldSig); + double beatsInNewSig = beatsInOldSig * newSig.beatUnit / oldSig.beatUnit; + return BBT::fromBeats(newSig, beatsInNewSig); +} + +double BBT::toBeats(TimeSignature sig) const +{ + return beat + bar * sig.beatsPerBar; +} + +BBT BBT::fromBeats(TimeSignature sig, double beats) +{ + int newBar = static_cast(beats / sig.beatsPerBar); + double newBeat = beats - newBar * sig.beatsPerBar; + return BBT(newBar, newBeat); +} + +double BBT::toBars(TimeSignature sig) const +{ + return bar + beat / sig.beatsPerBar; +} + +/// +constexpr int BeatClock::resolution; + +auto BeatClock::quantize(double beats) -> qbeats_t +{ + double d = beats * (1 << resolution); + d = std::copysign(0.5 + std::fabs(d), d); + return static_cast(d); +} + +template +T BeatClock::dequantize(qbeats_t qbeats) +{ + return qbeats / static_cast(1 << resolution); +} + +/// +void BeatClock::clear() +{ + beatsPerSecond_ = 2.0; + timeSig_ = { 4, 4 }; + isPlaying_ = false; + + lastHostPos_ = { 0, 0 }; + lastClientPos_ = { 0, 0 }; +} + +void BeatClock::beginCycle(unsigned numFrames) +{ + currentCycleFrames_ = numFrames; + currentCycleFill_ = 0; + currentCycleStartPos_ = lastClientPos_; +} + +void BeatClock::endCycle() +{ + fillBufferUpTo(currentCycleFrames_); +} + +void BeatClock::setSampleRate(double sampleRate) +{ + samplePeriod_ = 1.0 / sampleRate; +} + +void BeatClock::setSamplesPerBlock(unsigned samplesPerBlock) +{ + runningBeatNumber_.resize(samplesPerBlock); + runningBeatPosition_.resize(samplesPerBlock); + runningBeatsPerBar_.resize(samplesPerBlock); +} + +void BeatClock::setTempo(unsigned delay, double secondsPerBeat) +{ + fillBufferUpTo(delay); + + beatsPerSecond_ = 1.0 / secondsPerBeat; +} + +void BeatClock::setTimeSignature(unsigned delay, TimeSignature newSig) +{ + fillBufferUpTo(delay); + + if (!newSig.valid()) { + CHECKFALSE; + return; + } + + TimeSignature oldSig = timeSig_; + if (oldSig == newSig) + return; + + timeSig_ = newSig; + + // convert time to new signature + lastHostPos_ = lastHostPos_.toSignature(oldSig, newSig); + lastClientPos_ = lastClientPos_.toSignature(oldSig, newSig); +} + +void BeatClock::setTimePosition(unsigned delay, BBT newPos) +{ + fillBufferUpTo(delay); + + lastHostPos_ = newPos; + + // apply host position in the next frame + mustApplyHostPos_ = true; +} + +void BeatClock::setPlaying(unsigned delay, bool playing) +{ + fillBufferUpTo(delay); + + isPlaying_ = playing; +} + +absl::Span BeatClock::getRunningBeatNumber() +{ + fillBufferUpTo(currentCycleFrames_); + + return absl::MakeConstSpan(runningBeatNumber_.data(), currentCycleFrames_); +} + +absl::Span BeatClock::getRunningBeatPosition() +{ + fillBufferUpTo(currentCycleFrames_); + + return absl::MakeConstSpan(runningBeatPosition_.data(), currentCycleFrames_); +} + +absl::Span BeatClock::getRunningBeatsPerBar() +{ + fillBufferUpTo(currentCycleFrames_); + + return absl::MakeConstSpan(runningBeatsPerBar_.data(), currentCycleFrames_); +} + +void BeatClock::fillBufferUpTo(unsigned delay) +{ + int *beatNumberData = runningBeatNumber_.data(); + float *beatNumberPosition = runningBeatPosition_.data(); + int *beatsPerBarData = runningBeatsPerBar_.data(); + unsigned fillIdx = currentCycleFill_; + + const TimeSignature sig = timeSig_; + for (unsigned i = fillIdx; i < delay; ++i) + beatsPerBarData[i] = sig.beatsPerBar; + + if (!isPlaying_) { + if (fillIdx < delay) { + fill(absl::MakeSpan(&beatNumberData[fillIdx], delay - fillIdx), 0); + fill(absl::MakeSpan(&beatNumberPosition[fillIdx], delay - fillIdx), 0.0f); + } + currentCycleFill_ = fillIdx; + return; + } + + BBT clientPos = lastClientPos_; + const double beatsPerFrame = beatsPerSecond_ * samplePeriod_; + + const BBT hostPos = lastHostPos_; + bool mustApplyHostPos = mustApplyHostPos_; + + for (; fillIdx < delay; ++fillIdx) { + clientPos = BBT::fromBeats(sig, clientPos.toBeats(sig) + beatsPerFrame); + clientPos = mustApplyHostPos ? hostPos : clientPos; + mustApplyHostPos = false; + + // quantization to nearest for prevention of rounding errors + double beats = clientPos.toBeats(sig); + beatNumberData[fillIdx] = dequantize(quantize(beats)); + beatNumberPosition[fillIdx] = static_cast(beats); + } + + currentCycleFill_ = fillIdx; + lastClientPos_ = clientPos; + mustApplyHostPos_ = mustApplyHostPos; +} + +void BeatClock::calculatePhase(float beatPeriod, float* phaseOut) +{ + const unsigned numFrames = currentCycleFrames_; + + if (beatPeriod <= 0.0f) { + fill(absl::MakeSpan(phaseOut, numFrames), 0.0f); + return; + } + + const float invBeatPeriod = 1.0f / beatPeriod; + const float* beatPositionData = getRunningBeatPosition().data(); + + for (unsigned i = 0; i < numFrames; ++i) { + float beatPosition = std::max(0.0f, beatPositionData[i]); + float phase = beatPosition * invBeatPeriod; + phase -= static_cast(phase); + phaseOut[i] = phase; + } +} + +void BeatClock::calculatePhaseModulated(const float* beatPeriodData, float* phaseOut) +{ + const unsigned numFrames = currentCycleFrames_; + const float* beatPositionData = getRunningBeatPosition().data(); + + for (unsigned i = 0; i < numFrames; ++i) { + float beatPeriod = beatPeriodData[i]; + float beatPosition = std::max(0.0f, beatPositionData[i]); + float phase = beatPosition / beatPeriod; + phase -= static_cast(phase); + phaseOut[i] = (beatPeriod > 0.0f) ? phase : 0.0f; + } +} + +} // namespace sfz + +std::ostream& operator<<(std::ostream& os, const sfz::BBT& pos) +{ + return os << pos.bar << ':' << std::fixed << pos.beat; +} + +std::ostream& operator<<(std::ostream& os, const sfz::TimeSignature& sig) +{ + return os << sig.beatsPerBar << '/' << sig.beatUnit; +} diff --git a/src/sfizz/BeatClock.h b/src/sfizz/BeatClock.h new file mode 100644 index 00000000..880c2134 --- /dev/null +++ b/src/sfizz/BeatClock.h @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once +#include "Buffer.h" +#include +#include +#include + +namespace sfz { + +/** + * @brief Musical time signature + */ +struct TimeSignature { + TimeSignature() {} + TimeSignature(int beatsPerBar, int beatUnit) : beatsPerBar(beatsPerBar), beatUnit(beatUnit) {} + + /** + * @brief Check the signature validity. + * Valid signatures have a strictly positive numerator and denominator. + */ + bool valid() const { return beatsPerBar > 0 && beatUnit > 0; } + + bool operator==(const TimeSignature& other) const; + bool operator!=(const TimeSignature& other) const; + + /** + * @brief Time signature numerator, indicating the number of beats in a bar + */ + int beatsPerBar = 0; + /** + * @brief Time signature denominator, indicating the type of note (4=quarter) + */ + int beatUnit = 0; +}; + +/** + * @brief Musical time in BBT form + */ +struct BBT { + BBT() {} + BBT(int bar, double beat) : bar(bar), beat(beat) {} + + /** + * @brief Convert the time to a different signature. + */ + BBT toSignature(TimeSignature oldSig, TimeSignature newSig) const; + /** + * @brief Convert the time to a fractional quantity in beats. + */ + double toBeats(TimeSignature sig) const; + /** + * @brief Convert the time to a fractional quantity in bars. + */ + double toBars(TimeSignature sig) const; + /** + * @brief Convert the fractional quantity in beats to musical time. + */ + static BBT fromBeats(TimeSignature sig, double beats); + + /** + * @brief Bar number + */ + int bar = 0; + /** + * @brief Beat and tick, stored in the integral and fractional parts + */ + double beat = 0; +}; + +class BeatClock { +public: + /** + * @brief Set the sample rate. + */ + void setSampleRate(double sampleRate); + /** + * @brief Set the block size. + */ + void setSamplesPerBlock(unsigned samplesPerBlock); + /** + * @brief Reinitialize the current state. + */ + void clear(); + /** + * @brief Start a new cycle of clock processing. + */ + void beginCycle(unsigned numFrames); + /** + * @brief End the current cycle of clock processing. + */ + void endCycle(); + /** + * @brief Set the tempo. + */ + void setTempo(unsigned delay, double secondsPerBeat); + /** + * @brief Set the time signature. + */ + void setTimeSignature(unsigned delay, TimeSignature newSig); + /** + * @brief Set the time position. + */ + void setTimePosition(unsigned delay, BBT newPos); + /** + * @brief Set whether the clock is ticking or stopped. + */ + void setPlaying(unsigned delay, bool playing); + /** + * Check whether the clock is currently ticking. + */ + bool isPlaying() const noexcept { return isPlaying_; } + /** + * @brief Get the beat number for each frame of the current cycle. + * + * This signal is quantized to a fixed resolution, such that it never + * suffers 1-off errors due to imprecision in the host time position. + */ + absl::Span getRunningBeatNumber(); + /** + * @brief Get the beat position for each frame of the current cycle. + * + * This is a fractional equivalent of the beat number, however the beat + * boundaries can be traversed erratically due to approximation errors. + * If you need to perform work on exact beat transitions, prefer + * `getRunningBeatNumber` instead. + */ + absl::Span getRunningBeatPosition(); + /** + * @brief Get the time signature numerator for each frame of the current cycle. + */ + absl::Span getRunningBeatsPerBar(); + /** + * @brief Create a normalized phase signal for LFO which completes a + * period every N-th beat. + */ + void calculatePhase(float beatPeriod, float* phaseOut); + /** + * @brief Create a normalized phase signal for LFO which completes a + * period every N-th beat, where N can vary over time. + */ + void calculatePhaseModulated(const float* beatPeriodData, float* phaseOut); + +private: + void fillBufferUpTo(unsigned delay); + +private: + double samplePeriod_ { 1.0 / config::defaultSampleRate }; + + // quantization + typedef int64_t qbeats_t; + static constexpr int resolution = 16; // bits + static qbeats_t quantize(int beats) { return beats * (1 << resolution); } + static qbeats_t quantize(double beats); + template static T dequantize(qbeats_t qbeats); + + // status of current cycle + unsigned currentCycleFrames_ = 0; + unsigned currentCycleFill_ = 0; + BBT currentCycleStartPos_; + + // musical time information from host + double beatsPerSecond_ = 2.0; + TimeSignature timeSig_ { 4, 4 }; + bool isPlaying_ = false; + + // last time position received from host + BBT lastHostPos_; + bool mustApplyHostPos_ = false; + + // plugin-side counter + BBT lastClientPos_; + + Buffer runningBeatNumber_ { config::defaultSamplesPerBlock }; + Buffer runningBeatPosition_ { config::defaultSamplesPerBlock }; + Buffer runningBeatsPerBar_ { config::defaultSamplesPerBlock }; +}; + +} // namespace sfz + +std::ostream& operator<<(std::ostream& os, const sfz::BBT& pos); +std::ostream& operator<<(std::ostream& os, const sfz::TimeSignature& sig); diff --git a/src/sfizz/BufferPool.h b/src/sfizz/BufferPool.h index eea96b56..51d40794 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/Defaults.h b/src/sfizz/Defaults.h index 7ddfcf37..2ebca118 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -222,6 +222,9 @@ namespace Default constexpr int numLFOSubs { 2 }; constexpr int numLFOSteps { 8 }; constexpr Range lfoFreqRange { 0.0, 100.0 }; + constexpr Range lfoFreqModRange { -100.0, 100.0 }; + constexpr Range lfoBeatsRange { 0.0, 1000.0 }; + constexpr Range lfoBeatsModRange { -1000.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 06c823b5..547e296c 100644 --- a/src/sfizz/LFO.cpp +++ b/src/sfizz/LFO.cpp @@ -6,9 +6,14 @@ #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 "modulations/ModKey.h" +#include "modulations/ModId.h" #include #include #include @@ -16,6 +21,20 @@ namespace sfz { struct LFO::Impl { + explicit Impl(NumericId id, BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix) + : id_(id), + bufferPool_(bufferPool), + beatClock_(beatClock), + modMatrix_(modMatrix), + sampleRate_(config::defaultSampleRate), + desc_(&LFODescription::getDefault()) + { + } + + NumericId id_; + BufferPool& bufferPool_; + BeatClock* beatClock_ = nullptr; + ModMatrix* modMatrix_ = nullptr; float sampleRate_ = 0; // control @@ -26,19 +45,23 @@ struct LFO::Impl { float fadePosition_ = 0; std::array subPhases_ {{}}; std::array sampleHoldMem_ {{}}; + std::array sampleHoldState_ {{}}; }; -LFO::LFO() - : impl_(new Impl) +LFO::LFO(NumericId id, BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix) + : impl_(new Impl(id, bufferPool, beatClock, modMatrix)) { - impl_->sampleRate_ = config::defaultSampleRate; - impl_->desc_ = &LFODescription::getDefault(); } LFO::~LFO() { } +NumericId LFO::getId() const noexcept +{ + return impl_->id_; +} + void LFO::setSampleRate(double sampleRate) { impl_->sampleRate_ = sampleRate; @@ -55,8 +78,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 +142,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,29 +207,17 @@ 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) +void LFO::process(absl::Span out, NumericId regionId) { Impl& impl = *impl_; const LFODescription& desc = *impl.desc_; @@ -244,39 +238,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, regionId); + processSteps(out, phases.data()); ++subno; } for (; subno < countSubs; ++subno) { + generatePhase(subno, phases, regionId); 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 +311,87 @@ void LFO::processFadeIn(absl::Span out) impl.fadePosition_ = fadePosition; } +void LFO::generatePhase(unsigned nth, absl::Span phases, NumericId regionId) +{ + Impl& impl = *impl_; + BufferPool& bufferPool = impl.bufferPool_; + BeatClock* beatClock = impl.beatClock_; + ModMatrix* modMatrix = impl.modMatrix_; + const NumericId id { impl.id_ }; + 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(); + + // TODO(jpc) lfoN_count: number of repetitions + + // modulations + const float* beatsMod = nullptr; + const float* freqMod = nullptr; + if (modMatrix && id && regionId) { + // Note(jpc) we might switch between beats and frequency, if host + // switches play state on and off; continually generate both. + ModKey beatsKey = ModKey::createNXYZ(ModId::LFOBeats, regionId, id.number()); + ModKey freqKey = ModKey::createNXYZ(ModId::LFOFrequency, regionId, id.number()); + beatsMod = modMatrix->getModulationByKey(beatsKey); + freqMod = modMatrix->getModulationByKey(freqKey); + } + + if (beatClock && 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()); + else { + auto temp = bufferPool.getBuffer(numFrames); + if (!temp) { + ASSERTFALSE; + 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()); + } + } + } + else { + // generate using the frequency + if (!freqMod) { + for (size_t i = 0; i < numFrames; ++i) { + phases[i] = phase; + float incr = ratio * samplePeriod * baseFreq; + phase += incr; + int numWraps = (int)phase; + phase -= numWraps; + } + } + else { + for (size_t i = 0; i < numFrames; ++i) { + phases[i] = phase; + float incr = ratio * samplePeriod * (baseFreq + freqMod[i]); + phase += incr; + int numWraps = (int)phase; + phase -= numWraps; + } + } + } + + // apply phase offsets + for (size_t i = 0; i < numFrames; ++i) { + float withOffset = phases[i] + phaseOffset; + withOffset -= (int)withOffset; + phases[i] = withOffset; + } + + impl.subPhases_[nth] = phase; +} + } // namespace sfz diff --git a/src/sfizz/LFO.h b/src/sfizz/LFO.h index 7a43fd13..caac1c27 100644 --- a/src/sfizz/LFO.h +++ b/src/sfizz/LFO.h @@ -5,10 +5,15 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #pragma once +#include "utility/NumericId.h" #include #include namespace sfz { +class BufferPool; +class BeatClock; +class ModMatrix; +struct Region; enum class LFOWave : int; struct LFODescription; @@ -49,9 +54,15 @@ struct LFODescription; class LFO { public: - LFO(); + explicit LFO( + NumericId id, + BufferPool& bufferPool, + BeatClock* beatClock = nullptr, + ModMatrix* modMatrix = nullptr); ~LFO(); + NumericId getId() const noexcept; + /** Sets the sample rate. */ @@ -74,7 +85,7 @@ public: TODO(jpc) frequency modulations */ - void process(absl::Span out); + void process(absl::Span out, NumericId regionId = {}); private: /** @@ -91,24 +102,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, NumericId regionId); + private: struct Impl; std::unique_ptr impl_; 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/Metronome.cpp b/src/sfizz/Metronome.cpp new file mode 100644 index 00000000..5b0b1ff0 --- /dev/null +++ b/src/sfizz/Metronome.cpp @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "Metronome.h" +#include "Config.h" + +namespace sfz { + +Metronome::Metronome() +{ + fGain = 0.5f; + init(config::defaultSampleRate); +} + +void Metronome::init(float sampleRate) +{ + fConst0 = std::min(192000.0f, std::max(1.0f, float(sampleRate))); + fConst1 = std::cos((2764.60156f / fConst0)); + fConst2 = std::sqrt(std::max(0.0f, ((fConst1 + 1.0f) / (1.0f - fConst1)))); + fConst3 = (1.0f / fConst2); + fConst4 = std::cos((5529.20312f / fConst0)); + fConst5 = std::sqrt(std::max(0.0f, ((fConst4 + 1.0f) / (1.0f - fConst4)))); + fConst6 = (1.0f / fConst5); + fConst7 = std::max(1.0f, (0.00499999989f * fConst0)); + fConst8 = (1.0f / fConst7); + fConst9 = (1.0f / std::max(1.0f, (0.100000001f * fConst0))); + clear(); +} + +void Metronome::clear() +{ + for (int l0 = 0; (l0 < 2); l0 = (l0 + 1)) { + iVec0[l0] = 0; + } + for (int l1 = 0; (l1 < 2); l1 = (l1 + 1)) { + iVec1[l1] = 0; + } + for (int l2 = 0; (l2 < 2); l2 = (l2 + 1)) { + iVec2[l2] = 0; + } + for (int l3 = 0; (l3 < 2); l3 = (l3 + 1)) { + iRec0[l3] = 0; + } + for (int l4 = 0; (l4 < 2); l4 = (l4 + 1)) { + fVec3[l4] = 0.0f; + } + for (int l5 = 0; (l5 < 2); l5 = (l5 + 1)) { + fRec1[l5] = 0.0f; + } + for (int l6 = 0; (l6 < 2); l6 = (l6 + 1)) { + fRec2[l6] = 0.0f; + } + for (int l7 = 0; (l7 < 2); l7 = (l7 + 1)) { + fVec4[l7] = 0.0f; + } + for (int l8 = 0; (l8 < 2); l8 = (l8 + 1)) { + fRec3[l8] = 0.0f; + } + for (int l9 = 0; (l9 < 2); l9 = (l9 + 1)) { + fRec4[l9] = 0.0f; + } + for (int l10 = 0; (l10 < 2); l10 = (l10 + 1)) { + iRec5[l10] = 0; + } +} + +void Metronome::processAdding(const int* beats, const int* beatsPerBar, float* outputL, float* outputR, int numFrames) +{ + float fSlow0 = float(fGain); + for (int i = 0; (i < numFrames); i = (i + 1)) { + int iTemp0 = int(beats[i]); + iVec0[0] = iTemp0; + iVec1[0] = 1; + int iTemp1 = ((iTemp0 - iVec0[1]) > 0); + iVec2[0] = iTemp1; + iRec0[0] = (iTemp1 ? ((iTemp0 % int(beatsPerBar[i])) == 0) : iRec0[1]); + fVec3[0] = fConst2; + float fTemp2 = float((1 - iVec1[1])); + float fTemp3 = (fConst3 * (fRec2[1] * (fTemp2 + fVec3[1]))); + float fTemp4 = (fConst1 * (fTemp3 + fRec1[1])); + fRec1[0] = (fTemp4 + (fTemp2 + fTemp3)); + fRec2[0] = (fTemp4 - fRec1[1]); + fVec4[0] = fConst5; + float fTemp5 = (fConst6 * (fRec4[1] * (fTemp2 + fVec4[1]))); + float fTemp6 = (fConst4 * (fTemp5 + fRec3[1])); + fRec3[0] = (fTemp6 + (fTemp2 + fTemp5)); + fRec4[0] = (fTemp6 - fRec3[1]); + iRec5[0] = (((iRec5[1] + (iRec5[1] > 0)) * (iTemp1 <= iVec2[1])) + (iTemp1 > iVec2[1])); + float fTemp7 = float(iRec5[0]); + float fTemp8 = (fSlow0 * ((iRec0[0] ? (0.0f - (fConst5 * fRec4[0])) : (0.0f - (fConst2 * fRec2[0]))) * std::max(0.0f, std::min((fConst8 * fTemp7), ((fConst9 * (fConst7 - fTemp7)) + 1.0f))))); + outputL[i] += float(fTemp8); + outputR[i] += float(fTemp8); + iVec0[1] = iVec0[0]; + iVec1[1] = iVec1[0]; + iVec2[1] = iVec2[0]; + iRec0[1] = iRec0[0]; + fVec3[1] = fVec3[0]; + fRec1[1] = fRec1[0]; + fRec2[1] = fRec2[0]; + fVec4[1] = fVec4[0]; + fRec3[1] = fRec3[0]; + fRec4[1] = fRec4[0]; + iRec5[1] = iRec5[0]; + } +} + +} // namespace sfz diff --git a/src/sfizz/Metronome.h b/src/sfizz/Metronome.h new file mode 100644 index 00000000..55be83bc --- /dev/null +++ b/src/sfizz/Metronome.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once +#include +#include + +namespace sfz { + +class Metronome { +public: + Metronome(); + void init(float sampleRate); + void clear(); + void processAdding(const int* beats, const int* beatsPerBar, float* outputL, float* outputR, int numFrames); + void setGain(float gain) { fGain = gain; } + +private: + float fGain; + int iVec0[2]; + int iVec1[2]; + int iVec2[2]; + int iRec0[2]; + float fConst0; + float fConst1; + float fConst2; + float fVec3[2]; + float fConst3; + float fRec1[2]; + float fRec2[2]; + float fConst4; + float fConst5; + float fVec4[2]; + float fConst6; + float fRec3[2]; + float fRec4[2]; + float fConst7; + float fConst8; + int iRec5[2]; + float fConst9; + + /* + import("stdfaust.lib"); + + process(beats, beatsPerBar) = tone : *(envelope) <: (_, _) with { + gain = hslider("[1] Gain", 0.5, 0.0, 1.0, 0.001); + beatNumber = int(beats); + beatIncrement = beatNumber-beatNumber'; + tone = (os.oscws(440.0), os.oscws(880.0)) : select2(toneSelect); + toneSelect = x letrec { 'x = ba.if(beatIncrement>0, (beatNumber%int(beatsPerBar))==0, x); }; + envelope = (beatIncrement>0) : en.ar(5e-3, 100e-3) : *(gain); + }; + */ +}; + +} // namespace sfz diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 816211ba..feaa1e43 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -866,6 +866,36 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) setValueFromOpcode(opcode, lfos[lfoNumber - 1].freq, Default::lfoFreqRange); } break; + case_any_ccN("lfo&_freq"): + { + const auto lfoNumber = opcode.parameters.front(); + if (lfoNumber == 0) + return false; + if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) + return false; + processGenericCc(opcode, Default::lfoFreqModRange, ModKey::createNXYZ(ModId::LFOFrequency, id, lfoNumber - 1)); + } + 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_any_ccN("lfo&_beats"): + { + const auto lfoNumber = opcode.parameters.front(); + if (lfoNumber == 0) + return false; + if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) + return false; + processGenericCc(opcode, Default::lfoBeatsModRange, ModKey::createNXYZ(ModId::LFOBeats, id, lfoNumber - 1)); + } + break; case hash("lfo&_phase"): { const auto lfoNumber = opcode.parameters.front(); diff --git a/src/sfizz/Resources.h b/src/sfizz/Resources.h index 577ff756..27de6f1c 100644 --- a/src/sfizz/Resources.h +++ b/src/sfizz/Resources.h @@ -13,6 +13,8 @@ #include "Wavetables.h" #include "Curve.h" #include "Tuning.h" +#include "BeatClock.h" +#include "Metronome.h" #include "modulations/ModMatrix.h" #include "absl/types/optional.h" @@ -32,11 +34,15 @@ struct Resources Tuning tuning; absl::optional stretch; ModMatrix modMatrix; + BeatClock beatClock; + Metronome metronome; void setSampleRate(float samplerate) { midiState.setSampleRate(samplerate); modMatrix.setSampleRate(samplerate); + beatClock.setSampleRate(samplerate); + metronome.init(samplerate); } void setSamplesPerBlock(int samplesPerBlock) @@ -44,6 +50,7 @@ struct Resources bufferPool.setBufferSize(samplesPerBlock); midiState.setSamplesPerBlock(samplesPerBlock); modMatrix.setSamplesPerBlock(samplesPerBlock); + beatClock.setSamplesPerBlock(samplesPerBlock); } void clear() @@ -54,6 +61,8 @@ struct Resources logger.clear(); midiState.reset(); modMatrix.clear(); + beatClock.clear(); + metronome.clear(); } }; } diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 6a2e6e08..d951b39a 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -859,6 +859,9 @@ void Synth::renderBlock(AudioSpan buffer) noexcept ModMatrix& mm = impl.resources_.modMatrix; mm.beginCycle(numFrames); + BeatClock& bc = impl.resources_.beatClock; + bc.beginCycle(numFrames); + { // Clear effect busses ScopedTiming logger { callbackBreakdown.effects }; for (auto& bus : impl.effectBuses_) { @@ -921,9 +924,20 @@ void Synth::renderBlock(AudioSpan buffer) noexcept // Apply the master volume buffer.applyGain(db2mag(impl.volume_)); + // Process the metronome (debugging tool for host time info) + constexpr bool metronomeEnabled = false; + if (metronomeEnabled) { + impl.resources_.metronome.processAdding( + bc.getRunningBeatNumber().data(), bc.getRunningBeatsPerBar().data(), + buffer.getChannel(0), buffer.getChannel(1), numFrames); + } + // Perform any remaining modulators mm.endCycle(); + // Advance the clock to the end of cycle + bc.endCycle(); + { // Clear events and advance midi time ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; impl.resources_.midiState.advanceTime(buffer.getNumFrames()); @@ -1183,36 +1197,33 @@ void Synth::aftertouch(int /* delay */, uint8_t /* aftertouch */) noexcept Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; } -void Synth::tempo(int /* delay */, float /* secondsPerQuarter */) noexcept +void Synth::tempo(int delay, float secondsPerBeat) noexcept { Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; + + impl.resources_.beatClock.setTempo(delay, secondsPerBeat); } void Synth::timeSignature(int delay, int beatsPerBar, int beatUnit) { Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; - (void)delay; - (void)beatsPerBar; - (void)beatUnit; + impl.resources_.beatClock.setTimeSignature(delay, TimeSignature(beatsPerBar, beatUnit)); } -void Synth::timePosition(int delay, int bar, float barBeat) +void Synth::timePosition(int delay, int bar, double barBeat) { Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; - (void)delay; - (void)bar; - (void)barBeat; + impl.resources_.beatClock.setTimePosition(delay, BBT(bar, barBeat)); } void Synth::playbackState(int delay, int playbackState) { Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; - (void)delay; - (void)playbackState; + impl.resources_.beatClock.setPlaying(delay, playbackState == 1); } int Synth::getNumRegions() const noexcept diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index b95f193f..3a05b3c9 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -405,7 +405,7 @@ public: * @param bar The current bar. * @param bar_beat The fractional position of the current beat within the bar. */ - void timePosition(int delay, int bar, float barBeat); + void timePosition(int delay, int bar, double barBeat); /** * @brief Send the playback state. * diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 7940908e..fa6e4262 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -1475,10 +1475,13 @@ 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(); + const NumericId id { static_cast(i) }; + auto lfo = absl::make_unique(id, resources.bufferPool, &resources.beatClock, &resources.modMatrix); lfo->setSampleRate(impl.sampleRate_); impl.lfos_[i] = std::move(lfo); } diff --git a/src/sfizz/modulations/ModId.cpp b/src/sfizz/modulations/ModId.cpp index 3d837ec5..388da189 100644 --- a/src/sfizz/modulations/ModId.cpp +++ b/src/sfizz/modulations/ModId.cpp @@ -68,6 +68,10 @@ int ModIds::flags(ModId id) noexcept return kModIsPerVoice|kModIsAdditive; case ModId::OscillatorModDepth: return kModIsPerVoice|kModIsPercentMultiplicative; + case ModId::LFOFrequency: + return kModIsPerVoice|kModIsAdditive; + case ModId::LFOBeats: + return kModIsPerVoice|kModIsAdditive; // unknown default: diff --git a/src/sfizz/modulations/ModId.h b/src/sfizz/modulations/ModId.h index 48d9b2d9..f4c45965 100644 --- a/src/sfizz/modulations/ModId.h +++ b/src/sfizz/modulations/ModId.h @@ -49,6 +49,8 @@ enum class ModId : int { EqBandwidth, OscillatorDetune, OscillatorModDepth, + LFOFrequency, + LFOBeats, _TargetsEnd, // [/targets] -------------------------------------------------------------- diff --git a/src/sfizz/modulations/ModKey.cpp b/src/sfizz/modulations/ModKey.cpp index 55807863..474ca622 100644 --- a/src/sfizz/modulations/ModKey.cpp +++ b/src/sfizz/modulations/ModKey.cpp @@ -136,6 +136,10 @@ std::string ModKey::toString() const return absl::StrCat("OscillatorDetune {", region_.number(), ", N=", 1 + params_.N, "}"); case ModId::OscillatorModDepth: return absl::StrCat("OscillatorModDepth {", region_.number(), ", N=", 1 + params_.N, "}"); + case ModId::LFOFrequency: + return absl::StrCat("LFOFrequency {", region_.number(), ", N=", 1 + params_.N, "}"); + case ModId::LFOBeats: + return absl::StrCat("LFOBeats {", region_.number(), ", N=", 1 + params_.N, "}"); default: return {}; diff --git a/src/sfizz/modulations/sources/LFO.cpp b/src/sfizz/modulations/sources/LFO.cpp index a485833d..206f0ca1 100644 --- a/src/sfizz/modulations/sources/LFO.cpp +++ b/src/sfizz/modulations/sources/LFO.cpp @@ -59,7 +59,7 @@ void LFOSource::generate(const ModKey& sourceKey, NumericId voiceId, absl } LFO* lfo = voice->getLFO(lfoIndex); - lfo->process(buffer); + lfo->process(buffer, region->getId()); } } // namespace sfz diff --git a/src/sfizz/sfizz.cpp b/src/sfizz/sfizz.cpp index a8cc7e7c..8628ef97 100644 --- a/src/sfizz/sfizz.cpp +++ b/src/sfizz/sfizz.cpp @@ -169,7 +169,7 @@ void sfz::Sfizz::timeSignature(int delay, int beatsPerBar, int beatUnit) synth->timeSignature(delay, beatsPerBar, beatUnit); } -void sfz::Sfizz::timePosition(int delay, int bar, float barBeat) +void sfz::Sfizz::timePosition(int delay, int bar, double barBeat) { synth->timePosition(delay, bar, barBeat); } diff --git a/src/sfizz/sfizz_wrapper.cpp b/src/sfizz/sfizz_wrapper.cpp index f4d1866c..4ecd40f3 100644 --- a/src/sfizz/sfizz_wrapper.cpp +++ b/src/sfizz/sfizz_wrapper.cpp @@ -171,7 +171,7 @@ void sfizz_send_time_signature(sfizz_synth_t* synth, int delay, int beats_per_ba auto* self = reinterpret_cast(synth); self->timeSignature(delay, beats_per_bar, beat_unit); } -void sfizz_send_time_position(sfizz_synth_t* synth, int delay, int bar, float bar_beat) +void sfizz_send_time_position(sfizz_synth_t* synth, int delay, int bar, double bar_beat) { auto* self = reinterpret_cast(synth); self->timePosition(delay, bar, bar_beat); diff --git a/tests/LFOT.cpp b/tests/LFOT.cpp index 9a7ac3ab..4f2b6a6d 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,26 @@ 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]); + const NumericId id { static_cast(l) }; + sfz::LFO* lfo = new sfz::LFO(id, 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; diff --git a/vst/SfizzVstProcessor.cpp b/vst/SfizzVstProcessor.cpp index 674e27b8..c77365da 100644 --- a/vst/SfizzVstProcessor.cpp +++ b/vst/SfizzVstProcessor.cpp @@ -297,7 +297,7 @@ void SfizzVstProcessor::updateTimeInfo(const Vst::ProcessContext& context) double beats = context.projectTimeMusic * 0.25 * _timeSigDenominator; double bars = beats / _timeSigNumerator; beats -= int(bars) * _timeSigNumerator; - synth.timePosition(0, int(bars), float(beats)); + synth.timePosition(0, int(bars), beats); } synth.playbackState(0, (context.state & context.kPlaying) != 0);