From 2735d1536eb74719e2a33c5198d9eabc52a21120 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Wed, 17 Mar 2021 11:40:31 +0100 Subject: [PATCH 1/2] Reset the smoothers --- src/sfizz/BeatClock.cpp | 43 ++++++++++++-------- src/sfizz/BeatClock.h | 18 ++++++++ src/sfizz/MidiState.cpp | 18 +++++--- src/sfizz/MidiState.h | 9 +++- src/sfizz/Synth.cpp | 18 +++++++- src/sfizz/SynthPrivate.h | 2 + src/sfizz/modulations/sources/Controller.cpp | 18 ++++++++ src/sfizz/modulations/sources/Controller.h | 4 ++ 8 files changed, 105 insertions(+), 25 deletions(-) diff --git a/src/sfizz/BeatClock.cpp b/src/sfizz/BeatClock.cpp index 70ef5856..bfef364b 100644 --- a/src/sfizz/BeatClock.cpp +++ b/src/sfizz/BeatClock.cpp @@ -157,6 +157,12 @@ absl::Span BeatClock::getRunningBeatPosition() return absl::MakeConstSpan(runningBeatPosition_.data(), currentCycleFrames_); } +float BeatClock::getLastBeatPosition() const +{ + double beats = lastClientPos_.toBeats(timeSig_); + return static_cast(beats); +} + absl::Span BeatClock::getRunningBeatsPerBar() { fillBufferUpTo(currentCycleFrames_); @@ -175,32 +181,35 @@ void BeatClock::fillBufferUpTo(unsigned delay) 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); + if (!isPlaying_) { 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); + if (fillIdx < delay) { + double beats = clientPos.toBeats(sig); + // quantization to nearest for prevention of rounding errors + fill(absl::MakeSpan(&beatNumberData[fillIdx], delay - fillIdx), dequantize(quantize(beats))); + fill(absl::MakeSpan(&beatNumberPosition[fillIdx], delay - fillIdx), static_cast(beats)); + fillIdx = delay; + } + } else { + for (; fillIdx < delay; ++fillIdx) { + clientPos = BBT::fromBeats(sig, clientPos.toBeats(sig) + getBeatsPerFrame()); + 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; diff --git a/src/sfizz/BeatClock.h b/src/sfizz/BeatClock.h index 880c2134..62ed3d81 100644 --- a/src/sfizz/BeatClock.h +++ b/src/sfizz/BeatClock.h @@ -102,6 +102,11 @@ public: * @brief Set the time signature. */ void setTimeSignature(unsigned delay, TimeSignature newSig); + /** + * @brief Get the time signature + * + */ + TimeSignature getTimeSignature() const noexcept { return timeSig_; } /** * @brief Set the time position. */ @@ -134,6 +139,19 @@ public: * @brief Get the time signature numerator for each frame of the current cycle. */ absl::Span getRunningBeatsPerBar(); + + /** + * @brief Get the last BeatPosition + * + * @return float + */ + float getLastBeatPosition() const; + /** + * @brief Get the Beats Per Frame object + * + * @return float + */ + double getBeatsPerFrame() const { return beatsPerSecond_ * samplePeriod_; } /** * @brief Create a normalized phase signal for LFO which completes a * period every N-th beat. diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 52c7f188..938fba1e 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -57,21 +57,27 @@ void sfz::MidiState::setSampleRate(float sampleRate) noexcept void sfz::MidiState::advanceTime(int numSamples) noexcept { - auto clearEvents = [] (EventVector& events) { + internalClock += numSamples; + flushEvents(); +} + +void sfz::MidiState::flushEvents() noexcept +{ + auto flushEventVector = [] (EventVector& events) { ASSERT(!events.empty()); // CC event vectors should never be empty events.front().value = events.back().value; events.front().delay = 0; events.resize(1); }; - internalClock += numSamples; for (auto& ccEvents : cc) - clearEvents(ccEvents); + flushEventVector(ccEvents); - clearEvents(pitchEvents); - clearEvents(channelAftertouchEvents); + flushEventVector(pitchEvents); + flushEventVector(channelAftertouchEvents); } + void sfz::MidiState::setSamplesPerBlock(int samplesPerBlock) noexcept { auto updateEventBufferSize = [=] (EventVector& events) { @@ -113,7 +119,7 @@ float sfz::MidiState::getLastVelocity() const noexcept void sfz::MidiState::insertEventInVector(EventVector& events, int delay, float value) { - const auto insertionPoint = absl::c_upper_bound(events, delay, MidiEventDelayComparator {}); + const auto insertionPoint = absl::c_lower_bound(events, delay, MidiEventDelayComparator {}); if (insertionPoint == events.end() || insertionPoint->delay != delay) events.insert(insertionPoint, { delay, value }); else diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index 03306d77..f9766c81 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -130,11 +130,18 @@ public: /** * @brief Advances the internal clock of a given amount of samples. * You should call this at each callback. This will flush the events - * in the midistate memory. + * in the midistate memory by calling flushEvents(). * * @param numSamples the number of samples of clock advance */ void advanceTime(int numSamples) noexcept; + + /** + * @brief Flush events in all states, keeping only the last one as the "base" state + * + */ + void flushEvents() noexcept; + /** * @brief Get the CC value for CC number * diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index abdefb14..69885d39 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -258,6 +258,7 @@ void Synth::Impl::clear() groupOpcodes_.clear(); unknownOpcodes_.clear(); modificationTime_ = absl::nullopt; + playheadMoved_ = false; // set default controllers // midistate is reset above @@ -913,6 +914,12 @@ void Synth::renderBlock(AudioSpan buffer) noexcept BeatClock& bc = impl.resources_.beatClock; bc.beginCycle(numFrames); + if (impl.playheadMoved_ && impl.resources_.beatClock.isPlaying()) { + impl.resources_.midiState.flushEvents(); + impl.genController_->resetSmoothers(); + impl.playheadMoved_ = false; + } + { // Clear effect busses ScopedTiming logger { callbackBreakdown.effects }; for (auto& bus : impl.effectBuses_) { @@ -1301,7 +1308,16 @@ void Synth::timePosition(int delay, int bar, double barBeat) Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; - impl.resources_.beatClock.setTimePosition(delay, BBT(bar, barBeat)); + const auto newPosition = BBT(bar, barBeat); + const auto newBeatPosition = newPosition.toBeats(impl.resources_.beatClock.getTimeSignature()); + const auto currentBeatPosition = impl.resources_.beatClock.getLastBeatPosition(); + const auto positionDifference = std::abs(newBeatPosition - currentBeatPosition); + const auto threshold = 2 * static_cast(impl.resources_.beatClock.getBeatsPerFrame()); + + if (positionDifference > threshold) + impl.playheadMoved_ = true; + + impl.resources_.beatClock.setTimePosition(delay, newPosition); } void Synth::playbackState(int delay, int playbackState) diff --git a/src/sfizz/SynthPrivate.h b/src/sfizz/SynthPrivate.h index af694fa0..faf2b7e3 100644 --- a/src/sfizz/SynthPrivate.h +++ b/src/sfizz/SynthPrivate.h @@ -309,6 +309,8 @@ struct Synth::Impl final: public Parser::Listener { client.setReceiveCallback(broadcastReceiver); return client; } + + bool playheadMoved_ { false }; }; } // namespace sfz diff --git a/src/sfizz/modulations/sources/Controller.cpp b/src/sfizz/modulations/sources/Controller.cpp index c81229f2..c66b74f2 100644 --- a/src/sfizz/modulations/sources/Controller.cpp +++ b/src/sfizz/modulations/sources/Controller.cpp @@ -16,6 +16,7 @@ namespace sfz { struct ControllerSource::Impl { + float getLastTransformedValue(uint16_t cc, uint8_t curve) const noexcept; double sampleRate_ = config::defaultSampleRate; Resources* res_ = nullptr; absl::flat_hash_map smoother_; @@ -31,6 +32,22 @@ ControllerSource::~ControllerSource() { } +float ControllerSource::Impl::getLastTransformedValue(uint16_t cc, uint8_t curveIndex) const noexcept +{ + ASSERT(res_); + const Curve& curve = res_->curves.getCurve(curveIndex); + const auto lastCCValue = res_->midiState.getCCValue(cc); + return curve.evalNormalized(lastCCValue); +} + +void ControllerSource::resetSmoothers() +{ + for (auto& item : impl_->smoother_) { + const ModKey::Parameters p = item.first.parameters(); + item.second.reset(impl_->getLastTransformedValue(p.cc, p.curve)); + } +} + void ControllerSource::setSampleRate(double sampleRate) { if (impl_->sampleRate_ == sampleRate) @@ -58,6 +75,7 @@ void ControllerSource::init(const ModKey& sourceKey, NumericId voiceId, u if (p.smooth > 0) { Smoother s; s.setSmoothing(p.smooth, impl_->sampleRate_); + s.reset(impl_->getLastTransformedValue(p.cc, p.curve)); impl_->smoother_[sourceKey] = s; } else { diff --git a/src/sfizz/modulations/sources/Controller.h b/src/sfizz/modulations/sources/Controller.h index d5320cf3..829f0333 100644 --- a/src/sfizz/modulations/sources/Controller.h +++ b/src/sfizz/modulations/sources/Controller.h @@ -21,6 +21,10 @@ public: void init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; void generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) override; + /** + * @brief Reset the smoothers. + */ + void resetSmoothers(); private: struct Impl; std::unique_ptr impl_; From ec021ae5de81977a8a6b9910d0b3f4a927820217 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 20 Mar 2021 22:12:36 +0100 Subject: [PATCH 2/2] Correcting w.r.t. comments --- src/sfizz/BeatClock.cpp | 5 ++--- src/sfizz/BeatClock.h | 2 +- src/sfizz/Smoothers.cpp | 13 +++++++++++++ src/sfizz/Smoothers.h | 9 +++++++++ src/sfizz/Synth.cpp | 2 +- 5 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/sfizz/BeatClock.cpp b/src/sfizz/BeatClock.cpp index bfef364b..2b1d635e 100644 --- a/src/sfizz/BeatClock.cpp +++ b/src/sfizz/BeatClock.cpp @@ -157,10 +157,9 @@ absl::Span BeatClock::getRunningBeatPosition() return absl::MakeConstSpan(runningBeatPosition_.data(), currentCycleFrames_); } -float BeatClock::getLastBeatPosition() const +double BeatClock::getLastBeatPosition() const { - double beats = lastClientPos_.toBeats(timeSig_); - return static_cast(beats); + return lastClientPos_.toBeats(timeSig_); } absl::Span BeatClock::getRunningBeatsPerBar() diff --git a/src/sfizz/BeatClock.h b/src/sfizz/BeatClock.h index 62ed3d81..a3fbc38d 100644 --- a/src/sfizz/BeatClock.h +++ b/src/sfizz/BeatClock.h @@ -145,7 +145,7 @@ public: * * @return float */ - float getLastBeatPosition() const; + double getLastBeatPosition() const; /** * @brief Get the Beats Per Frame object * diff --git a/src/sfizz/Smoothers.cpp b/src/sfizz/Smoothers.cpp index df17280e..dd6f0146 100644 --- a/src/sfizz/Smoothers.cpp +++ b/src/sfizz/Smoothers.cpp @@ -31,6 +31,12 @@ void OnePoleSmoother::setSmoothing(unsigned smoothValue, float sampleRate) void OnePoleSmoother::reset(float value) { filter.reset(value); + target_ = value; +} + +void OnePoleSmoother::resetToTarget() +{ + reset(target_); } void OnePoleSmoother::process(absl::Span input, absl::Span output, bool canShortcut) @@ -55,6 +61,8 @@ void OnePoleSmoother::process(absl::Span input, absl::Span o } else if (input.data() != output.data()) { copy(input, output); } + + target_ = input.back(); } /// @@ -76,6 +84,11 @@ void LinearSmoother::reset(float value) //framesToTarget_ = 0; } +void LinearSmoother::resetToTarget() +{ + reset(target_); +} + void LinearSmoother::process(absl::Span input, absl::Span output, bool canShortcut) { CHECK_SPAN_SIZES(input, output); diff --git a/src/sfizz/Smoothers.h b/src/sfizz/Smoothers.h index 0527e96d..8614f327 100644 --- a/src/sfizz/Smoothers.h +++ b/src/sfizz/Smoothers.h @@ -31,6 +31,10 @@ public: * @param value */ void reset(float value = 0.0f); + /** + * @brief Reset to the target value (the back of the last vector passed) + */ + void resetToTarget(); /** * @brief Process a span of data. Input and output can refer to the same * memory. @@ -47,6 +51,7 @@ public: private: bool smoothing { false }; OnePoleFilter filter {}; + float target_ { 0.0f }; }; /** @@ -70,6 +75,10 @@ public: * @param value */ void reset(float value = 0.0f); + /** + * @brief Reset to the target value (the back of the last vector passed) + */ + void resetToTarget(); /** * @brief Process a span of data. Input and output can refer to the same * memory. diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 69885d39..5d18dc6c 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -1312,7 +1312,7 @@ void Synth::timePosition(int delay, int bar, double barBeat) const auto newBeatPosition = newPosition.toBeats(impl.resources_.beatClock.getTimeSignature()); const auto currentBeatPosition = impl.resources_.beatClock.getLastBeatPosition(); const auto positionDifference = std::abs(newBeatPosition - currentBeatPosition); - const auto threshold = 2 * static_cast(impl.resources_.beatClock.getBeatsPerFrame()); + const auto threshold = 2 * impl.resources_.beatClock.getBeatsPerFrame(); if (positionDifference > threshold) impl.playheadMoved_ = true;