From b194a0a0b9c058f504b8ef1f9b4b24a885428635 Mon Sep 17 00:00:00 2001 From: Darren Gibbs Date: Thu, 3 Sep 2026 17:56:14 -0400 Subject: [PATCH] Form the envelope coefficients with expm1f SetAttackTime and SetTimeConstant compute each per-sample coefficient as 1.f - expf(x). For long segment times x is tiny and expf(x) lands next to 1.0, where a float has steps of about 6e-8, so the subtraction keeps only that quantized remainder. With shape 1 (the near-linear attack) the coefficient for a 30 s attack is 7e-8, below one step, so above roughly 8 s the attack time snaps to one of four values: measured at 48 kHz, 10 s runs in 8.9, 20 s in 17.8 and 30 s in 35.5. Decay and release drift the same way at long times (1.4% at 5 s). expm1f(x) returns exp(x) - 1 without the cancellation, so the same times land within half a percent; short times are unchanged to within rounding. It runs only in the setters, and newlib provides it on the Cortex-M targets. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_018E31X9G4MVrfFXSAXZXMHb --- Source/Control/adsr.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Control/adsr.cpp b/Source/Control/adsr.cpp index cd627879..be932465 100644 --- a/Source/Control/adsr.cpp +++ b/Source/Control/adsr.cpp @@ -60,7 +60,7 @@ void Adsr::SetAttackTime(float timeInS, float shape) float target = 9.f * powf(x, 10.f) + 0.3f * x + 1.01f; attackTarget_ = target; float logTarget = logf(1.f - (1.f / target)); // -1 for decay - attackD0_ = 1.f - expf(logTarget / (timeInS * sample_rate_)); + attackD0_ = -expm1f(logTarget / (timeInS * sample_rate_)); } else attackD0_ = 1.f; // instant change @@ -84,7 +84,7 @@ void Adsr::SetTimeConstant(float timeInS, float& time, float& coeff) if(time > 0.f) { const float target = logf(1. / M_E); - coeff = 1.f - expf(target / (time * sample_rate_)); + coeff = -expm1f(target / (time * sample_rate_)); } else coeff = 1.f; // instant change