| 21 | EnergyAnalyzer::~EnergyAnalyzer() FL_NOEXCEPT = default; |
| 22 | |
| 23 | void EnergyAnalyzer::update(shared_ptr<Context> context) { |
| 24 | // Get RMS directly from Sample (no fft::FFT needed) |
| 25 | mCurrentRMS = context->getRMS(); |
| 26 | u32 timestamp = context->getTimestamp(); |
| 27 | |
| 28 | // Update peak tracking |
| 29 | updatePeak(mCurrentRMS, timestamp); |
| 30 | |
| 31 | // Update average and history |
| 32 | updateAverage(mCurrentRMS); |
| 33 | |
| 34 | // Update min/max |
| 35 | if (mCurrentRMS > 0.001f) { // Ignore near-silence |
| 36 | mMinEnergy = fl::min(mMinEnergy, mCurrentRMS); |
| 37 | mMaxEnergy = fl::max(mMaxEnergy, mCurrentRMS); |
| 38 | } |
| 39 | |
| 40 | // Compute normalized 0-1 RMS using adaptive range tracking. |
| 41 | // AttackDecayFilter: instant attack (0.001s), slow decay (2.0s). |
| 42 | const float dt = computeAudioDt(context->getPCM().size(), context->getSampleRate()); |
| 43 | float runningMax = mRunningMaxFilter.update(mCurrentRMS, dt); |
| 44 | // Ensure running max doesn't decay below a minimum threshold |
| 45 | if (runningMax < 1.0f) { |
| 46 | runningMax = 1.0f; |
| 47 | } |
| 48 | mNormalizedRMS = fl::min(1.0f, mCurrentRMS / runningMax); |
| 49 | } |
| 50 | |
| 51 | void EnergyAnalyzer::fireCallbacks() { |
| 52 | if (onEnergy) { |
no test coverage detected