FastLED 3.9.15
Loading...
Searching...
No Matches
noise_floor_tracker.cpp.hpp
Go to the documentation of this file.
2#include "fl/stl/algorithm.h"
3#include "fl/math/math.h"
4#include "fl/stl/noexcept.h"
5
6namespace fl {
7namespace audio {
8
11 mStats.currentFloor = mCurrentFloor; // Initialize stats to match internal state
12}
13
15 configure(config);
16 mStats.currentFloor = mCurrentFloor; // Initialize stats to match internal state
17}
18
20
22 mConfig = config;
23}
24
26 mCurrentFloor = 100.0f;
27 mLastHysteresisFloor = 0.0f; // Start at 0 to allow initial floor rise
29 mStats.currentFloor = 100.0f; // Initialize to match mCurrentFloor
30 mStats.minObserved = 0.0f;
31 mStats.maxObserved = 0.0f;
32 mStats.samplesProcessed = 0;
33 mStats.inHysteresis = false;
34}
35
36void NoiseFloorTracker::update(float timedomainLevel, float frequencydomainLevel) {
37 if (!mConfig.enabled) {
38 return;
39 }
40
41 // Combine time and frequency domain metrics if both provided
42 float combinedLevel = combineDomains(timedomainLevel, frequencydomainLevel);
43
44 // Update statistics
45 if (mStats.samplesProcessed == 0) {
46 mStats.minObserved = combinedLevel;
47 mStats.maxObserved = combinedLevel;
48
49 // Initialize floor to first observed level
50 mCurrentFloor = fl::max(mConfig.minFloor, fl::min(mConfig.maxFloor, combinedLevel));
52 } else {
53 mStats.minObserved = fl::min(mStats.minObserved, combinedLevel);
54 mStats.maxObserved = fl::max(mStats.maxObserved, combinedLevel);
55 }
56 mStats.samplesProcessed++;
57
58 // Update floor based on current observation
59 updateFloor(combinedLevel);
60
61 // Update stats
62 mStats.currentFloor = mCurrentFloor;
63}
64
65float NoiseFloorTracker::normalize(float level) const {
66 // Remove noise floor from signal, clamped to non-negative
67 const float normalized = level - mCurrentFloor;
68 return fl::max(0.0f, normalized);
69}
70
71bool NoiseFloorTracker::isAboveFloor(float level) const {
72 // Check if signal exceeds floor plus hysteresis margin
73 return level > (mCurrentFloor + mConfig.hysteresisMargin);
74}
75
77 // Exponential moving average tracking of noise floor
78 // with asymmetric rates for rise (attack) and fall (decay)
79 //
80 // Hysteresis is achieved through the slow attack rate, not through
81 // explicit blocking of updates
82
83 if (level < mCurrentFloor) {
84 // Signal is below floor - floor should decay (decrease) rapidly toward signal
85 // Use decay rate (typically high, like 0.99) for downward movement
86 const float decayAlpha = mConfig.decayRate;
87 mCurrentFloor = decayAlpha * mCurrentFloor + (1.0f - decayAlpha) * level;
88
90 mStats.inHysteresis = false;
91
92 // Track floor drops for statistics
93 if (mLastHysteresisFloor > 0.0f && (mLastHysteresisFloor - mCurrentFloor) >= mConfig.hysteresisMargin) {
95 }
96 } else {
97 // Signal is above floor - floor should rise slowly toward signal
98 // Use attack rate (typically low, like 0.01-0.05) for upward movement
99 // This slow rise prevents chasing transient peaks
100 const float attackAlpha = 1.0f - mConfig.attackRate;
101 mCurrentFloor = attackAlpha * mCurrentFloor + mConfig.attackRate * level;
102
104 mStats.inHysteresis = false;
105
106 // Update hysteresis reference when floor rises significantly
107 if (mCurrentFloor - mLastHysteresisFloor >= mConfig.hysteresisMargin) {
109 mStats.inHysteresis = true; // Indicate significant rise
110 }
111 }
112
113 // Clamp to configured range
115}
116
117float NoiseFloorTracker::combineDomains(float timeLevel, float freqLevel) const {
118 // If no frequency-domain metric provided, use time-domain only
119 if (freqLevel < 0.0f) {
120 return timeLevel;
121 }
122
123 // Weighted average of time and frequency domain metrics
124 const float w = mConfig.crossDomainWeight;
125 return (1.0f - w) * timeLevel + w * freqLevel;
126}
127
128} // namespace audio
129} // namespace fl
float normalize(float level) const FL_NOEXCEPT
Normalize signal by removing noise floor.
bool isAboveFloor(float level) const FL_NOEXCEPT
Check if signal is above noise floor + margin.
float mCurrentFloor
Current noise floor estimate.
void update(float timedomainLevel, float frequencydomainLevel=-1.0f) FL_NOEXCEPT
Update noise floor estimate with new observation.
void updateFloor(float level) FL_NOEXCEPT
Update floor estimate based on current observation.
NoiseFloorTrackerConfig mConfig
void configure(const NoiseFloorTrackerConfig &config) FL_NOEXCEPT
Configure the noise floor tracker.
void reset() FL_NOEXCEPT
Reset noise floor to initial state.
float mLastHysteresisFloor
Floor value at last hysteresis trigger Used to enforce hysteresis margin before allowing floor to ris...
float combineDomains(float timeLevel, float freqLevel) const FL_NOEXCEPT
Calculate combined metric from time and frequency domains.
~NoiseFloorTracker() FL_NOEXCEPT
u32 mBelowFloorCount
Count of consecutive samples below floor (for slow attack)
Configuration for noise floor tracking.
FL_DISABLE_WARNING_PUSH U constexpr common_type_t< T, U > min(T a, U b) FL_NOEXCEPT
Definition math.h:71
constexpr common_type_t< T, U > max(T a, U b) FL_NOEXCEPT
Definition math.h:75
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT