Skip to main content

embedded_dsp/
filtering.rs

1//! Digital filtering functions (FIR, Biquad IIR Direct Form I & II, LMS Adaptive Filter, Convolution, Correlation).
2
3use crate::types::*;
4
5// --- FIR Filter ---
6
7/// Instance structure for the floating-point FIR filter.
8pub struct FirInstanceF32<'a> {
9    pub num_taps: u16,
10    pub coeffs: &'a [f32],
11    pub state: &'a mut [f32],
12}
13
14impl<'a> FirInstanceF32<'a> {
15    pub fn init(num_taps: u16, coeffs: &'a [f32], state: &'a mut [f32]) -> Self {
16        state.fill(0.0);
17        Self {
18            num_taps,
19            coeffs,
20            state,
21        }
22    }
23}
24
25pub fn fir_f32(instance: &mut FirInstanceF32, src: &[f32], dst: &mut [f32]) {
26    let num_taps = instance.num_taps as usize;
27    let block_size = src.len().min(dst.len());
28
29    for i in 0..block_size {
30        // Shift state
31        for k in (1..num_taps).rev() {
32            instance.state[k] = instance.state[k - 1];
33        }
34        instance.state[0] = src[i];
35
36        // Compute dot product with coefficients
37        let mut acc = 0.0f32;
38        for k in 0..num_taps {
39            acc += instance.state[k] * instance.coeffs[k];
40        }
41        dst[i] = acc;
42    }
43}
44
45/// Instance structure for the Q31 FIR filter.
46pub struct FirInstanceQ31<'a> {
47    pub num_taps: u16,
48    pub coeffs: &'a [q31],
49    pub state: &'a mut [q31],
50}
51
52impl<'a> FirInstanceQ31<'a> {
53    pub fn init(num_taps: u16, coeffs: &'a [q31], state: &'a mut [q31]) -> Self {
54        state.fill(0);
55        Self {
56            num_taps,
57            coeffs,
58            state,
59        }
60    }
61}
62
63pub fn fir_q31(instance: &mut FirInstanceQ31, src: &[q31], dst: &mut [q31]) {
64    let num_taps = instance.num_taps as usize;
65    let block_size = src.len().min(dst.len());
66
67    for i in 0..block_size {
68        for k in (1..num_taps).rev() {
69            instance.state[k] = instance.state[k - 1];
70        }
71        instance.state[0] = src[i];
72
73        let mut acc: i64 = 0;
74        for k in 0..num_taps {
75            acc += (instance.state[k] as i64 * instance.coeffs[k] as i64) >> 31;
76        }
77        dst[i] = acc.clamp(i32::MIN as i64, i32::MAX as i64) as q31;
78    }
79}
80
81/// Instance structure for the Q15 FIR filter.
82pub struct FirInstanceQ15<'a> {
83    pub num_taps: u16,
84    pub coeffs: &'a [q15],
85    pub state: &'a mut [q15],
86}
87
88impl<'a> FirInstanceQ15<'a> {
89    pub fn init(num_taps: u16, coeffs: &'a [q15], state: &'a mut [q15]) -> Self {
90        state.fill(0);
91        Self {
92            num_taps,
93            coeffs,
94            state,
95        }
96    }
97}
98
99pub fn fir_q15(instance: &mut FirInstanceQ15, src: &[q15], dst: &mut [q15]) {
100    let num_taps = instance.num_taps as usize;
101    let block_size = src.len().min(dst.len());
102
103    for i in 0..block_size {
104        for k in (1..num_taps).rev() {
105            instance.state[k] = instance.state[k - 1];
106        }
107        instance.state[0] = src[i];
108
109        let mut acc: i32 = 0;
110        for k in 0..num_taps {
111            acc += (instance.state[k] as i32 * instance.coeffs[k] as i32) >> 15;
112        }
113        dst[i] = acc.clamp(i16::MIN as i32, i16::MAX as i32) as q15;
114    }
115}
116
117// --- Biquad Cascade Direct Form I Filter ---
118
119/// Instance structure for the floating-point Biquad Cascade Direct Form I filter.
120pub struct BiquadCascadeInstanceF32<'a> {
121    pub num_stages: u8,
122    pub coeffs: &'a [f32],    // 5 * num_stages: [b0, b1, b2, a1, a2]
123    pub state: &'a mut [f32], // 4 * num_stages: [x[n-1], x[n-2], y[n-1], y[n-2]]
124}
125
126impl<'a> BiquadCascadeInstanceF32<'a> {
127    pub fn init(num_stages: u8, coeffs: &'a [f32], state: &'a mut [f32]) -> Self {
128        state.fill(0.0);
129        Self {
130            num_stages,
131            coeffs,
132            state,
133        }
134    }
135}
136
137pub fn biquad_cascade_df1_f32(
138    instance: &mut BiquadCascadeInstanceF32,
139    src: &[f32],
140    dst: &mut [f32],
141) {
142    let num_stages = instance.num_stages as usize;
143    let block_size = src.len().min(dst.len());
144
145    let mut in_val;
146    let mut out_val;
147
148    for i in 0..block_size {
149        in_val = src[i];
150        for stage in 0..num_stages {
151            let b0 = instance.coeffs[stage * 5];
152            let b1 = instance.coeffs[stage * 5 + 1];
153            let b2 = instance.coeffs[stage * 5 + 2];
154            let a1 = instance.coeffs[stage * 5 + 3];
155            let a2 = instance.coeffs[stage * 5 + 4];
156
157            let x1 = instance.state[stage * 4];
158            let x2 = instance.state[stage * 4 + 1];
159            let y1 = instance.state[stage * 4 + 2];
160            let y2 = instance.state[stage * 4 + 3];
161
162            out_val = b0 * in_val + b1 * x1 + b2 * x2 + a1 * y1 + a2 * y2;
163
164            instance.state[stage * 4 + 1] = x1;
165            instance.state[stage * 4] = in_val;
166            instance.state[stage * 4 + 3] = y1;
167            instance.state[stage * 4 + 2] = out_val;
168
169            in_val = out_val;
170        }
171        dst[i] = in_val;
172    }
173}
174
175// --- LMS Adaptive Filter ---
176
177/// Instance structure for the floating-point LMS adaptive filter.
178pub struct LmsInstanceF32<'a> {
179    pub num_taps: u16,
180    pub coeffs: &'a mut [f32],
181    pub state: &'a mut [f32],
182    pub mu: f32,
183}
184
185impl<'a> LmsInstanceF32<'a> {
186    pub fn init(num_taps: u16, coeffs: &'a mut [f32], state: &'a mut [f32], mu: f32) -> Self {
187        state.fill(0.0);
188        coeffs.fill(0.0);
189        Self {
190            num_taps,
191            coeffs,
192            state,
193            mu,
194        }
195    }
196}
197
198pub fn lms_f32(
199    instance: &mut LmsInstanceF32,
200    src: &[f32],
201    ref_signal: &[f32],
202    out: &mut [f32],
203    err: &mut [f32],
204) {
205    let num_taps = instance.num_taps as usize;
206    let block_size = src
207        .len()
208        .min(ref_signal.len())
209        .min(out.len())
210        .min(err.len());
211
212    for i in 0..block_size {
213        for k in (1..num_taps).rev() {
214            instance.state[k] = instance.state[k - 1];
215        }
216        instance.state[0] = src[i];
217
218        let mut acc = 0.0f32;
219        for k in 0..num_taps {
220            acc += instance.state[k] * instance.coeffs[k];
221        }
222        out[i] = acc;
223        let e = ref_signal[i] - acc;
224        err[i] = e;
225
226        // Update coefficients: w[n+1] = w[n] + 2 * mu * e[n] * x[n]
227        let alpha = 2.0 * instance.mu * e;
228        for k in 0..num_taps {
229            instance.coeffs[k] += alpha * instance.state[k];
230        }
231    }
232}
233
234// --- Convolution ---
235
236pub fn conv_f32(src_a: &[f32], src_b: &[f32], dst: &mut [f32]) {
237    let len_a = src_a.len();
238    let len_b = src_b.len();
239    let out_len = (len_a + len_b - 1).min(dst.len());
240
241    dst[..out_len].fill(0.0);
242    for i in 0..len_a {
243        for j in 0..len_b {
244            if i + j < out_len {
245                dst[i + j] += src_a[i] * src_b[j];
246            }
247        }
248    }
249}
250
251pub fn conv_q31(src_a: &[q31], src_b: &[q31], dst: &mut [q31]) {
252    let len_a = src_a.len();
253    let len_b = src_b.len();
254    let out_len = (len_a + len_b - 1).min(dst.len());
255
256    for n in 0..out_len {
257        let mut acc: i64 = 0;
258        let k_min = if n >= len_b - 1 { n - (len_b - 1) } else { 0 };
259        let k_max = n.min(len_a - 1);
260        for k in k_min..=k_max {
261            acc += (src_a[k] as i64 * src_b[n - k] as i64) >> 31;
262        }
263        dst[n] = acc.clamp(i32::MIN as i64, i32::MAX as i64) as q31;
264    }
265}
266
267pub fn conv_q15(src_a: &[q15], src_b: &[q15], dst: &mut [q15]) {
268    let len_a = src_a.len();
269    let len_b = src_b.len();
270    let out_len = (len_a + len_b - 1).min(dst.len());
271
272    for n in 0..out_len {
273        let mut acc: i32 = 0;
274        let k_min = if n >= len_b - 1 { n - (len_b - 1) } else { 0 };
275        let k_max = n.min(len_a - 1);
276        for k in k_min..=k_max {
277            acc += (src_a[k] as i32 * src_b[n - k] as i32) >> 15;
278        }
279        dst[n] = acc.clamp(i16::MIN as i32, i16::MAX as i32) as q15;
280    }
281}
282
283pub fn conv_q7(src_a: &[q7], src_b: &[q7], dst: &mut [q7]) {
284    let len_a = src_a.len();
285    let len_b = src_b.len();
286    let out_len = (len_a + len_b - 1).min(dst.len());
287
288    for n in 0..out_len {
289        let mut acc: i32 = 0;
290        let k_min = if n >= len_b - 1 { n - (len_b - 1) } else { 0 };
291        let k_max = n.min(len_a - 1);
292        for k in k_min..=k_max {
293            acc += (src_a[k] as i32 * src_b[n - k] as i32) >> 7;
294        }
295        dst[n] = acc.clamp(i8::MIN as i32, i8::MAX as i32) as q7;
296    }
297}
298
299// --- Correlation ---
300
301pub fn correlate_f32(src_a: &[f32], src_b: &[f32], dst: &mut [f32]) {
302    let len_a = src_a.len();
303    let len_b = src_b.len();
304    let out_len = (len_a + len_b - 1).min(dst.len());
305
306    dst[..out_len].fill(0.0);
307    for n in 0..out_len {
308        let mut acc = 0.0f32;
309        for k in 0..len_a {
310            let idx_b = (k as isize) + (len_b as isize - 1) - (n as isize);
311            if idx_b >= 0 && (idx_b as usize) < len_b {
312                acc += src_a[k] * src_b[idx_b as usize];
313            }
314        }
315        dst[n] = acc;
316    }
317}
318
319pub fn correlate_q31(src_a: &[q31], src_b: &[q31], dst: &mut [q31]) {
320    let len_a = src_a.len();
321    let len_b = src_b.len();
322    let out_len = (len_a + len_b - 1).min(dst.len());
323
324    for n in 0..out_len {
325        let mut acc: i64 = 0;
326        for k in 0..len_a {
327            let idx_b = (k as isize) + (len_b as isize - 1) - (n as isize);
328            if idx_b >= 0 && (idx_b as usize) < len_b {
329                acc += (src_a[k] as i64 * src_b[idx_b as usize] as i64) >> 31;
330            }
331        }
332        dst[n] = acc.clamp(i32::MIN as i64, i32::MAX as i64) as q31;
333    }
334}
335
336pub fn correlate_q15(src_a: &[q15], src_b: &[q15], dst: &mut [q15]) {
337    let len_a = src_a.len();
338    let len_b = src_b.len();
339    let out_len = (len_a + len_b - 1).min(dst.len());
340
341    for n in 0..out_len {
342        let mut acc: i32 = 0;
343        for k in 0..len_a {
344            let idx_b = (k as isize) + (len_b as isize - 1) - (n as isize);
345            if idx_b >= 0 && (idx_b as usize) < len_b {
346                acc += (src_a[k] as i32 * src_b[idx_b as usize] as i32) >> 15;
347            }
348        }
349        dst[n] = acc.clamp(i16::MIN as i32, i16::MAX as i32) as q15;
350    }
351}
352
353// --- Non-linear Filtering (Median & Conditional Median) ---
354
355#[allow(unused_imports)]
356use crate::math::FloatMath;
357use crate::transform::cfft_f32;
358
359/// 1D Conditional / Thresholded Median Filter for f32.
360///
361/// Replaces sample `src[i]` with the local median only if `|src[i] - median| > threshold`.
362/// When `threshold == 0.0`, performs standard median filtering.
363///
364/// `window_len` must be odd and $\le 63$.
365pub fn median_filter_1d_f32(
366    src: &[f32],
367    dst: &mut [f32],
368    window_len: usize,
369    threshold: f32,
370) -> Status {
371    let n = src.len();
372    if n == 0 || dst.len() < n {
373        return Status::LengthError;
374    }
375    if window_len == 0 || window_len % 2 == 0 || window_len > 63 {
376        return Status::ArgumentError;
377    }
378
379    let half = window_len / 2;
380    let mut sort_buf = [0.0f32; 64];
381
382    for i in 0..n {
383        // Populate window with boundary clamping
384        for j in 0..window_len {
385            let idx = (i as isize + j as isize - half as isize).clamp(0, (n - 1) as isize) as usize;
386            sort_buf[j] = src[idx];
387        }
388
389        // Insertion sort on small stack buffer
390        for a in 1..window_len {
391            let mut b = a;
392            while b > 0 && sort_buf[b - 1] > sort_buf[b] {
393                sort_buf.swap(b - 1, b);
394                b -= 1;
395            }
396        }
397
398        let med = sort_buf[half];
399        let center = src[i];
400        if (center - med).abs() >= threshold {
401            dst[i] = med;
402        } else {
403            dst[i] = center;
404        }
405    }
406
407    Status::Success
408}
409
410/// 1D Conditional Median Filter for Q15.
411pub fn median_filter_1d_q15(
412    src: &[q15],
413    dst: &mut [q15],
414    window_len: usize,
415    threshold: q15,
416) -> Status {
417    let n = src.len();
418    if n == 0 || dst.len() < n {
419        return Status::LengthError;
420    }
421    if window_len == 0 || window_len % 2 == 0 || window_len > 63 {
422        return Status::ArgumentError;
423    }
424
425    let half = window_len / 2;
426    let mut sort_buf = [0i16; 64];
427
428    for i in 0..n {
429        for j in 0..window_len {
430            let idx = (i as isize + j as isize - half as isize).clamp(0, (n - 1) as isize) as usize;
431            sort_buf[j] = src[idx];
432        }
433
434        for a in 1..window_len {
435            let mut b = a;
436            while b > 0 && sort_buf[b - 1] > sort_buf[b] {
437                sort_buf.swap(b - 1, b);
438                b -= 1;
439            }
440        }
441
442        let med = sort_buf[half];
443        let center = src[i];
444        let diff = (center as i32 - med as i32).abs();
445        if diff >= threshold as i32 {
446            dst[i] = med;
447        } else {
448            dst[i] = center;
449        }
450    }
451
452    Status::Success
453}
454
455/// 1D Conditional Median Filter for Q31.
456pub fn median_filter_1d_q31(
457    src: &[q31],
458    dst: &mut [q31],
459    window_len: usize,
460    threshold: q31,
461) -> Status {
462    let n = src.len();
463    if n == 0 || dst.len() < n {
464        return Status::LengthError;
465    }
466    if window_len == 0 || window_len % 2 == 0 || window_len > 63 {
467        return Status::ArgumentError;
468    }
469
470    let half = window_len / 2;
471    let mut sort_buf = [0i32; 64];
472
473    for i in 0..n {
474        for j in 0..window_len {
475            let idx = (i as isize + j as isize - half as isize).clamp(0, (n - 1) as isize) as usize;
476            sort_buf[j] = src[idx];
477        }
478
479        for a in 1..window_len {
480            let mut b = a;
481            while b > 0 && sort_buf[b - 1] > sort_buf[b] {
482                sort_buf.swap(b - 1, b);
483                b -= 1;
484            }
485        }
486
487        let med = sort_buf[half];
488        let center = src[i];
489        let diff = (center as i64 - med as i64).abs();
490        if diff >= threshold as i64 {
491            dst[i] = med;
492        } else {
493            dst[i] = center;
494        }
495    }
496
497    Status::Success
498}
499
500// --- FFT Fast Convolution ---
501
502/// Performs fast linear convolution of `signal` and `kernel` via FFT multiplication.
503/// Output length is `signal.len() + kernel.len() - 1`.
504pub fn fast_convolve_f32(signal: &[f32], kernel: &[f32], dst: &mut [f32]) -> Status {
505    let len_sig = signal.len();
506    let len_ker = kernel.len();
507    if len_sig == 0 || len_ker == 0 {
508        return Status::LengthError;
509    }
510    let total_len = len_sig + len_ker - 1;
511    if dst.len() < total_len {
512        return Status::LengthError;
513    }
514
515    // Find next power of 2
516    let mut fft_n = 1;
517    while fft_n < total_len {
518        fft_n <<= 1;
519    }
520
521    if fft_n > 512 {
522        // Fall back to time-domain convolution if size exceeds stack scratch buffer
523        conv_f32(signal, kernel, dst);
524        return Status::Success;
525    }
526
527    let mut sig_buf = [0.0f32; 1024]; // 2 * fft_n
528    let mut ker_buf = [0.0f32; 1024];
529
530    for i in 0..len_sig {
531        sig_buf[2 * i] = signal[i];
532    }
533    for i in 0..len_ker {
534        ker_buf[2 * i] = kernel[i];
535    }
536
537    cfft_f32(&mut sig_buf[..2 * fft_n], fft_n, 0, 1);
538    cfft_f32(&mut ker_buf[..2 * fft_n], fft_n, 0, 1);
539
540    // Pointwise complex multiplication: (a + jb) * (c + jd)
541    for i in 0..fft_n {
542        let a = sig_buf[2 * i];
543        let b = sig_buf[2 * i + 1];
544        let c = ker_buf[2 * i];
545        let d = ker_buf[2 * i + 1];
546        sig_buf[2 * i] = a * c - b * d;
547        sig_buf[2 * i + 1] = a * d + b * c;
548    }
549
550    // Inverse FFT
551    cfft_f32(&mut sig_buf[..2 * fft_n], fft_n, 1, 1);
552
553    for i in 0..total_len {
554        dst[i] = sig_buf[2 * i];
555    }
556
557    Status::Success
558}
559
560// --- Real-time Circular Buffer & Delay Line ---
561
562/// Const-generic zero-allocation circular buffer and delay line for real-time DSP sample streams.
563#[derive(Debug, Clone, Copy)]
564pub struct CircularBuffer<T, const N: usize> {
565    buffer: [T; N],
566    head: usize,
567    count: usize,
568}
569
570impl<T: Copy, const N: usize> CircularBuffer<T, N> {
571    /// Creates a new circular buffer initialized with `init_val`.
572    pub const fn new(init_val: T) -> Self {
573        Self {
574            buffer: [init_val; N],
575            head: 0,
576            count: 0,
577        }
578    }
579
580    /// Pushes a new sample into the buffer, overwriting the oldest sample when full.
581    #[inline(always)]
582    pub fn push(&mut self, sample: T) {
583        if N == 0 {
584            return;
585        }
586        self.buffer[self.head] = sample;
587        self.head = (self.head + 1) % N;
588        if self.count < N {
589            self.count += 1;
590        }
591    }
592
593    /// Gets sample with historical lag $k$, where $k = 0$ is the newest sample (`x[n]`), $k = 1$ is `x[n-1]`, etc.
594    /// Returns `None` if `lag >= self.len()`.
595    #[inline(always)]
596    pub fn get(&self, lag: usize) -> Option<T> {
597        if lag >= self.count || N == 0 {
598            return None;
599        }
600        let idx = (self.head + N - 1 - (lag % N)) % N;
601        Some(self.buffer[idx])
602    }
603
604    /// Returns the most recently pushed sample (`x[n]`).
605    #[inline(always)]
606    pub fn latest(&self) -> Option<T> {
607        self.get(0)
608    }
609
610    /// Returns the oldest sample stored in the buffer.
611    #[inline(always)]
612    pub fn oldest(&self) -> Option<T> {
613        if self.count == 0 {
614            None
615        } else {
616            self.get(self.count - 1)
617        }
618    }
619
620    /// Returns the number of valid samples currently stored in the buffer.
621    #[inline(always)]
622    pub const fn len(&self) -> usize {
623        self.count
624    }
625
626    /// Returns the capacity of the circular buffer (`N`).
627    #[inline(always)]
628    pub const fn capacity(&self) -> usize {
629        N
630    }
631
632    /// Returns `true` if the buffer contains no samples.
633    #[inline(always)]
634    pub const fn is_empty(&self) -> bool {
635        self.count == 0
636    }
637
638    /// Returns `true` if the buffer is filled to capacity `N`.
639    #[inline(always)]
640    pub const fn is_full(&self) -> bool {
641        self.count == N
642    }
643
644    /// Clears the circular buffer, resetting sample count and filling with `reset_val`.
645    pub fn clear(&mut self, reset_val: T) {
646        self.buffer = [reset_val; N];
647        self.head = 0;
648        self.count = 0;
649    }
650}
651
652// --- Single-Pole Recursive Filter (Steven W. Smith, Ch. 19) ---
653
654/// The cheapest possible IIR filter: a single-pole recursive low-pass or high-pass filter
655/// (Steven W. Smith, Ch. 19, Eq. 19-2 / 19-3), needing only one or two multiplies per sample.
656/// Coefficients are designed from a decay factor `x` (see
657/// [`crate::filter_design::single_pole_decay_from_cutoff`] /
658/// [`crate::filter_design::single_pole_decay_from_time_constant`]).
659#[derive(Debug, Clone, Copy, Default)]
660#[cfg_attr(feature = "defmt", derive(defmt::Format))]
661pub struct SinglePoleFilter {
662    b0: f32,
663    b1: f32,
664    a1: f32,
665    x1: f32,
666    y1: f32,
667}
668
669impl SinglePoleFilter {
670    /// Creates a single-pole low-pass filter from decay factor `x` (`0.0..1.0`); larger `x`
671    /// means slower decay (a lower cutoff frequency).
672    pub fn lowpass(decay: f32) -> Self {
673        Self {
674            b0: 1.0 - decay,
675            b1: 0.0,
676            a1: decay,
677            x1: 0.0,
678            y1: 0.0,
679        }
680    }
681
682    /// Creates a single-pole high-pass filter from the same decay factor `x` used by
683    /// [`SinglePoleFilter::lowpass`].
684    pub fn highpass(decay: f32) -> Self {
685        let b0 = (1.0 + decay) / 2.0;
686        Self {
687            b0,
688            b1: -b0,
689            a1: decay,
690            x1: 0.0,
691            y1: 0.0,
692        }
693    }
694
695    /// Processes a single input sample and returns the filtered output.
696    #[inline(always)]
697    pub fn process(&mut self, x: f32) -> f32 {
698        let y = self.b0 * x + self.b1 * self.x1 + self.a1 * self.y1;
699        self.x1 = x;
700        self.y1 = y;
701        y
702    }
703
704    /// Resets the filter's delay state to zero.
705    pub fn reset(&mut self) {
706        self.x1 = 0.0;
707        self.y1 = 0.0;
708    }
709}
710
711// --- Recursive Moving Average Filter (Steven W. Smith, Ch. 15) ---
712
713/// Const-generic `N`-point moving average filter implemented recursively (Steven W. Smith,
714/// Ch. 15, Eq. 15-3): each sample is updated with a single add and subtract, instead of an
715/// `O(N)` convolution sum.
716#[derive(Debug, Clone)]
717#[cfg_attr(feature = "defmt", derive(defmt::Format))]
718pub struct RecursiveMovingAverage<const N: usize> {
719    history: CircularBuffer<f32, N>,
720    sum: f32,
721}
722
723impl<const N: usize> RecursiveMovingAverage<N> {
724    /// Creates a new `N`-point recursive moving average filter with empty history.
725    pub const fn new() -> Self {
726        Self {
727            history: CircularBuffer::new(0.0),
728            sum: 0.0,
729        }
730    }
731
732    /// Pushes a new input sample and returns the updated moving average. While fewer than `N`
733    /// samples have been seen, the average is taken over the (growing) window received so far.
734    #[inline(always)]
735    pub fn process(&mut self, x: f32) -> f32 {
736        let oldest = if self.history.is_full() {
737            self.history.oldest().unwrap_or(0.0)
738        } else {
739            0.0
740        };
741        self.sum += x - oldest;
742        self.history.push(x);
743        if self.history.len() == 0 {
744            0.0
745        } else {
746            self.sum / self.history.len() as f32
747        }
748    }
749
750    /// Resets the filter to its initial, empty state.
751    pub fn reset(&mut self) {
752        self.history.clear(0.0);
753        self.sum = 0.0;
754    }
755}
756
757impl<const N: usize> Default for RecursiveMovingAverage<N> {
758    fn default() -> Self {
759        Self::new()
760    }
761}