| 12 | : mConfig(cfg), mCurrent(cfg.targetValue) {} |
| 13 | |
| 14 | float SilenceEnvelope::update(bool isSilent, float currentValue, float dt) FL_NOEXCEPT { |
| 15 | if (!isSilent) { |
| 16 | // Pass-through during audio; cache for decay if silence starts next frame. |
| 17 | mCurrent = currentValue; |
| 18 | return mCurrent; |
| 19 | } |
| 20 | |
| 21 | // During silence — exponential decay toward target with time constant tau. |
| 22 | // alpha = 1 - exp(-dt/tau) gives a proper first-order response independent |
| 23 | // of dt. At dt=tau, alpha ≈ 0.632; at dt=3*tau, we've crossed 95% of the way. |
| 24 | if (mConfig.decayTauSeconds <= 0.0f || dt <= 0.0f) { |
| 25 | // No decay time or zero dt → snap to target immediately. |
| 26 | mCurrent = mConfig.targetValue; |
| 27 | return mCurrent; |
| 28 | } |
| 29 | const float alpha = 1.0f - fl::expf(-dt / mConfig.decayTauSeconds); |
| 30 | mCurrent = mCurrent + (mConfig.targetValue - mCurrent) * alpha; |
| 31 | return mCurrent; |
| 32 | } |
| 33 | |
| 34 | void SilenceEnvelope::reset(float initialValue) FL_NOEXCEPT { |
| 35 | mCurrent = initialValue; |