pot_conditioner/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(test), no_std)]
3#![warn(missing_docs)]
4
5use backlash::Backlash;
6use dyn_smooth::{DynamicSmootherEcoI32, I32_FRAC_BITS};
7
8/// Base frequency for dynamic smoother.
9const SMOOTHER_BASEFREQ: i32 = (0.1 * (1 << I32_FRAC_BITS) as f32) as i32;
10
11/// Sensitivity of dynamic smoother.
12const SMOOTHER_SENSITIVITY: i32 = (0.02 * (1 << I32_FRAC_BITS) as f32) as i32;
13
14/// Default movement threshold.
15const MOVEMENT_THRESHOLD_DEFAULT: i32 = 30;
16
17/// Processor struct containing variables and states.
18#[derive(Debug)]
19pub struct PotConditioner {
20    /// Current value.
21    value: i32,
22
23    /// Tuple of input value range (min, max).
24    input_range: (i32, i32),
25
26    /// Tuple of output value range (min, max).
27    output_range: (i32, i32),
28
29    /// Dynamic smoother.
30    smoother: DynamicSmootherEcoI32,
31
32    /// Backlash deadband width divided by 2.
33    deadband_half_width: i32,
34
35    /// Backlash processor.
36    backlash: Backlash<i32>,
37
38    /// Delta between current value and last one.
39    delta: i32,
40
41    /// Velocity of movement. Used for movement detection.
42    velocity: i32,
43
44    /// Movement threshold.
45    movement_threshold: i32,
46
47    /// Moved flag, set in `update()` when threshold was reached.
48    moved: bool,
49
50    /// Tick number of last detected movement.
51    last_movement: Option<u64>,
52}
53
54impl PotConditioner {
55    /// Create a new instance.
56    /// - `sampling_rate`: sampling rate in Hz.
57    /// - `input_range`: tuple of (lowest/highest) input value.
58    /// - `input_range`: tuple of (lowest/highest) output value.
59    pub fn new(sampling_rate: i32, input_range: (i32, i32), output_range: (i32, i32)) -> Self {
60        let deadband_width = (input_range.1 - input_range.0) / 512;
61
62        Self {
63            value: 0,
64            input_range,
65            output_range,
66            smoother: DynamicSmootherEcoI32::new(
67                SMOOTHER_BASEFREQ,
68                sampling_rate << I32_FRAC_BITS,
69                SMOOTHER_SENSITIVITY,
70            ),
71            deadband_half_width: deadband_width / 2,
72            backlash: Backlash::new(deadband_width),
73            delta: 0,
74            velocity: 0,
75            movement_threshold: MOVEMENT_THRESHOLD_DEFAULT,
76            moved: false,
77            last_movement: None,
78        }
79    }
80
81    /// Sets a new movement threshold. Recommended range is 10-255.
82    pub fn set_movement_threshold(&mut self, threshold: i32) {
83        self.movement_threshold = threshold;
84    }
85
86    /// Update value with new input and return processed value.
87    pub fn update(&mut self, value: i32, tick: u64) -> i32 {
88        let value = self.smoother.tick(value);
89        let value = self.backlash.update(value);
90        let value = rescale_and_clamp(
91            value,
92            self.input_range.0 + self.deadband_half_width,
93            self.input_range.1 - self.deadband_half_width,
94            self.output_range.0,
95            self.output_range.1,
96        );
97
98        self.delta = value - self.value;
99        self.velocity = (self.velocity + (self.smoother.g() << 8) / self.smoother.g0().max(1)) / 2;
100
101        self.moved = self.delta() != 0
102            && (self.velocity() > self.movement_threshold
103                || self.value() == self.output_range.0
104                || self.value() == self.output_range.1);
105
106        if self.moved {
107            self.last_movement = Some(tick);
108        }
109
110        self.value = value;
111
112        self.value
113    }
114
115    /// Return current value.
116    pub fn value(&self) -> i32 {
117        self.value
118    }
119
120    /// Return delta.
121    pub fn delta(&self) -> i32 {
122        self.delta
123    }
124
125    /// Return velocity.
126    pub fn velocity(&self) -> i32 {
127        self.velocity >> 8
128    }
129
130    /// Return if the pot has been moved faster than the given threshold.
131    pub fn moved(&self) -> bool {
132        self.moved
133    }
134
135    /// Return tick number of last detected movement.
136    pub fn last_movement(&self) -> Option<u64> {
137        self.last_movement
138    }
139
140    /// Sets a new output range.
141    pub fn set_output_range(&mut self, out_min: i32, out_max: i32) {
142        self.output_range = (out_min, out_max);
143    }
144
145    /// Returns the current output range.
146    pub fn output_range(&self) -> (i32, i32) {
147        self.output_range
148    }
149}
150
151/// Rescale a value to a new range with limiting.
152fn rescale_and_clamp(value: i32, in_min: i32, in_max: i32, out_min: i32, out_max: i32) -> i32 {
153    ((value - in_min) * (out_max - out_min) / (in_max - in_min) + out_min).clamp(out_min, out_max)
154}
155
156#[cfg(test)]
157mod tests;