Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/include/orbit_delay_c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
7 changes: 7 additions & 0 deletions core/include/orbit_delay_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -59,6 +61,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);

Expand Down Expand Up @@ -203,6 +206,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;
Expand Down Expand Up @@ -265,6 +269,7 @@ class OrbitDelayCore {
std::atomic<bool> pendingDcBlockEnabled_{false};
std::atomic<bool> pendingShimmerModeEnabled_{true};
std::atomic<uint32_t> pendingReadMode_{static_cast<uint32_t>(ReadMode::Orbit)};
std::atomic<bool> pendingReverseSmearEnabled_{false};
std::atomic<bool> pendingFeedbackTapMixerEnabled_{false};
std::atomic<float> pendingFeedbackTapMixerAmount_{0.0f};

Expand All @@ -284,6 +289,8 @@ class OrbitDelayCore {

AllpassDiffuser diffuserL_;
AllpassDiffuser diffuserR_;
AllpassStage reverseSmearStagesL_[kReverseSmearStages];
AllpassStage reverseSmearStagesR_[kReverseSmearStages];

EnvelopeFollowerLimiter feedbackLimiterL_;
EnvelopeFollowerLimiter feedbackLimiterR_;
Expand Down
8 changes: 8 additions & 0 deletions core/src/orbit_delay_c_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Comment on lines +273 to +277

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Expose reverse smear control in WASM bindings

Add the corresponding WASM bridge/export for this new C API setter; currently only native C callers can toggle reverse smear. The WASM target uses explicit wrappers and an explicit EXPORTED_FUNCTIONS list (targets/wasm/orbit_delay_wasm.cpp, targets/wasm/CMakeLists.txt), and there is no orbit_wasm_set_reverse_smear_enabled entry, so web/demo/worklet clients cannot access the feature introduced here.

Useful? React with 👍 / 👎.

return true;
}

bool orbit_set_feedback_tap_mixer_enabled(OrbitDelayHandle* handle, bool enabled) {
if (handle == nullptr) {
return false;
Expand Down
47 changes: 47 additions & 0 deletions core/src/orbit_delay_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,27 @@ struct FeedbackTapMixer {
}
};

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};

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,
Expand Down Expand Up @@ -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<ReadMode>(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);

Expand Down Expand Up @@ -278,6 +300,13 @@ void OrbitDelayCore::applyPendingParamsIfNeeded() {
diffuserDirty_ = true;
}
dcBlockEnabled_ = nextDcBlock;
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) {
Expand Down Expand Up @@ -527,6 +556,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) {
Expand Down Expand Up @@ -691,6 +722,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,
Expand Down Expand Up @@ -765,6 +801,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)) {
Expand Down
Loading