FastLED 3.9.15
Loading...
Searching...
No Matches
filter.h
Go to the documentation of this file.
1#pragma once
2
3// General-purpose signal smoothing filters.
4//
5// IIR (stateful, no buffer):
6// ExponentialSmoother<T> — first-order exponential (IIR/RC) smoother
7// AttackDecayFilter<T> — asymmetric EMA: separate attack/decay taus
8// CascadedEMA<T, Stages> — N stages of EMA in series (Gaussian-like)
9// LeakyIntegrator<T, K> — shift-based EMA, cheap for integer/fixed-point
10// DCBlocker<T> — single-pole DC offset removal (audio input)
11// BiquadFilter<T> — second-order IIR (lowpass/highpass/bandpass/notch)
12// KalmanFilter<T> — 1D scalar Kalman filter
13// OneEuroFilter<T> — adaptive velocity-based smoothing (VR/graphics)
14//
15// Multi-channel:
16// SpectralVariance<T> — per-bin EMA with relative deviation (audio/sensors)
17//
18// FIR (windowed, buffer-backed):
19// MovingAverage<T, N> — simple moving average (O(1) running sum)
20// WeightedMovingAverage<T, N> — linearly weighted moving average
21// TriangularFilter<T, N> — triangular (tent) weighted average
22// GaussianFilter<T, N> — binomial-coefficient Gaussian approximation
23// MedianFilter<T, N> — sliding-window median filter
24// AlphaTrimmedMean<T, N> — sorted window, trim extremes, average middle
25// HampelFilter<T, N> — median + deviation threshold outlier rejection
26// SavitzkyGolayFilter<T, N> — polynomial-fit smoothing (preserves peaks)
27// BilateralFilter<T, N> — edge-preserving value-similarity weighting
28//
29// Each FIR filter has a sensible default N (window size). You can also set
30// N = 0 for a runtime-sized buffer that takes capacity at construction:
31// MedianFilter<float> mf; // static, default N = 5
32// MedianFilter<float, 0> mf(10); // dynamic, capacity set at runtime
33//
34// ============================================================================
35// Filter Reference Table
36// ============================================================================
37//
38// IIR filters (stateful, no buffer, constant memory):
39//
40// Filter | update() | Memory | Notes
41// -----------------------------|-----------|--------|-------------------------------
42// ExponentialSmoother<T> | O(1) | 2T | exp() per call, time-aware
43// AttackDecayFilter<T> | O(1) | 3T | exp() per call, asymmetric tau
44// DCBlocker<T> | O(1) | 2T | 1 mul + 2 adds, no exp()
45// LeakyIntegrator<T, K> | O(1) | 1T | bit-shift only, very cheap
46// CascadedEMA<T, S> | O(S) | S*T | S stages, exp() per call
47// BiquadFilter<T> | O(1) | 5T | 5 muls + 4 adds, LP/HP/BP/notch
48// KalmanFilter<T> | O(1) | 4T | 1 div + few muls
49// OneEuroFilter<T> | O(1) | 5T | 2 EMA steps + derivative
50//
51// FIR filters (windowed, buffer-backed, N = window size):
52//
53// Filter | update() | Memory | Odd N? | Notes
54// -----------------------------|-----------|--------|--------|--------------------
55// MovingAverage<T, N> | O(1) | N*T | no | running sum trick
56// WeightedMovingAverage<T, N> | O(N) | N*T | no | linear weights [1..N]
57// TriangularFilter<T, N> | O(N) | N*T | yes | tent weights, symmetric
58// GaussianFilter<T, N> | O(N) | N*T | no | binomial coefficients
59// MedianFilter<T, N> | O(N) | 2*N*T | yes | sorted insert + shift
60// AlphaTrimmedMean<T, N> | O(N) | 2*N*T | no | sorted + trim + avg
61// HampelFilter<T, N> | O(N) | 2*N*T | yes | sorted + MAD outlier
62// SavitzkyGolayFilter<T, N> | O(N) | N*T | yes | polynomial fit, peaks
63// BilateralFilter<T, N> | O(N) | N*T | no | exp() per sample
64//
65// "Odd N?" = static_assert enforces odd N (even N auto-corrected at runtime).
66//
67// ============================================================================
68// Choosing a Filter — Common Scenarios
69// ============================================================================
70//
71// Fast attack, slow decay (instant response to rising input, slow fade-out):
72// AttackDecayFilter<float> env(0.01f, 0.5f); // attack=10ms, decay=500ms
73// float v = env.update(input, dt);
74//
75// Slow attack, fast decay (sluggish ramp-up, snappy drop-off):
76// AttackDecayFilter<float> env(0.5f, 0.01f); // attack=500ms, decay=10ms
77// float v = env.update(input, dt);
78// Useful for peak-hold displays or "gravity" effects on VU meters.
79//
80// Preserve square waves / sharp edges (smooth noise but keep transitions):
81// MedianFilter — rejects impulse noise without smearing edges. A spike
82// gets replaced by the middle value; a genuine step persists once half
83// the window has crossed the threshold.
84// BilateralFilter — weights by value similarity, so samples on the other
85// side of an edge contribute almost nothing. Best edge preservation of
86// the averaging filters.
87// SavitzkyGolayFilter — polynomial fit preserves peaks and step shapes
88// better than any averaging filter, though it does smooth slightly.
89//
90// Reject random spikes / outliers (sensor glitches, bad readings):
91// MedianFilter — the classic choice. Completely ignores isolated spikes.
92// HampelFilter — like MedianFilter but replaces only statistical outliers
93// (beyond threshold * MAD), passing normal variation through unchanged.
94// AlphaTrimmedMean — trims extremes then averages the rest. Smoother
95// output than pure median, still robust to outliers.
96//
97// Smooth noisy sensor data (general-purpose, low latency):
98// LeakyIntegrator — cheapest option: one shift + one add. Good enough
99// for most ADC smoothing on microcontrollers.
100// MovingAverage — O(1) update, easy to reason about. N=8 or N=16 are
101// common choices for ADC readings.
102// ExponentialSmoother — time-aware, handles variable sample rates cleanly.
103//
104// Gaussian-quality smoothing (best frequency response, minimal ringing):
105// GaussianFilter — binomial weights approximate a Gaussian kernel. Best
106// stopband attenuation of the simple FIR filters.
107// CascadedEMA — IIR approximation of Gaussian with no buffer. 3-4 stages
108// gives near-Gaussian impulse response.
109//
110// Pointer / VR / motion tracking (low lag on fast gestures, smooth at rest):
111// OneEuroFilter — designed specifically for this. Adaptive cutoff based
112// on velocity: low jitter when still, low lag when moving fast.
113//
114// Audio input cleanup (DC removal, hum rejection):
115// DCBlocker — removes DC offset from microphone / line-in / ADC bias.
116// BiquadFilter::notch(60.0f, sr, 30.0f) — kills 50/60 Hz mains hum.
117// BiquadFilter::highpass(20.0f, sr) — rumble filter, removes sub-bass.
118//
119// Audio envelope / VU meter:
120// AttackDecayFilter<float> vu(0.001f, 0.3f); // fast attack, slow decay
121// Or BiquadFilter::butterworth() for a clean low-pass at a specific cutoff.
122//
123// Frequency isolation (audio band selection):
124// BiquadFilter::bandpass(440.0f, sr, 2.0f) — isolate a frequency band.
125//
126// Noisy signal with known process model:
127// KalmanFilter — optimal when you can estimate process noise (Q) and
128// measurement noise (R). Converges to the true value over time.
129//
130// Cheapest possible filter (minimal CPU, integer-only MCU):
131// LeakyIntegrator<int, K> — one right-shift and one add. No division,
132// no multiplication, no buffer. K=2 (alpha=1/4) or K=3 (alpha=1/8).
133
135#include "fl/stl/span.h"
136
137// Detail impl headers — IIR
146
147// Detail impl headers — Multi-channel
149
150// Detail impl headers — FIR
160#include "fl/stl/noexcept.h"
161
162namespace fl {
163
164// ============================================================================
165// IIR filters (no buffer, live in fl:: namespace directly)
166// ============================================================================
167
168// First-order exponential (RC low-pass) smoother. Time-aware: takes dt so
169// it works at any sample rate. Requires one exp() call per update.
170//
171// update(): O(1) Memory: 2T
172//
173// ExponentialSmoother<float> ema(0.1f); // tau = 100 ms
174// void loop() {
175// float dt = millis_since_last / 1000.0f;
176// float smoothed = ema.update(analogRead(A0), dt);
177// }
178//
179// Larger tau = slower response. setTau() lets you change it at runtime.
180//
181// Fixed-point:
182// using FP = fl::fixed_point<16,16>;
183// ExponentialSmoother<FP> ema(FP(0.5f), FP(0.0f));
184// FP v = ema.update(FP(1.0f), FP(0.05f));
185template <typename T = float>
187 public:
188 explicit ExponentialSmoother(T tau_seconds, T initial = T(0))
189 : mImpl(tau_seconds, initial) {}
190 FASTLED_FORCE_INLINE T update(T input, T dt_seconds) { return mImpl.update(input, dt_seconds); }
191 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
192 FASTLED_FORCE_INLINE void reset(T initial = T(0)) { mImpl.reset(initial); }
193 FASTLED_FORCE_INLINE void setTau(T tau_seconds) { mImpl.setTau(tau_seconds); }
194 private:
196};
197
198// Asymmetric exponential smoother with separate time constants for rising
199// (attack) and falling (decay) signals. Picks the appropriate tau on each
200// update based on whether the input is above or below the current value.
201// Same exponential math as ExponentialSmoother, one exp() call per update.
202//
203// update(): O(1) Memory: 3T
204//
205// AttackDecayFilter<float> env(0.01f, 0.5f); // attack=10ms, decay=500ms
206// void loop() {
207// float dt = millis_since_last / 1000.0f;
208// float v = env.update(analogRead(A0), dt);
209// }
210//
211// Swap the tau values for slow-attack / fast-decay (e.g., peak-hold, gravity).
212template <typename T = float>
214 public:
215 AttackDecayFilter(T attack_tau, T decay_tau, T initial = T(0))
216 : mImpl(attack_tau, decay_tau, initial) {}
217 FASTLED_FORCE_INLINE T update(T input, T dt_seconds) { return mImpl.update(input, dt_seconds); }
218 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
219 FASTLED_FORCE_INLINE void reset(T initial = T(0)) { mImpl.reset(initial); }
220 FASTLED_FORCE_INLINE void setAttackTau(T tau_seconds) { mImpl.setAttackTau(tau_seconds); }
221 FASTLED_FORCE_INLINE void setDecayTau(T tau_seconds) { mImpl.setDecayTau(tau_seconds); }
222 private:
224};
225
226// Single-pole DC offset removal filter. Essential for audio input processing
227// (microphone, line-in) where hardware bias or ADC offset adds a constant DC
228// component. One multiply and two adds per sample, no exp().
229//
230// update(): O(1) Memory: 2T
231//
232// DCBlocker<float> dc; // default R = 0.995
233// void loop() {
234// float clean = dc.update(micSample); // DC removed
235// }
236//
237// R controls cutoff: closer to 1.0 = lower cutoff. R=0.995 gives ~1.6 Hz
238// at 1 kHz sample rate. R=0.99 gives ~3.2 Hz. R=0.9 is aggressive.
239template <typename T = float>
241 public:
242 explicit DCBlocker(T r = T(0.995f)) : mImpl(r) {}
243 FASTLED_FORCE_INLINE T update(T input) { return mImpl.update(input); }
244 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
245 FASTLED_FORCE_INLINE void reset() { mImpl.reset(); }
246 FASTLED_FORCE_INLINE void setR(T r) { mImpl.setR(r); }
247 private:
249};
250
251// Shift-based EMA: alpha = 1/(2^K). The cheapest filter here — one
252// bit-shift and one add for integer types, no exp() or division.
253//
254// update(): O(1) Memory: 1T
255//
256// LeakyIntegrator<int, 3> li; // alpha = 1/8
257// void loop() {
258// int smoothed = li.update(rawADC);
259// }
260//
261// For floats it divides by 2^K instead. K=2 (alpha=0.25) is a good default.
262template <typename T = float, int K = 2>
264 public:
266 explicit LeakyIntegrator(T initial) : mImpl(initial) {}
267 FASTLED_FORCE_INLINE T update(T input) { return mImpl.update(input); }
268 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
269 FASTLED_FORCE_INLINE void reset(T initial = T(0)) { mImpl.reset(initial); }
270 private:
272};
273
274// S stages of EMA in series for a near-Gaussian impulse response without a
275// buffer. One exp() call shared across all stages. More stages = smoother
276// (but slower to respond).
277//
278// update(): O(S) Memory: S*T (S = Stages, typically 2-4)
279//
280// CascadedEMA<float, 3> smooth(0.05f); // 3-stage, tau = 50 ms
281// void loop() {
282// float v = smooth.update(sensorValue, dt);
283// }
284//
285// Fixed-point:
286// using FP = fl::fixed_point<16,16>;
287// CascadedEMA<FP, 2> smooth(FP(0.1f), FP(0.0f));
288// FP v = smooth.update(FP(reading), FP(dt));
289template <typename T = float, int Stages = 2>
291 public:
292 explicit CascadedEMA(T tau_seconds, T initial = T(0))
293 : mImpl(tau_seconds, initial) {}
294 FASTLED_FORCE_INLINE T update(T input, T dt_seconds) { return mImpl.update(input, dt_seconds); }
295 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
296 FASTLED_FORCE_INLINE void reset(T initial = T(0)) { mImpl.reset(initial); }
297 FASTLED_FORCE_INLINE void setTau(T tau_seconds) { mImpl.setTau(tau_seconds); }
298 private:
300};
301
302// Second-order IIR filter (5 multiplies + 4 adds per sample, no exp()).
303// Factory methods for common filter types:
304// butterworth() — low-pass, -12 dB/octave above cutoff
305// highpass() — high-pass, -12 dB/octave below cutoff
306// bandpass() — band-pass, passes center frequency, Q controls width
307// notch() — band-reject, removes center frequency (50/60 Hz hum)
308//
309// update(): O(1) Memory: 5T (coefficients) + 4T (state)
310//
311// auto lpf = BiquadFilter<float>::butterworth(100.0f, 1000.0f);
312// auto hpf = BiquadFilter<float>::highpass(20.0f, 44100.0f);
313// auto bpf = BiquadFilter<float>::bandpass(440.0f, 44100.0f, 2.0f);
314// auto hum = BiquadFilter<float>::notch(60.0f, 44100.0f, 30.0f);
315//
316// You can also construct directly with custom coefficients (b0,b1,b2,a1,a2).
317template <typename T = float>
320 BiquadFilter(const Impl& impl) : mImpl(impl) {}
321 public:
322 BiquadFilter(T b0, T b1, T b2, T a1, T a2)
323 : mImpl(b0, b1, b2, a1, a2) {}
324 static BiquadFilter butterworth(float cutoff_hz, float sample_rate) {
325 return BiquadFilter(Impl::butterworth(cutoff_hz, sample_rate));
326 }
327 static BiquadFilter highpass(float cutoff_hz, float sample_rate) {
328 return BiquadFilter(Impl::highpass(cutoff_hz, sample_rate));
329 }
330 static BiquadFilter bandpass(float center_hz, float sample_rate,
331 float q = 1.0f) {
332 return BiquadFilter(Impl::bandpass(center_hz, sample_rate, q));
333 }
334 static BiquadFilter notch(float center_hz, float sample_rate,
335 float q = 1.0f) {
336 return BiquadFilter(Impl::notch(center_hz, sample_rate, q));
337 }
338 FASTLED_FORCE_INLINE T update(T input) { return mImpl.update(input); }
339 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
340 FASTLED_FORCE_INLINE void reset() { mImpl.reset(); }
341 private:
343};
344
345// 1-D scalar Kalman filter. Balances process noise (Q) against measurement
346// noise (R) to produce an optimal estimate. One division per update.
347//
348// update(): O(1) Memory: 4T
349//
350// KalmanFilter<float> kf(0.01f, 0.1f); // Q = 0.01, R = 0.1
351// void loop() {
352// float estimate = kf.update(noisyMeasurement);
353// }
354//
355// Fixed-point:
356// using FP = fl::fixed_point<16,16>;
357// KalmanFilter<FP> kf(FP(0.01f), FP(0.1f));
358// FP estimate = kf.update(FP(measurement));
359template <typename T = float>
361 public:
362 KalmanFilter(T process_noise, T measurement_noise, T initial = T(0))
363 : mImpl(process_noise, measurement_noise, initial) {}
364 FASTLED_FORCE_INLINE T update(T measurement) { return mImpl.update(measurement); }
365 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
366 FASTLED_FORCE_INLINE void reset(T initial = T(0)) { mImpl.reset(initial); }
367 private:
369};
370
371// Adaptive velocity-based smoother (1-Euro filter). Two EMA steps plus a
372// derivative estimate per update. Smooths slow movements heavily but lets
373// fast movements through with minimal lag. Ideal for VR/graphics/pointer.
374//
375// update(): O(1) Memory: 5T
376//
377// OneEuroFilter<float> oef(1.0f, 0.5f); // min_cutoff=1 Hz, beta=0.5
378// void loop() {
379// float dt = millis_since_last / 1000.0f;
380// float smoothed = oef.update(pointerX, dt);
381// }
382//
383// Higher beta = less lag on fast motions but more jitter at rest.
384template <typename T = float>
386 public:
387 OneEuroFilter(T min_cutoff, T beta, T d_cutoff = T(1.0f))
388 : mImpl(min_cutoff, beta, d_cutoff) {}
389 FASTLED_FORCE_INLINE T update(T input, T dt) { return mImpl.update(input, dt); }
390 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
391 FASTLED_FORCE_INLINE void reset(T initial = T(0)) { mImpl.reset(initial); }
392 private:
394};
395
396// ============================================================================
397// FIR filter public API — composition with impl
398// N > 0: static (inlined) buffer, default-constructible
399// N == 0: dynamic buffer, requires capacity at construction
400// ============================================================================
401
402// Simple moving average using a running sum: subtracts the oldest sample
403// and adds the newest, so each update is O(1) regardless of window size.
404// Good general-purpose smoother with equal weight on all samples.
405//
406// update(): O(1) Memory: N*T
407//
408// MovingAverage<float> ma; // default N = 8
409// MovingAverage<float, 16> ma16; // explicit 16-sample window
410// MovingAverage<float, 0> dyn(32); // dynamic: capacity set at runtime
411// void loop() {
412// ma.update(analogRead(A0));
413// float smoothed = ma.value(); // average of last 8 readings
414// }
415//
416// Fixed-point:
417// using FP = fl::fixed_point<16,16>;
418// MovingAverage<FP, 4> ma;
419// ma.update(FP(reading));
420// float result = ma.value().to_float();
421template <typename T = float, fl::size N = 8>
423 public:
426 FASTLED_FORCE_INLINE T update(T input) { return mImpl.update(input); }
427 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
428 FASTLED_FORCE_INLINE void reset() { mImpl.reset(); }
429 FASTLED_FORCE_INLINE bool full() const { return mImpl.full(); }
430 FASTLED_FORCE_INLINE fl::size size() const { return mImpl.size(); }
431 FASTLED_FORCE_INLINE fl::size capacity() const { return mImpl.capacity(); }
432 FASTLED_FORCE_INLINE void resize(fl::size new_capacity) { mImpl.resize(new_capacity); }
433 private:
435};
436
437// Sliding-window median: maintains a sorted copy of the window via binary
438// search + shift. Rejects outliers by returning the middle value. Best for
439// impulse/spike noise (e.g., bad sensor readings, random spikes).
440// N must be odd (static_assert for compile-time, auto-corrected at runtime).
441//
442// update(): O(N) Memory: 2*N*T (ring + sorted copy)
443//
444// MedianFilter<float> mf; // default N = 5
445// MedianFilter<float, 0> dyn(11); // dynamic, odd N required
446// void loop() {
447// mf.update(distanceSensor());
448// float clean = mf.value(); // spike-free output
449// }
450//
451// Fixed-point:
452// using FP = fl::fixed_point<16,16>;
453// MedianFilter<FP, 5> mf;
454// mf.update(FP(reading));
455// float result = mf.value().to_float();
456template <typename T = float, fl::size N = 5>
458 public:
461 FASTLED_FORCE_INLINE T update(T input) { return mImpl.update(input); }
462 FASTLED_FORCE_INLINE T update(fl::span<const T> values) { return mImpl.update(values); }
463 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
464 FASTLED_FORCE_INLINE void reset() { mImpl.reset(); }
465 FASTLED_FORCE_INLINE fl::size size() const { return mImpl.size(); }
466 FASTLED_FORCE_INLINE fl::size capacity() const { return mImpl.capacity(); }
467 FASTLED_FORCE_INLINE void resize(fl::size new_capacity) { mImpl.resize(new_capacity); }
468 private:
470};
471
472// Linearly weighted moving average: recomputes weighted sum each update.
473// Weights are [1, 2, 3, ..., N] so newer samples dominate. Use when recent
474// data matters more than older.
475//
476// update(): O(N) Memory: N*T
477//
478// WeightedMovingAverage<float> wma; // default N = 8
479// void loop() {
480// wma.update(sensorValue);
481// float smoothed = wma.value(); // recent-biased average
482// }
483//
484// Fixed-point:
485// using FP = fl::fixed_point<16,16>;
486// WeightedMovingAverage<FP, 3> wma;
487// wma.update(FP(reading));
488// float result = wma.value().to_float();
489template <typename T = float, fl::size N = 8>
491 public:
494 FASTLED_FORCE_INLINE T update(T input) { return mImpl.update(input); }
495 FASTLED_FORCE_INLINE T update(fl::span<const T> values) { return mImpl.update(values); }
496 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
497 FASTLED_FORCE_INLINE void reset() { mImpl.reset(); }
498 FASTLED_FORCE_INLINE bool full() const { return mImpl.full(); }
499 FASTLED_FORCE_INLINE fl::size size() const { return mImpl.size(); }
500 FASTLED_FORCE_INLINE fl::size capacity() const { return mImpl.capacity(); }
501 FASTLED_FORCE_INLINE void resize(fl::size new_capacity) { mImpl.resize(new_capacity); }
502 private:
504};
505
506// Triangular (tent) weighted average: recomputes tent-shaped weights each
507// update. Weights for N=5: [1, 2, 3, 2, 1]. Smoother than MovingAverage,
508// cheaper than Gaussian. N must be odd for a symmetric peak
509// (static_assert for compile-time, auto-corrected at runtime).
510//
511// update(): O(N) Memory: N*T
512//
513// TriangularFilter<float> tf; // default N = 7
514// void loop() {
515// tf.update(sensorValue);
516// float smoothed = tf.value();
517// }
518//
519// Fixed-point:
520// using FP = fl::fixed_point<16,16>;
521// TriangularFilter<FP, 5> tf;
522// tf.update(FP(reading));
523// float result = tf.value().to_float();
524template <typename T = float, fl::size N = 7>
526 public:
529 FASTLED_FORCE_INLINE T update(T input) { return mImpl.update(input); }
530 FASTLED_FORCE_INLINE T update(fl::span<const T> values) { return mImpl.update(values); }
531 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
532 FASTLED_FORCE_INLINE void reset() { mImpl.reset(); }
533 FASTLED_FORCE_INLINE bool full() const { return mImpl.full(); }
534 FASTLED_FORCE_INLINE fl::size size() const { return mImpl.size(); }
535 FASTLED_FORCE_INLINE fl::size capacity() const { return mImpl.capacity(); }
536 FASTLED_FORCE_INLINE void resize(fl::size new_capacity) { mImpl.resize(new_capacity); }
537 private:
539};
540
541// Binomial-coefficient Gaussian approximation: recomputes Pascal's-triangle
542// weights each update. Weights for N=5: [1, 4, 6, 4, 1]. Best
543// frequency-domain response of the simple FIR filters.
544//
545// update(): O(N) Memory: N*T
546//
547// GaussianFilter<float> gf; // default N = 5
548// void loop() {
549// gf.update(sensorValue);
550// float smoothed = gf.value();
551// }
552//
553// Fixed-point:
554// using FP = fl::fixed_point<16,16>;
555// GaussianFilter<FP, 5> gf;
556// for (int i = 0; i < 5; ++i) gf.update(FP(7.0f));
557// float result = gf.value().to_float(); // ≈ 7.0
558template <typename T = float, fl::size N = 5>
560 public:
563 FASTLED_FORCE_INLINE T update(T input) { return mImpl.update(input); }
564 FASTLED_FORCE_INLINE T update(fl::span<const T> values) { return mImpl.update(values); }
565 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
566 FASTLED_FORCE_INLINE void reset() { mImpl.reset(); }
567 FASTLED_FORCE_INLINE bool full() const { return mImpl.full(); }
568 FASTLED_FORCE_INLINE fl::size size() const { return mImpl.size(); }
569 FASTLED_FORCE_INLINE fl::size capacity() const { return mImpl.capacity(); }
570 FASTLED_FORCE_INLINE void resize(fl::size new_capacity) { mImpl.resize(new_capacity); }
571 private:
573};
574
575// Alpha-trimmed mean: maintains a sorted window via binary search + shift,
576// trims extreme values from both ends, and averages the middle. With N=7
577// and trim_count=1, drops min and max, averages the remaining 5. Robust
578// to outliers while still averaging.
579//
580// update(): O(N) Memory: 2*N*T (ring + sorted copy)
581//
582// AlphaTrimmedMean<float> atm(1); // default N=7, trim 1 each end
583// AlphaTrimmedMean<float, 5> atm5(2); // N=5, trim 2 each end (= median)
584// void loop() {
585// atm.update(sensorValue);
586// float robust = atm.value();
587// }
588//
589// Fixed-point:
590// using FP = fl::fixed_point<16,16>;
591// AlphaTrimmedMean<FP, 5> atm(1);
592// atm.update(FP(reading));
593// float result = atm.value().to_float();
594template <typename T = float, fl::size N = 7>
596 public:
597 explicit AlphaTrimmedMean(fl::size trim_count = 1) : mImpl(trim_count) {}
598 AlphaTrimmedMean(fl::size capacity, fl::size trim_count) : mImpl(capacity, trim_count) {}
599 FASTLED_FORCE_INLINE T update(T input) { return mImpl.update(input); }
600 FASTLED_FORCE_INLINE T update(fl::span<const T> values) { return mImpl.update(values); }
601 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
602 FASTLED_FORCE_INLINE void reset() { mImpl.reset(); }
603 FASTLED_FORCE_INLINE fl::size size() const { return mImpl.size(); }
604 FASTLED_FORCE_INLINE fl::size capacity() const { return mImpl.capacity(); }
605 FASTLED_FORCE_INLINE void resize(fl::size new_capacity, fl::size trim_count) { mImpl.resize(new_capacity, trim_count); }
606 private:
608};
609
610// Hampel filter: maintains a sorted window, computes the median and median
611// absolute deviation (MAD), then replaces values that deviate beyond
612// threshold * MAD with the median. N must be odd (static_assert for
613// compile-time, auto-corrected at runtime).
614//
615// update(): O(N) Memory: 2*N*T (ring + sorted copy)
616//
617// HampelFilter<float> hf(3.0f); // default N=5, threshold=3
618// HampelFilter<float, 7> hf7(2.0f); // 7-sample window, tighter threshold
619// void loop() {
620// float clean = hf.update(noisyValue); // outliers replaced with median
621// }
622template <typename T = float, fl::size N = 5>
624 public:
625 explicit HampelFilter(T threshold = T(3.0f)) : mImpl(threshold) {}
626 HampelFilter(fl::size capacity, T threshold) : mImpl(capacity, threshold) {}
627 FASTLED_FORCE_INLINE T update(T input) { return mImpl.update(input); }
628 FASTLED_FORCE_INLINE T update(fl::span<const T> values) { return mImpl.update(values); }
629 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
630 FASTLED_FORCE_INLINE void reset() { mImpl.reset(); }
631 FASTLED_FORCE_INLINE fl::size size() const { return mImpl.size(); }
632 FASTLED_FORCE_INLINE fl::size capacity() const { return mImpl.capacity(); }
633 FASTLED_FORCE_INLINE void resize(fl::size new_capacity) { mImpl.resize(new_capacity); }
634 private:
636};
637
638// Savitzky-Golay: fits a local quadratic polynomial to the window and returns
639// the center value. Recomputes polynomial weights each update. Smooths noise
640// while preserving peaks, edges, and signal shape better than averaging.
641// N must be odd (static_assert for compile-time, auto-corrected at runtime).
642//
643// update(): O(N) Memory: N*T
644//
645// SavitzkyGolayFilter<float> sg; // default N = 5
646// SavitzkyGolayFilter<float, 7> sg7; // wider window = more smoothing
647// void loop() {
648// sg.update(spectrumBin);
649// float smoothed = sg.value(); // peaks preserved
650// }
651//
652// Fixed-point:
653// using FP = fl::fixed_point<16,16>;
654// SavitzkyGolayFilter<FP, 5> sg;
655// for (int i = 0; i < 5; ++i) sg.update(FP(3.0f));
656// float result = sg.value().to_float(); // ≈ 3.0
657template <typename T = float, fl::size N = 5>
659 public:
662 FASTLED_FORCE_INLINE T update(T input) { return mImpl.update(input); }
663 FASTLED_FORCE_INLINE T update(fl::span<const T> values) { return mImpl.update(values); }
664 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
665 FASTLED_FORCE_INLINE void reset() { mImpl.reset(); }
666 FASTLED_FORCE_INLINE bool full() const { return mImpl.full(); }
667 FASTLED_FORCE_INLINE fl::size size() const { return mImpl.size(); }
668 FASTLED_FORCE_INLINE fl::size capacity() const { return mImpl.capacity(); }
669 FASTLED_FORCE_INLINE void resize(fl::size new_capacity) { mImpl.resize(new_capacity); }
670 private:
672};
673
674// Bilateral filter: edge-preserving smoother weighted by value similarity.
675// Computes exp() per sample in the window each update. Each sample's weight
676// depends on how close its value is to the newest sample. Small sigma_range
677// = only very similar values contribute (sharp edges kept). Large sigma_range
678// = all values contribute equally (acts like a box filter).
679//
680// update(): O(N) Memory: N*T (one exp() per sample per update)
681//
682// BilateralFilter<float> bf(1.0f); // default N=5, sigma=1.0
683// BilateralFilter<float, 7> bf7(0.5f); // 7-sample, tighter similarity
684// void loop() {
685// bf.update(ledBrightness);
686// float smoothed = bf.value(); // edges preserved
687// }
688template <typename T = float, fl::size N = 5>
690 public:
691 explicit BilateralFilter(T sigma_range = T(1.0f)) : mImpl(sigma_range) {}
692 BilateralFilter(fl::size capacity, T sigma_range) : mImpl(capacity, sigma_range) {}
693 FASTLED_FORCE_INLINE T update(T input) { return mImpl.update(input); }
694 FASTLED_FORCE_INLINE T update(fl::span<const T> values) { return mImpl.update(values); }
695 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
696 FASTLED_FORCE_INLINE void reset() { mImpl.reset(); }
697 FASTLED_FORCE_INLINE bool full() const { return mImpl.full(); }
698 FASTLED_FORCE_INLINE fl::size size() const { return mImpl.size(); }
699 FASTLED_FORCE_INLINE fl::size capacity() const { return mImpl.capacity(); }
700 FASTLED_FORCE_INLINE void resize(fl::size new_capacity) { mImpl.resize(new_capacity); }
701 private:
703};
704
705// ============================================================================
706// Multi-channel filters
707// ============================================================================
708
709// Multi-channel EMA with per-bin relative deviation measurement. Feeds a
710// span of values (e.g., FFT bins, sensor array) each update, smooths each
711// channel independently with an EMA, and returns the mean relative deviation
712// from the smoothed values. Detects temporal instability across channels.
713//
714// update(): O(N) Memory: N*T (one EMA state per channel)
715//
716// Use cases:
717// - Audio: detect spectral change over time (voice vs steady instrument)
718// - Sensors: detect unstable channels in a multi-sensor array
719//
720// SpectralVariance<float> sv(0.2f); // alpha=0.2, 5-frame window
721// void loop() {
722// float variance = sv.update(fftBins); // mean per-bin relative change
723// if (variance > threshold) { /* spectral content is changing */ }
724// }
725//
726// Alpha controls tracking speed:
727// 0.1 = slow (10-frame window), 0.2 = moderate (default), 0.5 = fast
728template <typename T = float>
730 public:
731 explicit SpectralVariance(T alpha = T(0.2f), T floor = T(1e-4f))
732 : mImpl(alpha, floor) {}
733 FASTLED_FORCE_INLINE T update(fl::span<const T> bins) { return mImpl.update(bins); }
734 FASTLED_FORCE_INLINE T value() const { return mImpl.value(); }
735 FASTLED_FORCE_INLINE void reset() { mImpl.reset(); }
736 FASTLED_FORCE_INLINE void setAlpha(T alpha) { mImpl.setAlpha(alpha); }
738 FASTLED_FORCE_INLINE fl::size size() const { return mImpl.size(); }
739 private:
741};
742
743} // namespace fl
AlphaTrimmedMean(fl::size capacity, fl::size trim_count)
Definition filter.h:598
FASTLED_FORCE_INLINE T value() const
Definition filter.h:601
FASTLED_FORCE_INLINE void reset()
Definition filter.h:602
FASTLED_FORCE_INLINE T update(T input)
Definition filter.h:599
AlphaTrimmedMean(fl::size trim_count=1)
Definition filter.h:597
FASTLED_FORCE_INLINE void resize(fl::size new_capacity, fl::size trim_count)
Definition filter.h:605
FASTLED_FORCE_INLINE fl::size size() const
Definition filter.h:603
FASTLED_FORCE_INLINE fl::size capacity() const
Definition filter.h:604
detail::AlphaTrimmedMeanImpl< T, N > mImpl
Definition filter.h:607
FASTLED_FORCE_INLINE T update(fl::span< const T > values)
Definition filter.h:600
AttackDecayFilter(T attack_tau, T decay_tau, T initial=T(0))
Definition filter.h:215
FASTLED_FORCE_INLINE void reset(T initial=T(0))
Definition filter.h:219
detail::AttackDecayFilterImpl< T > mImpl
Definition filter.h:223
FASTLED_FORCE_INLINE void setDecayTau(T tau_seconds)
Definition filter.h:221
FASTLED_FORCE_INLINE T value() const
Definition filter.h:218
FASTLED_FORCE_INLINE T update(T input, T dt_seconds)
Definition filter.h:217
FASTLED_FORCE_INLINE void setAttackTau(T tau_seconds)
Definition filter.h:220
FASTLED_FORCE_INLINE T update(fl::span< const T > values)
Definition filter.h:694
FASTLED_FORCE_INLINE fl::size capacity() const
Definition filter.h:699
FASTLED_FORCE_INLINE T value() const
Definition filter.h:695
FASTLED_FORCE_INLINE T update(T input)
Definition filter.h:693
FASTLED_FORCE_INLINE bool full() const
Definition filter.h:697
FASTLED_FORCE_INLINE void reset()
Definition filter.h:696
FASTLED_FORCE_INLINE void resize(fl::size new_capacity)
Definition filter.h:700
FASTLED_FORCE_INLINE fl::size size() const
Definition filter.h:698
BilateralFilter(T sigma_range=T(1.0f))
Definition filter.h:691
BilateralFilter(fl::size capacity, T sigma_range)
Definition filter.h:692
detail::BilateralFilterImpl< T, N > mImpl
Definition filter.h:702
FASTLED_FORCE_INLINE void reset()
Definition filter.h:340
BiquadFilter(T b0, T b1, T b2, T a1, T a2)
Definition filter.h:322
detail::BiquadFilterImpl< T > Impl
Definition filter.h:319
BiquadFilter(const Impl &impl)
Definition filter.h:320
static BiquadFilter bandpass(float center_hz, float sample_rate, float q=1.0f)
Definition filter.h:330
static BiquadFilter notch(float center_hz, float sample_rate, float q=1.0f)
Definition filter.h:334
FASTLED_FORCE_INLINE T value() const
Definition filter.h:339
FASTLED_FORCE_INLINE T update(T input)
Definition filter.h:338
static BiquadFilter butterworth(float cutoff_hz, float sample_rate)
Definition filter.h:324
static BiquadFilter highpass(float cutoff_hz, float sample_rate)
Definition filter.h:327
FASTLED_FORCE_INLINE void setTau(T tau_seconds)
Definition filter.h:297
CascadedEMA(T tau_seconds, T initial=T(0))
Definition filter.h:292
detail::CascadedEMAImpl< T, Stages > mImpl
Definition filter.h:299
FASTLED_FORCE_INLINE T value() const
Definition filter.h:295
FASTLED_FORCE_INLINE void reset(T initial=T(0))
Definition filter.h:296
FASTLED_FORCE_INLINE T update(T input, T dt_seconds)
Definition filter.h:294
FASTLED_FORCE_INLINE T value() const
Definition filter.h:244
DCBlocker(T r=T(0.995f))
Definition filter.h:242
FASTLED_FORCE_INLINE T update(T input)
Definition filter.h:243
FASTLED_FORCE_INLINE void reset()
Definition filter.h:245
detail::DCBlockerImpl< T > mImpl
Definition filter.h:248
FASTLED_FORCE_INLINE void setR(T r)
Definition filter.h:246
FASTLED_FORCE_INLINE void setTau(T tau_seconds)
Definition filter.h:193
ExponentialSmoother(T tau_seconds, T initial=T(0))
Definition filter.h:188
detail::ExponentialSmootherImpl< T > mImpl
Definition filter.h:195
FASTLED_FORCE_INLINE T update(T input, T dt_seconds)
Definition filter.h:190
FASTLED_FORCE_INLINE void reset(T initial=T(0))
Definition filter.h:192
FASTLED_FORCE_INLINE T value() const
Definition filter.h:191
FASTLED_FORCE_INLINE void resize(fl::size new_capacity)
Definition filter.h:570
FASTLED_FORCE_INLINE fl::size size() const
Definition filter.h:568
FASTLED_FORCE_INLINE T value() const
Definition filter.h:565
FASTLED_FORCE_INLINE T update(fl::span< const T > values)
Definition filter.h:564
GaussianFilter() FL_NOEXCEPT=default
detail::GaussianFilterImpl< T, N > mImpl
Definition filter.h:572
FASTLED_FORCE_INLINE fl::size capacity() const
Definition filter.h:569
FASTLED_FORCE_INLINE T update(T input)
Definition filter.h:563
FASTLED_FORCE_INLINE bool full() const
Definition filter.h:567
FASTLED_FORCE_INLINE void reset()
Definition filter.h:566
FASTLED_FORCE_INLINE T update(T input)
Definition filter.h:627
FASTLED_FORCE_INLINE T update(fl::span< const T > values)
Definition filter.h:628
detail::HampelFilterImpl< T, N > mImpl
Definition filter.h:635
FASTLED_FORCE_INLINE T value() const
Definition filter.h:629
FASTLED_FORCE_INLINE fl::size capacity() const
Definition filter.h:632
FASTLED_FORCE_INLINE void resize(fl::size new_capacity)
Definition filter.h:633
FASTLED_FORCE_INLINE fl::size size() const
Definition filter.h:631
HampelFilter(fl::size capacity, T threshold)
Definition filter.h:626
HampelFilter(T threshold=T(3.0f))
Definition filter.h:625
FASTLED_FORCE_INLINE void reset()
Definition filter.h:630
FASTLED_FORCE_INLINE void reset(T initial=T(0))
Definition filter.h:366
KalmanFilter(T process_noise, T measurement_noise, T initial=T(0))
Definition filter.h:362
FASTLED_FORCE_INLINE T update(T measurement)
Definition filter.h:364
detail::KalmanFilterImpl< T > mImpl
Definition filter.h:368
FASTLED_FORCE_INLINE T value() const
Definition filter.h:365
LeakyIntegrator() FL_NOEXCEPT=default
FASTLED_FORCE_INLINE void reset(T initial=T(0))
Definition filter.h:269
detail::LeakyIntegratorImpl< T, K > mImpl
Definition filter.h:271
FASTLED_FORCE_INLINE T value() const
Definition filter.h:268
FASTLED_FORCE_INLINE T update(T input)
Definition filter.h:267
FASTLED_FORCE_INLINE T value() const
Definition filter.h:463
FASTLED_FORCE_INLINE fl::size size() const
Definition filter.h:465
FASTLED_FORCE_INLINE T update(T input)
Definition filter.h:461
FASTLED_FORCE_INLINE void resize(fl::size new_capacity)
Definition filter.h:467
FASTLED_FORCE_INLINE fl::size capacity() const
Definition filter.h:466
FASTLED_FORCE_INLINE T update(fl::span< const T > values)
Definition filter.h:462
detail::MedianFilterImpl< T, N > mImpl
Definition filter.h:469
MedianFilter() FL_NOEXCEPT=default
FASTLED_FORCE_INLINE void reset()
Definition filter.h:464
detail::MovingAverageImpl< T, N > mImpl
Definition filter.h:434
FASTLED_FORCE_INLINE fl::size size() const
Definition filter.h:430
FASTLED_FORCE_INLINE void reset()
Definition filter.h:428
FASTLED_FORCE_INLINE T update(T input)
Definition filter.h:426
FASTLED_FORCE_INLINE fl::size capacity() const
Definition filter.h:431
MovingAverage() FL_NOEXCEPT=default
FASTLED_FORCE_INLINE void resize(fl::size new_capacity)
Definition filter.h:432
FASTLED_FORCE_INLINE bool full() const
Definition filter.h:429
FASTLED_FORCE_INLINE T value() const
Definition filter.h:427
detail::OneEuroFilterImpl< T > mImpl
Definition filter.h:393
FASTLED_FORCE_INLINE T value() const
Definition filter.h:390
FASTLED_FORCE_INLINE void reset(T initial=T(0))
Definition filter.h:391
OneEuroFilter(T min_cutoff, T beta, T d_cutoff=T(1.0f))
Definition filter.h:387
FASTLED_FORCE_INLINE T update(T input, T dt)
Definition filter.h:389
FASTLED_FORCE_INLINE fl::size size() const
Definition filter.h:667
FASTLED_FORCE_INLINE T update(fl::span< const T > values)
Definition filter.h:663
FASTLED_FORCE_INLINE T update(T input)
Definition filter.h:662
FASTLED_FORCE_INLINE fl::size capacity() const
Definition filter.h:668
FASTLED_FORCE_INLINE void resize(fl::size new_capacity)
Definition filter.h:669
SavitzkyGolayFilter() FL_NOEXCEPT=default
FASTLED_FORCE_INLINE bool full() const
Definition filter.h:666
detail::SavitzkyGolayFilterImpl< T, N > mImpl
Definition filter.h:671
FASTLED_FORCE_INLINE void reset()
Definition filter.h:665
FASTLED_FORCE_INLINE T value() const
Definition filter.h:664
FASTLED_FORCE_INLINE fl::size size() const
Definition filter.h:738
FASTLED_FORCE_INLINE void setFloor(T floor)
Definition filter.h:737
FASTLED_FORCE_INLINE T value() const
Definition filter.h:734
detail::SpectralVarianceImpl< T > mImpl
Definition filter.h:740
FASTLED_FORCE_INLINE T update(fl::span< const T > bins)
Definition filter.h:733
FASTLED_FORCE_INLINE void reset()
Definition filter.h:735
SpectralVariance(T alpha=T(0.2f), T floor=T(1e-4f))
Definition filter.h:731
FASTLED_FORCE_INLINE void setAlpha(T alpha)
Definition filter.h:736
FASTLED_FORCE_INLINE fl::size capacity() const
Definition filter.h:535
detail::TriangularFilterImpl< T, N > mImpl
Definition filter.h:538
FASTLED_FORCE_INLINE T update(fl::span< const T > values)
Definition filter.h:530
FASTLED_FORCE_INLINE T value() const
Definition filter.h:531
FASTLED_FORCE_INLINE void resize(fl::size new_capacity)
Definition filter.h:536
TriangularFilter() FL_NOEXCEPT=default
FASTLED_FORCE_INLINE T update(T input)
Definition filter.h:529
FASTLED_FORCE_INLINE fl::size size() const
Definition filter.h:534
FASTLED_FORCE_INLINE bool full() const
Definition filter.h:533
FASTLED_FORCE_INLINE void reset()
Definition filter.h:532
WeightedMovingAverage() FL_NOEXCEPT=default
FASTLED_FORCE_INLINE T update(T input)
Definition filter.h:494
FASTLED_FORCE_INLINE void reset()
Definition filter.h:497
FASTLED_FORCE_INLINE fl::size size() const
Definition filter.h:499
detail::WeightedMovingAverageImpl< T, N > mImpl
Definition filter.h:503
FASTLED_FORCE_INLINE void resize(fl::size new_capacity)
Definition filter.h:501
FASTLED_FORCE_INLINE T value() const
Definition filter.h:496
FASTLED_FORCE_INLINE T update(fl::span< const T > values)
Definition filter.h:495
FASTLED_FORCE_INLINE fl::size capacity() const
Definition filter.h:500
FASTLED_FORCE_INLINE bool full() const
Definition filter.h:498
static BiquadFilterImpl highpass(float cutoff_hz, float sample_rate)
static BiquadFilterImpl butterworth(float cutoff_hz, float sample_rate)
static BiquadFilterImpl bandpass(float center_hz, float sample_rate, float q=1.0f)
static BiquadFilterImpl notch(float center_hz, float sample_rate, float q=1.0f)
Multi-channel EMA with per-bin relative deviation measurement.
constexpr enable_if< is_fixed_point< T >::value, T >::type floor(T x) FL_NOEXCEPT
Base definition for an LED controller.
Definition crgb.hpp:179
#define FASTLED_FORCE_INLINE
#define FL_NOEXCEPT