| 37 | Processor::~Processor() FL_NOEXCEPT = default; |
| 38 | |
| 39 | void Processor::update(const Sample& sample) { |
| 40 | // Signal conditioning pipeline: raw sample → conditioned sample |
| 41 | Sample conditioned = sample; |
| 42 | |
| 43 | // Stage 1: Signal conditioning (DC removal, spike filtering, noise gate) |
| 44 | if (mSignalConditioningEnabled && conditioned.isValid()) { |
| 45 | conditioned = mSignalConditioner.processSample(conditioned); |
| 46 | if (!conditioned.isValid()) { |
| 47 | return; // Signal was entirely filtered out |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | // Stage 2: Digital gain (simple multiplier) |
| 52 | if (mGain != 1.0f && conditioned.isValid()) { |
| 53 | conditioned.applyGain(mGain); |
| 54 | } |
| 55 | |
| 56 | // Stage 3: Noise floor tracking (passive — updates estimate but doesn't modify signal) |
| 57 | if (mNoiseFloorTrackingEnabled && conditioned.isValid()) { |
| 58 | mNoiseFloorTracker.update(conditioned.rms()); |
| 59 | } |
| 60 | |
| 61 | mContext->setSample(conditioned); |
| 62 | |
| 63 | // Stage 4: Silence flag — detectors use this to gate outputs when audio stops. |
| 64 | // Only populated when NFT is enabled; otherwise silence is unknowable and |
| 65 | // stays at its default (false) after setSample() above. |
| 66 | // |
| 67 | // Uses an absolute RMS threshold rather than NFT.isAboveFloor(): the |
| 68 | // adaptive floor's first-sample init matches the current level, so |
| 69 | // isAboveFloor() stays false for any steady signal. Absolute RMS is the |
| 70 | // right silence primitive — a loud constant tone has large RMS. |
| 71 | if (mNoiseFloorTrackingEnabled && conditioned.isValid()) { |
| 72 | constexpr float kSilenceRmsThreshold = 10.0f; |
| 73 | mContext->setSilent(conditioned.rms() < kSilenceRmsThreshold); |
| 74 | } |
| 75 | |
| 76 | // Phase 1: Compute state for all active detector |
| 77 | for (auto& d : mActiveDetectors) { |
| 78 | d->update(mContext); |
| 79 | } |
| 80 | |
| 81 | // Phase 2: Fire callbacks for all active detector |
| 82 | for (auto& d : mActiveDetectors) { |
| 83 | d->fireCallbacks(); |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | void Processor::updateFromContext(shared_ptr<Context> externalContext) { |
| 88 | // Use externally-provided context (FFT already cached, signal already conditioned). |
no test coverage detected