FastLED 3.9.15
Loading...
Searching...
No Matches

◆ processSample()

void fl::audio::Reactive::processSample ( const Sample & sample)

Definition at line 164 of file audio_reactive.cpp.hpp.

164 {
165 if (!sample.isValid()) {
166 return; // Invalid sample, ignore
167 }
168
169 // Extract timestamp from the Sample
170 fl::u32 currentTimeMs = sample.timestamp();
171
172 // Phase 1: Signal conditioning pipeline
173 Sample processedSample = sample;
174
175 // Step 1: Signal conditioning (DC removal, spike filtering, noise gate)
176 if (mConfig.enableSignalConditioning) {
177 processedSample = mSignalConditioner.processSample(processedSample);
178 if (!processedSample.isValid()) {
179 return; // Signal was completely filtered out
180 }
181 }
182
183 // Step 2: Noise floor tracking (update tracker, but don't modify signal)
184 if (mConfig.enableNoiseFloorTracking) {
185 float rms = processedSample.rms();
186 mNoiseFloorTracker.update(rms);
187 }
188
189 // Set conditioned sample on shared Context (clears per-frame FFT cache)
190 mContext->setSample(processedSample);
191
192 // Populate silence flag after setSample() resets it — detectors run via
193 // updateFromContext() below and will read context->isSilent().
194 //
195 // Uses an absolute RMS threshold (not the adaptive noise floor). The
196 // adaptive floor's first-sample init matches the current level, which
197 // causes isAboveFloor() to be stuck at false for any steady signal —
198 // wrongly flagging loud constant tones as silent. Absolute RMS is the
199 // right primitive for "no signal present": a steady loud tone has
200 // large RMS and is not silent regardless of noise-floor adaptation.
201 if (mConfig.enableNoiseFloorTracking) {
202 constexpr float kSilenceRmsThreshold = 10.0f;
203 mContext->setSilent(processedSample.rms() < kSilenceRmsThreshold);
204 }
205
206 // Process the conditioned Sample - timing is gated by sample availability
207 processFFT(processedSample);
208
209 // Update internal Processor BEFORE populating Data fields,
210 // so updateVolumeAndPeak() can source normalized volume.
212 mAudioProcessor->updateFromContext(mContext);
213
214 updateVolumeAndPeak(processedSample);
215
216 // Enhanced processing pipeline
219
220 // Apply pink noise compensation AFTER band energy calculation
221 // so that bassEnergy/midEnergy/trebleEnergy reflect actual spectral content
222 if (mPinkNoiseComputed) {
223 for (int i = 0; i < 16; ++i) {
224 mCurrentData.frequencyBins[i] *= mPinkNoiseGains[i];
225 }
226 }
227
228 // Apply A-weighting BEFORE spectral flux and beat detection so that
229 // high-frequency attenuation is visible to onset/beat algorithms.
230 // Previously this ran after beat detection, causing bin 15 to appear
231 // disproportionately active (boosted by pink noise but not yet attenuated).
233
235
236 // Silence gate for spectral metrics (FastLED#2253).
237 // During audio the envelopes are pass-through; during silence they
238 // exponentially decay (tau=0.2s) the raw argmax / flux outputs toward
239 // zero so the FFT noise floor cannot lock onto arbitrary bins.
240 // dt is the exact audio duration of this PCM buffer (frame-rate
241 // independent). When enableNoiseFloorTracking is false, isSilent()
242 // is always false, making this a strict pass-through (no behavior
243 // change for users who haven't opted in).
244 {
245 const bool silent = mContext->isSilent();
246 const float dt = computeAudioDt(processedSample.pcm().size(),
247 mConfig.sampleRate);
248 mCurrentData.dominantFrequency = mDominantFrequencyEnvelope.update(
249 silent, mCurrentData.dominantFrequency, dt);
250 mCurrentData.magnitude = mMagnitudeEnvelope.update(
251 silent, mCurrentData.magnitude, dt);
252 mCurrentData.spectralFlux = mSpectralFluxEnvelope.update(
253 silent, mCurrentData.spectralFlux, dt);
254 }
255
256 // Enhanced beat detection (includes original)
257 detectBeat(currentTimeMs);
258 detectEnhancedBeats(currentTimeMs);
259
260 // Loudness compensation stays after beat detection — it is a global
261 // level adjustment that should not affect relative frequency balance.
263
264 applyGain();
265 applyScaling();
267
268 mCurrentData.timestamp = currentTimeMs;
269
270 // Processor was already updated earlier in this method (before updateVolumeAndPeak).
271}
float rms(fl::span< const int16_t > data)
Definition simple.h:104
void detectEnhancedBeats(fl::u32 currentTimeMs)
void detectBeat(fl::u32 currentTimeMs)
SilenceEnvelope mDominantFrequencyEnvelope
shared_ptr< Context > mContext
fl::unique_ptr< Processor > mAudioProcessor
Processor & ensureAudioProcessor()
void processFFT(const Sample &sample)
SilenceEnvelope mMagnitudeEnvelope
ReactiveConfig mConfig
SignalConditioner mSignalConditioner
NoiseFloorTracker mNoiseFloorTracker
SilenceEnvelope mSpectralFluxEnvelope
void updateVolumeAndPeak(const Sample &sample)
float computeAudioDt(fl::size pcmSize, int sampleRate) FL_NOEXCEPT
Compute the time delta (in seconds) for an audio buffer.
CRGB sample(const CRGB *grid, const XYMap &xyMap, float x, float y, SampleMode mode)
Sample a pixel from a 2D CRGB grid at floating-point coordinates.
Definition sample.cpp.hpp:9

References applyAWeighting(), applyGain(), applyLoudnessCompensation(), applyScaling(), applySpectralEqualization(), calculateBandEnergies(), fl::audio::computeAudioDt(), detectBeat(), detectEnhancedBeats(), ensureAudioProcessor(), fl::audio::Sample::isValid(), mAudioProcessor, mConfig, mContext, mCurrentData, mDominantFrequencyEnvelope, mMagnitudeEnvelope, mNoiseFloorTracker, mPinkNoiseComputed, mPinkNoiseGains, mSignalConditioner, mSpectralFluxEnvelope, fl::audio::Sample::pcm(), processFFT(), fl::audio::Sample::rms(), rms(), fl::sample(), fl::vector_basic::size(), smoothResults(), updateSpectralFlux(), and updateVolumeAndPeak().

+ Here is the call graph for this function: