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
8const SMOOTHER_BASEFREQ: i32 = (0.1 * (1 << I32_FRAC_BITS) as f32) as i32;
10
11const SMOOTHER_SENSITIVITY: i32 = (0.02 * (1 << I32_FRAC_BITS) as f32) as i32;
13
14const MOVEMENT_THRESHOLD_DEFAULT: i32 = 30;
16
17#[derive(Debug)]
19pub struct PotConditioner {
20 value: i32,
22
23 input_range: (i32, i32),
25
26 output_range: (i32, i32),
28
29 smoother: DynamicSmootherEcoI32,
31
32 deadband_half_width: i32,
34
35 backlash: Backlash<i32>,
37
38 delta: i32,
40
41 velocity: i32,
43
44 movement_threshold: i32,
46
47 moved: bool,
49
50 last_movement: Option<u64>,
52}
53
54impl PotConditioner {
55 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 pub fn set_movement_threshold(&mut self, threshold: i32) {
83 self.movement_threshold = threshold;
84 }
85
86 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 pub fn value(&self) -> i32 {
117 self.value
118 }
119
120 pub fn delta(&self) -> i32 {
122 self.delta
123 }
124
125 pub fn velocity(&self) -> i32 {
127 self.velocity >> 8
128 }
129
130 pub fn moved(&self) -> bool {
132 self.moved
133 }
134
135 pub fn last_movement(&self) -> Option<u64> {
137 self.last_movement
138 }
139
140 pub fn set_output_range(&mut self, out_min: i32, out_max: i32) {
142 self.output_range = (out_min, out_max);
143 }
144
145 pub fn output_range(&self) -> (i32, i32) {
147 self.output_range
148 }
149}
150
151fn 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;