diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index db6c9cc..06490d6 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -7,3 +7,9 @@ target_include_directories(orbit_delay_core PUBLIC ${CMAKE_SOURCE_DIR} ) + + +target_compile_definitions(orbit_delay_core + PUBLIC + ORBIT_DELAY_ENABLE_HERMITE=1 +) diff --git a/core/include/orbit_delay_core.h b/core/include/orbit_delay_core.h index 0bde976..e250a43 100644 --- a/core/include/orbit_delay_core.h +++ b/core/include/orbit_delay_core.h @@ -105,8 +105,12 @@ class OrbitDelayCore { static constexpr float kMaxNoteDivision = 4.0f; static constexpr uint32_t kHeavyParamCadenceSamples = 16u; static constexpr float kLowpassUpdateDeltaHz = 20.0f; + static constexpr float kLowpassCadenceUpdateDeltaHz = 80.0f; static constexpr float kDiffuserUpdateDelta = 0.01f; + static constexpr float kDiffuserCadenceUpdateDelta = 0.03f; static constexpr float kHighpassCutoffHz = 30.0f; + static constexpr float kOrbitReadGuardMinSamples = 16.0f; + static constexpr float kOrbitReadGuardMaxSamples = 64.0f; static constexpr float kHighpassUpdateDeltaHz = 1.0f; static constexpr float kDefaultFeedbackLowpassQ = 0.707f; static constexpr float kReverseLegacyFeedbackLowpassQ = 0.5f; diff --git a/core/src/orbit_delay_core.cpp b/core/src/orbit_delay_core.cpp index 660836b..9ff17d9 100644 --- a/core/src/orbit_delay_core.cpp +++ b/core/src/orbit_delay_core.cpp @@ -88,6 +88,14 @@ float computeTempoDelaySamples(float sampleRate, float tempoBpm, float noteDivis float wrapUnitParam(float value, float fallback) { return isFiniteSafe(value) ? wrapPosFloat(value, 1.0f, 1.0f) : fallback; } + +float readDelayWrapped(const DelayLine& delay, float readPos) { +#if defined(ORBIT_DELAY_ENABLE_HERMITE) + return delay.readAbsoluteHermiteWrapped(readPos); +#else + return delay.readAbsoluteLinearWrapped(readPos); +#endif +} } // namespace void OrbitDelayCore::applyPendingParamsIfNeeded() { @@ -375,7 +383,8 @@ bool OrbitDelayCore::advanceCadence() { void OrbitDelayCore::maybeApplyLowpassCutoff(float smoothedToneHz) { const float clampedToneHz = clampf(smoothedToneHz, 300.0f, clampf(0.49f * sampleRate_, 300.0f, 12000.0f)); const float delta = std::fabs(clampedToneHz - appliedToneHz_); - if (lowpassDirty_ || heavyParamCadenceHit_ || delta >= kLowpassUpdateDeltaHz) { + const bool cadenceAndAudible = heavyParamCadenceHit_ && delta >= kLowpassCadenceUpdateDeltaHz; + if (lowpassDirty_ || delta >= kLowpassUpdateDeltaHz || cadenceAndAudible) { const float lowpassQ = feedbackLowpassQ(); lowpassL_.setParams(clampedToneHz, lowpassQ); lowpassR_.setParams(clampedToneHz, lowpassQ); @@ -386,7 +395,8 @@ void OrbitDelayCore::maybeApplyLowpassCutoff(float smoothedToneHz) { void OrbitDelayCore::maybeApplyDiffuserAmount(float smoothedSmear) { const float delta = std::fabs(smoothedSmear - appliedSmear_); - if (diffuserDirty_ || heavyParamCadenceHit_ || delta >= kDiffuserUpdateDelta) { + const bool cadenceAndAudible = heavyParamCadenceHit_ && delta >= kDiffuserCadenceUpdateDelta; + if (diffuserDirty_ || delta >= kDiffuserUpdateDelta || cadenceAndAudible) { const float clamped = clampf(smoothedSmear, 0.0f, 1.0f); diffuserL_.setAmount(clamped); diffuserR_.setAmount(clamped); @@ -618,21 +628,21 @@ float OrbitDelayCore::processChannelFast(float input, if (readMode_ == ReadMode::AccidentalReverse) { const float readPos = wrapPosFloat(static_cast(delay.writePos) - params.tempoDelaySamples + spread + lfoSamples, delaySize, invDelaySize); baseReadPos = readPos; -#if defined(ORBIT_DELAY_ENABLE_HERMITE) - wet = delay.readAbsoluteHermiteWrapped(readPos); -#else - wet = delay.readAbsoluteLinearWrapped(readPos); -#endif + wet = readDelayWrapped(delay, readPos); } else { float readPos = params.orbit * static_cast(delay.writePos) + params.offsetSamples + spread + lfoSamples; readPos = wrapPosFloat(readPos, delaySize, invDelaySize); - baseReadPos = readPos; -#if defined(ORBIT_DELAY_ENABLE_HERMITE) - wet = delay.readAbsoluteHermiteWrapped(readPos); -#else - wet = delay.readAbsoluteLinearWrapped(readPos); -#endif + const float writePos = static_cast(delay.writePos); + const float maxGuardBySize = 0.25f * delaySize; + const float guardSamples = clampf(maxGuardBySize, kOrbitReadGuardMinSamples, kOrbitReadGuardMaxSamples); + const float distanceForward = wrapPosFloat(readPos - writePos, delaySize, invDelaySize); + if (distanceForward < guardSamples || distanceForward > (delaySize - guardSamples)) { + readPos = wrapPosFloat(writePos + guardSamples, delaySize, invDelaySize); + } + + baseReadPos = readPos; + wet = readDelayWrapped(delay, readPos); } if (!isFiniteSafe(wet)) { @@ -682,7 +692,8 @@ float OrbitDelayCore::processChannelFast(float input, } } - delay.write(toBuffer); + const float toBufferClipped = softClipPolynomial(toBuffer); + delay.write(toBufferClipped); const float out = (sanitizedInput * (1.0f - params.mix) + filteredWet * params.mix) * params.outputGain; return isFiniteSafe(out) ? out : 0.0f;