FastLED 3.9.15
Loading...
Searching...
No Matches
signal_conditioner.cpp.hpp
Go to the documentation of this file.
2#include "fl/stl/algorithm.h"
3#include "fl/stl/new.h"
4#include "fl/stl/shared_ptr.h"
5#include "fl/math/math.h"
6#include "fl/stl/noexcept.h"
7
8namespace fl {
9namespace audio {
10
14
18
20
22 mConfig = config;
23}
24
26 mNoiseGateOpen = false;
27 mStats.dcOffset = 0;
28 mStats.noiseGateOpen = false;
29 mStats.spikesRejected = 0;
30 mStats.samplesProcessed = 0;
31}
32
34 if (!sample.isValid() || sample.size() == 0) {
35 return Sample(); // Return empty sample
36 }
37
38 const auto pcm = sample.pcm();
39 const size sampleCount = pcm.size();
40
41 // Reserve buffers to avoid repeated allocations
42 mValidMask.clear();
43 mValidMask.reserve(sampleCount);
44 mTempBuffer.clear();
45 mTempBuffer.reserve(sampleCount);
46 mOutputBuffer.clear();
47 mOutputBuffer.reserve(sampleCount);
48
49 // Stage 1: Spike filtering (if enabled)
50 if (mConfig.enableSpikeFilter) {
52 } else {
53 // All samples valid if spike filtering disabled
54 mValidMask.assign(sampleCount, true);
55 }
56
57 // Stage 2: DC offset removal (if enabled)
58 // Uses per-buffer instantaneous DC calculation for accuracy,
59 // plus per-sample DCBlocker for cross-buffer continuity.
60 i32 dcOffset = 0;
61 if (mConfig.enableDCRemoval) {
62 dcOffset = calculateDCOffset(pcm, mValidMask);
63 removeDCOffset(pcm, dcOffset, mTempBuffer);
64 } else {
65 // Copy samples to temp buffer without DC removal
66 mTempBuffer.assign(pcm.begin(), pcm.end());
67 }
68
69 // Stage 3: Noise gate (if enabled)
70 if (mConfig.enableNoiseGate) {
72 } else {
73 // Copy temp buffer to output without gating
75 }
76
77 // Update stats
78 mStats.dcOffset = dcOffset;
79 mStats.noiseGateOpen = mNoiseGateOpen;
80 mStats.samplesProcessed += sampleCount;
81
82 // Create new Sample from cleaned PCM
83 SampleImplPtr impl = fl::make_shared<SampleImpl>();
84 impl->assign(mOutputBuffer.begin(), mOutputBuffer.end(), sample.timestamp());
85 return Sample(impl);
86}
87
89 const size count = pcm.size();
90 validMask.clear();
91 validMask.reserve(count);
92
93 size validCount = 0;
94 const i16 threshold = mConfig.spikeThreshold;
95
96 for (size i = 0; i < count; ++i) {
97 const i16 sample = pcm[i];
98 const bool isValid = (sample > -threshold) && (sample < threshold);
99 validMask.push_back(isValid);
100 if (isValid) {
101 validCount++;
102 }
103 }
104
105 // Update spike rejection count
106 const size spikesInSample = count - validCount;
107 mStats.spikesRejected += spikesInSample;
108
109 return validCount;
110}
111
113 const size count = pcm.size();
114 i64 sum = 0;
115 size validCount = 0;
116
117 // Calculate average of valid samples only
118 for (size i = 0; i < count; ++i) {
119 if (validMask[i]) {
120 sum += pcm[i];
121 validCount++;
122 }
123 }
124
125 if (validCount == 0) {
126 // No valid samples - return zero offset
127 return 0;
128 }
129
130 // Calculate instantaneous DC offset for this buffer
131 const i32 instantDC = static_cast<i32>(sum / static_cast<i64>(validCount));
132 return instantDC;
133}
134
136 const size count = pcm.size();
137 output.clear();
138 output.reserve(count);
139
140 for (size i = 0; i < count; ++i) {
141 // Zero out samples that were marked as spikes
142 if (i < mValidMask.size() && !mValidMask[i]) {
143 output.push_back(0);
144 continue;
145 }
146
147 i32 sample32 = static_cast<i32>(pcm[i]) - dcOffset;
148
149 // Clamp to int16 range to prevent overflow
150 if (sample32 > 32767) sample32 = 32767;
151 if (sample32 < -32768) sample32 = -32768;
152
153 output.push_back(static_cast<i16>(sample32));
154 }
155}
156
158 const size count = pcm.size();
159 output.clear();
160 output.reserve(count);
161
162 const i16 openThreshold = mConfig.noiseGateOpenThreshold;
163 const i16 closeThreshold = mConfig.noiseGateCloseThreshold;
164
165 for (size i = 0; i < count; ++i) {
166 const i16 sample = pcm[i];
167 const i16 absSample = (sample < 0) ? -sample : sample;
168
169 // Hysteresis logic: Different thresholds for opening vs closing
170 if (!mNoiseGateOpen) {
171 // Gate is closed - check if signal exceeds open threshold
172 if (absSample >= openThreshold) {
173 mNoiseGateOpen = true;
174 }
175 } else {
176 // Gate is open - check if signal falls below close threshold
177 if (absSample < closeThreshold) {
178 mNoiseGateOpen = false;
179 }
180 }
181
182 // Output sample if gate is open, else zero
183 output.push_back(mNoiseGateOpen ? sample : 0);
184 }
185}
186
187} // namespace audio
188} // namespace fl
void removeDCOffset(span< const i16 > pcm, i32 dcOffset, vector< i16 > &output) FL_NOEXCEPT
Remove DC offset from samples.
bool mNoiseGateOpen
Noise gate state.
i32 calculateDCOffset(span< const i16 > pcm, const vector< bool > &validMask) FL_NOEXCEPT
Calculate DC offset from valid samples only.
~SignalConditioner() FL_NOEXCEPT
vector< bool > mValidMask
Working buffers (reused to avoid allocations)
void applyNoiseGate(span< const i16 > pcm, vector< i16 > &output) FL_NOEXCEPT
Apply noise gate with hysteresis.
Sample processSample(const Sample &sample) FL_NOEXCEPT
Process a raw audio sample through the conditioning pipeline.
size filterSpikes(span< const i16 > pcm, vector< bool > &validMask) FL_NOEXCEPT
Detect and reject spike samples.
void configure(const SignalConditionerConfig &config) FL_NOEXCEPT
Configure the signal conditioner.
void reset() FL_NOEXCEPT
Reset internal state (DC estimate, noise gate state)
SignalConditionerConfig mConfig
constexpr fl::size size() const FL_NOEXCEPT
Definition span.h:458
void reserve(fl::size n) FL_NOEXCEPT
Definition vector.h:591
void clear() FL_NOEXCEPT
Definition vector.h:634
void push_back(const T &value) FL_NOEXCEPT
Definition vector.h:624
Configuration for signal conditioning pipeline.
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
fl::i64 i64
Definition s16x16x4.h:222
shared_ptr< T > make_shared(Args &&... args) FL_NOEXCEPT
Definition shared_ptr.h:414
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT