| 76 | } |
| 77 | |
| 78 | Sample AutoGain::process(const Sample& sample) { |
| 79 | // Pass through if disabled |
| 80 | if (!mConfig.enabled) { |
| 81 | return sample; |
| 82 | } |
| 83 | |
| 84 | // Return empty sample if input is invalid or empty |
| 85 | if (!sample.isValid() || sample.size() == 0) { |
| 86 | return Sample(); // Return invalid sample |
| 87 | } |
| 88 | |
| 89 | // Calculate input RMS |
| 90 | const float inputRMS = sample.rms(); |
| 91 | mStats.inputRMS = inputRMS; |
| 92 | |
| 93 | // Compute dt from sample size and sample rate |
| 94 | const float dt = (mSampleRate > 0 && sample.size() > 0) |
| 95 | ? static_cast<float>(sample.size()) / static_cast<float>(mSampleRate) |
| 96 | : 0.023f; |
| 97 | |
| 98 | // Silence detection: spin down integrator when input is essentially silent |
| 99 | // (WLED: control_integrated *= 0.91 during silence) |
| 100 | const float silenceThreshold = 10.0f; |
| 101 | if (inputRMS < silenceThreshold) { |
| 102 | mIntegrator *= 0.91f; |
| 103 | if (fl::abs(mIntegrator) < 0.01f) mIntegrator = 0.0f; |
| 104 | } |
| 105 | |
| 106 | // Step 1: Update peak envelope (fast attack, slow decay) |
| 107 | mPeakEnvelope.update(inputRMS, dt); |
| 108 | const float peakEnv = mPeakEnvelope.value(); |
| 109 | mStats.peakEnvelope = peakEnv; |
| 110 | |
| 111 | // Step 2: Compute target gain from peak envelope |
| 112 | const float targetGain = computeTargetGain(); |
| 113 | mStats.targetGain = targetGain; |
| 114 | |
| 115 | // Step 3: PI controller smoothly drives gain toward target |
| 116 | const float smoothedGain = updatePIController(targetGain, dt); |
| 117 | |
| 118 | // Step 4: Clamp to configured range |
| 119 | const float clampedGain = fl::max(mConfig.minGain, |
| 120 | fl::min(mConfig.maxGain, smoothedGain)); |
| 121 | mStats.currentGain = clampedGain; |
| 122 | mLastGain = clampedGain; |
| 123 | |
| 124 | // Step 5: Apply gain to audio |
| 125 | const auto& pcm = sample.pcm(); |
| 126 | mOutputBuffer.clear(); |
| 127 | mOutputBuffer.reserve(pcm.size()); |
| 128 | applyGain(pcm, clampedGain, mOutputBuffer); |
| 129 | |
| 130 | // Calculate output RMS for statistics |
| 131 | i64 sumSq = 0; |
| 132 | for (size i = 0; i < mOutputBuffer.size(); ++i) { |
| 133 | i32 val = static_cast<i32>(mOutputBuffer[i]); |
| 134 | sumSq += val * val; |
| 135 | } |