From 5352087f41688c29a57fe26efc76259198dd4bc8 Mon Sep 17 00:00:00 2001 From: Alexandre Lunelli da Silva Date: Tue, 26 May 2026 02:05:37 -0300 Subject: [PATCH] Add feedback HPF cutoff parameter with smoothing and WASM bridge --- core/include/orbit_delay_c_api.h | 1 + core/include/orbit_delay_core.h | 6 ++++++ core/src/orbit_delay_c_api.cpp | 8 ++++++++ core/src/orbit_delay_core.cpp | 29 ++++++++++++++++++++++++----- targets/wasm/CMakeLists.txt | 2 +- targets/wasm/js/ui.js | 1 + targets/wasm/orbit_delay_wasm.cpp | 1 + 7 files changed, 42 insertions(+), 6 deletions(-) diff --git a/core/include/orbit_delay_c_api.h b/core/include/orbit_delay_c_api.h index fa9a80d..7a673ec 100644 --- a/core/include/orbit_delay_c_api.h +++ b/core/include/orbit_delay_c_api.h @@ -85,6 +85,7 @@ bool orbit_set_feedback(OrbitDelayHandle* handle, float value); bool orbit_set_feedback_drive(OrbitDelayHandle* handle, float value); bool orbit_set_feedback_nonlinear_amount(OrbitDelayHandle* handle, float value); bool orbit_set_feedback_comp_threshold(OrbitDelayHandle* handle, float value); +bool orbit_set_feedback_highpass_hz(OrbitDelayHandle* handle, float value); bool orbit_set_mix(OrbitDelayHandle* handle, float value); bool orbit_set_input_gain(OrbitDelayHandle* handle, float value); bool orbit_set_output_gain(OrbitDelayHandle* handle, float value); diff --git a/core/include/orbit_delay_core.h b/core/include/orbit_delay_core.h index e250a43..bd2622d 100644 --- a/core/include/orbit_delay_core.h +++ b/core/include/orbit_delay_core.h @@ -36,6 +36,7 @@ class OrbitDelayCore { void setFeedbackDrive(float value); void setFeedbackNonlinearAmount(float value); void setFeedbackCompThreshold(float value); + void setFeedbackHighpassHz(float value); void setMix(float value); void setInputGain(float value); void setOutputGain(float value); @@ -109,6 +110,8 @@ class OrbitDelayCore { static constexpr float kDiffuserUpdateDelta = 0.01f; static constexpr float kDiffuserCadenceUpdateDelta = 0.03f; static constexpr float kHighpassCutoffHz = 30.0f; + static constexpr float kMinFeedbackHighpassHz = 20.0f; + static constexpr float kMaxFeedbackHighpassHz = 2000.0f; static constexpr float kOrbitReadGuardMinSamples = 16.0f; static constexpr float kOrbitReadGuardMaxSamples = 64.0f; static constexpr float kHighpassUpdateDeltaHz = 1.0f; @@ -172,6 +175,7 @@ class OrbitDelayCore { float feedbackDrive_ = 1.0f; float feedbackNonlinearAmount_ = 0.0f; float feedbackCompThreshold_ = 1.0f; + float feedbackHighpassHz_ = kHighpassCutoffHz; float mix_ = 0.35f; float toneHz_ = 8000.0f; float smear_ = 0.0f; @@ -202,6 +206,7 @@ class OrbitDelayCore { LinearSmoother feedbackDriveSm_; LinearSmoother feedbackNonlinearAmountSm_; LinearSmoother feedbackCompThresholdSm_; + LinearSmoother feedbackHighpassHzSm_; LinearSmoother toneSm_; LinearSmoother orbitSm_; LinearSmoother offsetSm_; @@ -227,6 +232,7 @@ class OrbitDelayCore { std::atomic pendingFeedbackDrive_{1.0f}; std::atomic pendingFeedbackNonlinearAmount_{0.0f}; std::atomic pendingFeedbackCompThreshold_{1.0f}; + std::atomic pendingFeedbackHighpassHz_{kHighpassCutoffHz}; std::atomic pendingMix_{0.35f}; std::atomic pendingInputGain_{1.0f}; std::atomic pendingOutputGain_{1.0f}; diff --git a/core/src/orbit_delay_c_api.cpp b/core/src/orbit_delay_c_api.cpp index 9e12bdb..eaabbf6 100644 --- a/core/src/orbit_delay_c_api.cpp +++ b/core/src/orbit_delay_c_api.cpp @@ -173,6 +173,14 @@ bool orbit_set_feedback_comp_threshold(OrbitDelayHandle* handle, float value) { return true; } +bool orbit_set_feedback_highpass_hz(OrbitDelayHandle* handle, float value) { + if (handle == nullptr) { + return false; + } + handle->core.setFeedbackHighpassHz(value); + return true; +} + bool orbit_set_mix(OrbitDelayHandle* handle, float value) { if (handle == nullptr) { return false; diff --git a/core/src/orbit_delay_core.cpp b/core/src/orbit_delay_core.cpp index 5d69efd..dc5725e 100644 --- a/core/src/orbit_delay_core.cpp +++ b/core/src/orbit_delay_core.cpp @@ -117,6 +117,7 @@ void OrbitDelayCore::applyPendingParamsIfNeeded() { const float nextFeedbackDrive = pendingFeedbackDrive_.load(std::memory_order_relaxed); const float nextFeedbackNonlinearAmount = pendingFeedbackNonlinearAmount_.load(std::memory_order_relaxed); const float nextFeedbackCompThreshold = pendingFeedbackCompThreshold_.load(std::memory_order_relaxed); + const float nextFeedbackHighpassHz = pendingFeedbackHighpassHz_.load(std::memory_order_relaxed); const float nextMix = pendingMix_.load(std::memory_order_relaxed); const float nextInputGain = pendingInputGain_.load(std::memory_order_relaxed); const float nextOutputGain = pendingOutputGain_.load(std::memory_order_relaxed); @@ -198,6 +199,12 @@ void OrbitDelayCore::applyPendingParamsIfNeeded() { feedbackCompThreshold_ = clampedFeedbackCompThreshold; smoothTargetsChanged = true; } + const float clampedFeedbackHighpassHz = + clampf(sanitizeFinite(nextFeedbackHighpassHz, feedbackHighpassHz_), kMinFeedbackHighpassHz, kMaxFeedbackHighpassHz); + if (feedbackHighpassHz_ != clampedFeedbackHighpassHz) { + feedbackHighpassHz_ = clampedFeedbackHighpassHz; + smoothTargetsChanged = true; + } const float clampedMix = clampf(sanitizeFinite(nextMix, mix_), 0.0f, 1.0f); if (mix_ != clampedMix) { mix_ = clampedMix; @@ -278,6 +285,7 @@ void OrbitDelayCore::syncDspParams() { feedbackDriveSm_.configure(sampleRate_, kSmoothFeedbackNonlinearMs); feedbackNonlinearAmountSm_.configure(sampleRate_, kSmoothFeedbackNonlinearMs); feedbackCompThresholdSm_.configure(sampleRate_, kSmoothFeedbackNonlinearMs); + feedbackHighpassHzSm_.configure(sampleRate_, kSmoothToneMs); feedbackLimiterL_.configure(sampleRate_); feedbackLimiterR_.configure(sampleRate_); toneSm_.configure(sampleRate_, kSmoothToneMs); @@ -301,9 +309,10 @@ void OrbitDelayCore::syncDspParams() { const float lowpassQ = feedbackLowpassQ(); lowpassL_.setParams(nyquistLimited, lowpassQ); lowpassR_.setParams(nyquistLimited, lowpassQ); - highpassL_.setParams(kHighpassCutoffHz, kDefaultFeedbackLowpassQ); - highpassR_.setParams(kHighpassCutoffHz, kDefaultFeedbackLowpassQ); - appliedHighpassHz_ = kHighpassCutoffHz; + const float clampedHighpassHz = clampf(feedbackHighpassHz_, kMinFeedbackHighpassHz, clampf(0.49f * sampleRate_, kMinFeedbackHighpassHz, kMaxFeedbackHighpassHz)); + highpassL_.setParams(clampedHighpassHz, kDefaultFeedbackLowpassQ); + highpassR_.setParams(clampedHighpassHz, kDefaultFeedbackLowpassQ); + appliedHighpassHz_ = clampedHighpassHz; lowpassDirty_ = false; } @@ -331,6 +340,7 @@ void OrbitDelayCore::updateSmoothedTargetsIfDirty() { feedbackDriveSm_.setTarget(feedbackDrive_); feedbackNonlinearAmountSm_.setTarget(feedbackNonlinearAmount_); feedbackCompThresholdSm_.setTarget(feedbackCompThreshold_); + feedbackHighpassHzSm_.setTarget(feedbackHighpassHz_); mixSm_.setTarget(mix_); inputGainSm_.setTarget(inputGain_); outputGainSm_.setTarget(outputGain_); @@ -360,7 +370,8 @@ OrbitDelayCore::SmoothedParams OrbitDelayCore::advanceSmoothers() { heavyParamCadenceHit_ = advanceCadence(); maybeApplyLowpassCutoff(smoothedToneHz); - maybeApplyHighpassCutoff(kHighpassCutoffHz); + const float smoothedFeedbackHighpassHz = feedbackHighpassHzSm_.next(); + maybeApplyHighpassCutoff(smoothedFeedbackHighpassHz); maybeApplyDiffuserAmount(smoothedSmear); return params; } @@ -406,7 +417,8 @@ void OrbitDelayCore::maybeApplyDiffuserAmount(float smoothedSmear) { } void OrbitDelayCore::maybeApplyHighpassCutoff(float targetHighpassHz) { - const float clampedHighpassHz = clampf(targetHighpassHz, 10.0f, clampf(0.49f * sampleRate_, 10.0f, 2000.0f)); + const float clampedHighpassHz = + clampf(targetHighpassHz, kMinFeedbackHighpassHz, clampf(0.49f * sampleRate_, kMinFeedbackHighpassHz, kMaxFeedbackHighpassHz)); const float delta = std::fabs(clampedHighpassHz - appliedHighpassHz_); if (lowpassDirty_ || (heavyParamCadenceHit_ && delta >= kHighpassUpdateDeltaHz)) { highpassL_.setParams(clampedHighpassHz, kDefaultFeedbackLowpassQ); @@ -438,6 +450,7 @@ void OrbitDelayCore::reset(float sampleRate) { feedbackDriveSm_.reset(feedbackDrive_); feedbackNonlinearAmountSm_.reset(feedbackNonlinearAmount_); feedbackCompThresholdSm_.reset(feedbackCompThreshold_); + feedbackHighpassHzSm_.reset(feedbackHighpassHz_); feedbackLimiterL_.reset(); feedbackLimiterR_.reset(); mixSm_.reset(mix_); @@ -551,6 +564,12 @@ void OrbitDelayCore::setMix(float value) { pendingParamVersion_.fetch_add(1u, std::memory_order_release); } +void OrbitDelayCore::setFeedbackHighpassHz(float value) { + pendingFeedbackHighpassHz_.store(clampf(sanitizeFinite(value, feedbackHighpassHz_), kMinFeedbackHighpassHz, kMaxFeedbackHighpassHz), + std::memory_order_relaxed); + pendingParamVersion_.fetch_add(1u, std::memory_order_release); +} + void OrbitDelayCore::setInputGain(float value) { pendingInputGain_.store(clampf(sanitizeFinite(value, inputGain_), 0.0f, 4.0f), std::memory_order_relaxed); pendingParamVersion_.fetch_add(1u, std::memory_order_release); diff --git a/targets/wasm/CMakeLists.txt b/targets/wasm/CMakeLists.txt index 3aa94fa..6970e85 100644 --- a/targets/wasm/CMakeLists.txt +++ b/targets/wasm/CMakeLists.txt @@ -24,6 +24,6 @@ target_link_options(orbit_delay_wasm PRIVATE "-sEXPORT_ES6=1" "-sENVIRONMENT=web,worker,node" "-sALLOW_MEMORY_GROWTH=1" - "-sEXPORTED_FUNCTIONS=['_malloc','_free','_orbit_wasm_init','_orbit_wasm_free','_orbit_wasm_reset','_orbit_wasm_process_stereo','_orbit_wasm_set_orbit','_orbit_wasm_set_offset_samples','_orbit_wasm_set_tempo_bpm','_orbit_wasm_set_note_division','_orbit_wasm_set_stereo_spread','_orbit_wasm_set_lfo_rate_hz','_orbit_wasm_set_lfo_depth_samples','_orbit_wasm_set_lfo_stereo_phase_offset','_orbit_wasm_set_feedback','_orbit_wasm_set_mix','_orbit_wasm_set_input_gain','_orbit_wasm_set_output_gain','_orbit_wasm_set_tone_hz','_orbit_wasm_set_smear_amount','_orbit_wasm_set_shimmer_mode','_orbit_wasm_set_diffuser_stages','_orbit_wasm_set_dc_block_enabled','_orbit_wasm_set_read_mode','_orbit_wasm_set_feedback_tap_mixer_enabled','_orbit_wasm_set_feedback_tap_mixer_amount']" + "-sEXPORTED_FUNCTIONS=['_malloc','_free','_orbit_wasm_init','_orbit_wasm_free','_orbit_wasm_reset','_orbit_wasm_process_stereo','_orbit_wasm_set_orbit','_orbit_wasm_set_offset_samples','_orbit_wasm_set_tempo_bpm','_orbit_wasm_set_note_division','_orbit_wasm_set_stereo_spread','_orbit_wasm_set_lfo_rate_hz','_orbit_wasm_set_lfo_depth_samples','_orbit_wasm_set_lfo_stereo_phase_offset','_orbit_wasm_set_feedback','_orbit_wasm_set_feedback_highpass_hz','_orbit_wasm_set_mix','_orbit_wasm_set_input_gain','_orbit_wasm_set_output_gain','_orbit_wasm_set_tone_hz','_orbit_wasm_set_smear_amount','_orbit_wasm_set_shimmer_mode','_orbit_wasm_set_diffuser_stages','_orbit_wasm_set_dc_block_enabled','_orbit_wasm_set_read_mode','_orbit_wasm_set_feedback_tap_mixer_enabled','_orbit_wasm_set_feedback_tap_mixer_amount']" "-sEXPORTED_RUNTIME_METHODS=['ccall','cwrap']" ) diff --git a/targets/wasm/js/ui.js b/targets/wasm/js/ui.js index 8b6eb92..3f71160 100644 --- a/targets/wasm/js/ui.js +++ b/targets/wasm/js/ui.js @@ -8,6 +8,7 @@ export function bindOrbitUiControls(api, root = document) { ['readMode', 'orbit_wasm_set_read_mode', (v) => Math.round(parseFloat(v))], ['stereoSpread', 'orbit_wasm_set_stereo_spread', parseFloat], ['feedback', 'orbit_wasm_set_feedback', parseFloat], + ['feedbackHighpassHz', 'orbit_wasm_set_feedback_highpass_hz', parseFloat], ['mix', 'orbit_wasm_set_mix', parseFloat], ['inputGain', 'orbit_wasm_set_input_gain', parseFloat], ['outputGain', 'orbit_wasm_set_output_gain', parseFloat], diff --git a/targets/wasm/orbit_delay_wasm.cpp b/targets/wasm/orbit_delay_wasm.cpp index 1076423..0751554 100644 --- a/targets/wasm/orbit_delay_wasm.cpp +++ b/targets/wasm/orbit_delay_wasm.cpp @@ -92,6 +92,7 @@ bool orbit_wasm_set_lfo_rate_hz(float value) { return orbit_set_lfo_rate_hz(g_ct bool orbit_wasm_set_lfo_depth_samples(float value) { return orbit_set_lfo_depth_samples(g_ctx.handle, value); } bool orbit_wasm_set_lfo_stereo_phase_offset(float value) { return orbit_set_lfo_stereo_phase_offset(g_ctx.handle, value); } bool orbit_wasm_set_feedback(float value) { return orbit_set_feedback(g_ctx.handle, value); } +bool orbit_wasm_set_feedback_highpass_hz(float value) { return orbit_set_feedback_highpass_hz(g_ctx.handle, value); } bool orbit_wasm_set_mix(float value) { return orbit_set_mix(g_ctx.handle, value); } bool orbit_wasm_set_input_gain(float value) { return orbit_set_input_gain(g_ctx.handle, value); } bool orbit_wasm_set_output_gain(float value) { return orbit_set_output_gain(g_ctx.handle, value); }