From 29381baccddceb453d7e850ce7b83c5e49931b93 Mon Sep 17 00:00:00 2001 From: Alexandre Lunelli da Silva Date: Tue, 26 May 2026 20:27:48 -0300 Subject: [PATCH 1/2] Add reverse smear control and dedicated reverse allpass chain --- core/include/orbit_delay_c_api.h | 1 + core/include/orbit_delay_core.h | 5 ++++ core/src/orbit_delay_c_api.cpp | 8 +++++++ core/src/orbit_delay_core.cpp | 41 ++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+) diff --git a/core/include/orbit_delay_c_api.h b/core/include/orbit_delay_c_api.h index 1067646..59095ed 100644 --- a/core/include/orbit_delay_c_api.h +++ b/core/include/orbit_delay_c_api.h @@ -96,6 +96,7 @@ bool orbit_set_shimmer_mode(OrbitDelayHandle* handle, bool enabled); bool orbit_set_diffuser_stages(OrbitDelayHandle* handle, uint32_t value); bool orbit_set_dc_block_enabled(OrbitDelayHandle* handle, bool enabled); bool orbit_set_read_mode(OrbitDelayHandle* handle, OrbitReadMode mode); +bool orbit_set_reverse_smear_enabled(OrbitDelayHandle* handle, bool enabled); bool orbit_set_feedback_tap_mixer_enabled(OrbitDelayHandle* handle, bool enabled); bool orbit_set_feedback_tap_mixer_amount(OrbitDelayHandle* handle, float value); diff --git a/core/include/orbit_delay_core.h b/core/include/orbit_delay_core.h index 62dbe6b..94a2d43 100644 --- a/core/include/orbit_delay_core.h +++ b/core/include/orbit_delay_core.h @@ -59,6 +59,7 @@ class OrbitDelayCore { void setDiffuserStages(uint32_t count); void setDcBlockEnabled(bool enabled); void setReadMode(ReadMode mode); + void setReverseSmearEnabled(bool enabled); void setFeedbackTapMixerEnabled(bool enabled); void setFeedbackTapMixerAmount(float value); @@ -203,6 +204,7 @@ class OrbitDelayCore { bool shimmerModeEnabled_ = true; bool feedbackTapMixerEnabled_ = false; float feedbackTapMixerAmount_ = 0.0f; + bool reverseSmearEnabled_ = false; ReadMode readMode_ = ReadMode::Orbit; FeedbackPreset feedbackPreset_ = FeedbackPreset::Default; bool initialized_ = false; @@ -265,6 +267,7 @@ class OrbitDelayCore { std::atomic pendingDcBlockEnabled_{false}; std::atomic pendingShimmerModeEnabled_{true}; std::atomic pendingReadMode_{static_cast(ReadMode::Orbit)}; + std::atomic pendingReverseSmearEnabled_{false}; std::atomic pendingFeedbackTapMixerEnabled_{false}; std::atomic pendingFeedbackTapMixerAmount_{0.0f}; @@ -284,6 +287,8 @@ class OrbitDelayCore { AllpassDiffuser diffuserL_; AllpassDiffuser diffuserR_; + AllpassStage reverseSmearStagesL_[4]; + AllpassStage reverseSmearStagesR_[4]; EnvelopeFollowerLimiter feedbackLimiterL_; EnvelopeFollowerLimiter feedbackLimiterR_; diff --git a/core/src/orbit_delay_c_api.cpp b/core/src/orbit_delay_c_api.cpp index 248eea2..a7754eb 100644 --- a/core/src/orbit_delay_c_api.cpp +++ b/core/src/orbit_delay_c_api.cpp @@ -270,6 +270,14 @@ bool orbit_set_read_mode(OrbitDelayHandle* handle, OrbitReadMode mode) { } } +bool orbit_set_reverse_smear_enabled(OrbitDelayHandle* handle, bool enabled) { + if (handle == nullptr) { + return false; + } + handle->core.setReverseSmearEnabled(enabled); + return true; +} + bool orbit_set_feedback_tap_mixer_enabled(OrbitDelayHandle* handle, bool enabled) { if (handle == nullptr) { return false; diff --git a/core/src/orbit_delay_core.cpp b/core/src/orbit_delay_core.cpp index 63b071f..2b481ed 100644 --- a/core/src/orbit_delay_core.cpp +++ b/core/src/orbit_delay_core.cpp @@ -37,6 +37,27 @@ struct FeedbackTapMixer { } }; +constexpr uint32_t kReverseSmearStages = 4u; +constexpr uint32_t kReverseSmearDelaysL[kReverseSmearStages] = {19u, 43u, 71u, 109u}; +constexpr uint32_t kReverseSmearDelaysR[kReverseSmearStages] = {23u, 47u, 79u, 113u}; +constexpr float kReverseSmearGains[kReverseSmearStages] = {0.62f, 0.58f, 0.54f, 0.5f}; + +void initReverseSmearStages(AllpassStage (&stages)[kReverseSmearStages], const uint32_t (&delays)[kReverseSmearStages]) { + for (uint32_t i = 0; i < kReverseSmearStages; ++i) { + stages[i].setDelaySamples(delays[i]); + stages[i].setGain(kReverseSmearGains[i]); + stages[i].reset(); + } +} + +float processReverseSmear(AllpassStage (&stages)[kReverseSmearStages], float x) { + float y = x; + for (uint32_t i = 0; i < kReverseSmearStages; ++i) { + y = stages[i].process(y); + } + return y; +} + bool attachBuffersImpl(DelayLine& delayL, DelayLine& delayR, bool& initialized, @@ -161,6 +182,7 @@ void OrbitDelayCore::applyPendingParamsIfNeeded() { const bool nextDcBlock = pendingDcBlockEnabled_.load(std::memory_order_relaxed); const bool nextShimmer = pendingShimmerModeEnabled_.load(std::memory_order_relaxed); const ReadMode nextReadMode = static_cast(pendingReadMode_.load(std::memory_order_relaxed)); + const bool nextReverseSmearEnabled = pendingReverseSmearEnabled_.load(std::memory_order_relaxed); const bool nextFeedbackTapMixerEnabled = pendingFeedbackTapMixerEnabled_.load(std::memory_order_relaxed); const float nextFeedbackTapMixerAmount = pendingFeedbackTapMixerAmount_.load(std::memory_order_relaxed); @@ -278,6 +300,7 @@ void OrbitDelayCore::applyPendingParamsIfNeeded() { diffuserDirty_ = true; } dcBlockEnabled_ = nextDcBlock; + reverseSmearEnabled_ = nextReverseSmearEnabled; feedbackTapMixerEnabled_ = nextFeedbackTapMixerEnabled; feedbackTapMixerAmount_ = clampf(sanitizeFinite(nextFeedbackTapMixerAmount, feedbackTapMixerAmount_), 0.0f, 1.0f); if (shimmerModeEnabled_ != nextShimmer) { @@ -527,6 +550,8 @@ void OrbitDelayCore::reset(float sampleRate) { dcR_.reset(); diffuserL_.reset(); diffuserR_.reset(); + initReverseSmearStages(reverseSmearStagesL_, kReverseSmearDelaysL); + initReverseSmearStages(reverseSmearStagesR_, kReverseSmearDelaysR); } bool OrbitDelayCore::attachBuffers(float* leftBuffer, float* rightBuffer, uint32_t size) { @@ -691,6 +716,11 @@ void OrbitDelayCore::setReadMode(ReadMode mode) { pendingParamVersion_.fetch_add(1u, std::memory_order_release); } +void OrbitDelayCore::setReverseSmearEnabled(bool enabled) { + pendingReverseSmearEnabled_.store(enabled, std::memory_order_relaxed); + pendingParamVersion_.fetch_add(1u, std::memory_order_release); +} + float OrbitDelayCore::processChannelFast(float input, DelayLine& delay, BiquadLowpass& lp, @@ -765,6 +795,17 @@ float OrbitDelayCore::processChannelFast(float input, } } + if (readMode_ == ReadMode::AccidentalReverse && reverseSmearEnabled_) { + auto& reverseSmearStages = (&delay == &delayL_) ? reverseSmearStagesL_ : reverseSmearStagesR_; + wet = processReverseSmear(reverseSmearStages, wet); + if (!isFiniteSafe(wet)) { + for (uint32_t i = 0; i < kReverseSmearStages; ++i) { + reverseSmearStages[i].reset(); + } + wet = 0.0f; + } + } + if (shimmerModeEnabled_) { wet = diffuser.process(wet); if (!isFiniteSafe(wet)) { From 5d2d8d2d070a4e5d1a329343b5fa7a002fc38789 Mon Sep 17 00:00:00 2001 From: Alexandre Lunelli da Silva Date: Tue, 26 May 2026 21:02:32 -0300 Subject: [PATCH 2/2] Refine reverse smear constants and reset behavior --- core/include/orbit_delay_core.h | 6 ++++-- core/src/orbit_delay_core.cpp | 10 ++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/core/include/orbit_delay_core.h b/core/include/orbit_delay_core.h index 94a2d43..7819ba2 100644 --- a/core/include/orbit_delay_core.h +++ b/core/include/orbit_delay_core.h @@ -14,6 +14,8 @@ namespace orbit::dsp { class OrbitDelayCore { public: + static constexpr uint32_t kReverseSmearStages = 4u; + struct BlockDiagnostics { float feedbackInPeak = 0.0f; float feedbackInRms = 0.0f; @@ -287,8 +289,8 @@ class OrbitDelayCore { AllpassDiffuser diffuserL_; AllpassDiffuser diffuserR_; - AllpassStage reverseSmearStagesL_[4]; - AllpassStage reverseSmearStagesR_[4]; + AllpassStage reverseSmearStagesL_[kReverseSmearStages]; + AllpassStage reverseSmearStagesR_[kReverseSmearStages]; EnvelopeFollowerLimiter feedbackLimiterL_; EnvelopeFollowerLimiter feedbackLimiterR_; diff --git a/core/src/orbit_delay_core.cpp b/core/src/orbit_delay_core.cpp index 2b481ed..982eba7 100644 --- a/core/src/orbit_delay_core.cpp +++ b/core/src/orbit_delay_core.cpp @@ -37,7 +37,7 @@ struct FeedbackTapMixer { } }; -constexpr uint32_t kReverseSmearStages = 4u; +constexpr uint32_t kReverseSmearStages = OrbitDelayCore::kReverseSmearStages; constexpr uint32_t kReverseSmearDelaysL[kReverseSmearStages] = {19u, 43u, 71u, 109u}; constexpr uint32_t kReverseSmearDelaysR[kReverseSmearStages] = {23u, 47u, 79u, 113u}; constexpr float kReverseSmearGains[kReverseSmearStages] = {0.62f, 0.58f, 0.54f, 0.5f}; @@ -300,7 +300,13 @@ void OrbitDelayCore::applyPendingParamsIfNeeded() { diffuserDirty_ = true; } dcBlockEnabled_ = nextDcBlock; - reverseSmearEnabled_ = nextReverseSmearEnabled; + if (reverseSmearEnabled_ != nextReverseSmearEnabled) { + reverseSmearEnabled_ = nextReverseSmearEnabled; + for (uint32_t i = 0; i < kReverseSmearStages; ++i) { + reverseSmearStagesL_[i].reset(); + reverseSmearStagesR_[i].reset(); + } + } feedbackTapMixerEnabled_ = nextFeedbackTapMixerEnabled; feedbackTapMixerAmount_ = clampf(sanitizeFinite(nextFeedbackTapMixerAmount, feedbackTapMixerAmount_), 0.0f, 1.0f); if (shimmerModeEnabled_ != nextShimmer) {