1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// Copyright (c) 2021 Weird Constructor <weirdconstructor@gmail.com>
// This file is a part of HexoDSP. Released under GPL-3.0-or-later.
// See README.md and COPYING for details.

const VALUE_SAMPLING_FILTER_SIZE: usize = 10;

/// Accumulates the values for a single visible feedback value,
/// like an LED ([crate::Matrix::led_value_for]) or the
/// output feedbacks [crate::Matrix::out_fb_for] from the [crate::Matrix].
#[derive(Debug, Clone, Copy)]
pub struct VisualSamplingFilter {
    /// Holds a state bit, that is used to check if this
    /// filter needs to recalculate or not.
    recalc_state: bool,

    /// Current write head into the sample buffer.
    write_ptr: usize,

    /// Holds a set of the most recent samples to calculate
    /// the output.
    sample_buffer: [f32; VALUE_SAMPLING_FILTER_SIZE],

    /// Holds the last output, will only be recalculated
    /// when necessary.
    last_output: (f32, f32),
}

impl VisualSamplingFilter {
    pub fn new() -> Self {
        Self {
            recalc_state: false,
            write_ptr: 0,
            sample_buffer: [0.0; VALUE_SAMPLING_FILTER_SIZE],
            last_output: (0.0, 0.0),
        }
    }

    /// Used to check if we need to update this filter.
    #[inline]
    fn needs_recalc(&mut self, recalc_value: bool) -> bool {
        if self.recalc_state != recalc_value {
            self.recalc_state = recalc_value;
            true
        } else {
            false
        }
    }

    /// Retrieves the current output value of the filter.
    /// Negate the input for `recalc_value` one each frame,
    /// to reduce access to the `retrieve_fn` to be done only
    /// once per frame and per [VisualSamplingFilter].
    ///
    ///```
    /// use hexodsp::nodes::visual_sampling_filter::*;
    ///
    /// let mut vsf = VisualSamplingFilter::new();
    ///
    /// let inputs = [-0.87, -0.8, 0.2, 0.75, 0.5, 0.0, 0.22];
    /// let mut recalc = true;
    ///
    /// let mut last_output = (0.0, 0.0);
    /// for ip in inputs {
    ///     last_output = vsf.get(recalc, ip);
    ///     recalc = !recalc;
    /// }
    ///
    /// assert_eq!(last_output, (0.87, 0.75));
    ///```
    pub fn get(&mut self, recalc_value: bool, sample: f32) -> (f32, f32) {
        if self.needs_recalc(recalc_value) {
            let write_ptr = (self.write_ptr + 1) % self.sample_buffer.len();
            self.write_ptr = write_ptr;

            self.sample_buffer[write_ptr] = sample;

            let mut neg_max: f32 = 0.0;
            let mut pos_max: f32 = 0.0;

            for v in self.sample_buffer.iter() {
                if *v >= 0.0 {
                    pos_max = pos_max.max((*v).abs());
                } else {
                    neg_max = neg_max.max((*v).abs());
                }
            }

            self.last_output = (neg_max, pos_max);
        }

        self.last_output
    }
}