Skip to main content

midi_controller/
encoder_accel.rs

1//! Encoder acceleration: converts time intervals between detents into step multipliers.
2//!
3//! Faster turning → shorter intervals → more steps per detent.
4//! Platform-agnostic: accepts `now_ms` timestamps.
5
6/// Encoder acceleration state. Tracks time of last detent.
7#[derive(Debug, Clone)]
8pub struct EncoderAccel {
9    last_detent_ms: u32,
10    initialized: bool,
11}
12
13impl Default for EncoderAccel {
14    fn default() -> Self {
15        Self::new()
16    }
17}
18
19impl EncoderAccel {
20    pub fn new() -> Self {
21        Self {
22            last_detent_ms: 0,
23            initialized: false,
24        }
25    }
26
27    /// Record a detent at `now_ms` and return the number of steps to apply.
28    /// First detent always returns 1 (no interval to measure yet).
29    pub fn steps(&mut self, now_ms: u32) -> u8 {
30        if !self.initialized {
31            self.last_detent_ms = now_ms;
32            self.initialized = true;
33            return 1;
34        }
35
36        let interval_ms = now_ms.wrapping_sub(self.last_detent_ms);
37        self.last_detent_ms = now_ms;
38
39        accel_curve(interval_ms)
40    }
41
42    /// Reset state (e.g., on preset switch).
43    pub fn reset(&mut self) {
44        self.initialized = false;
45    }
46}
47
48/// Acceleration curve: interval (ms) → step count.
49/// Tuned for typical rotary encoders with ~20 detents/revolution.
50fn accel_curve(interval_ms: u32) -> u8 {
51    if interval_ms < 20 {
52        8 // very fast
53    } else if interval_ms < 50 {
54        4 // fast
55    } else if interval_ms < 100 {
56        2 // moderate
57    } else {
58        1 // slow/normal
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn first_detent_always_one_step() {
68        let mut enc = EncoderAccel::new();
69        assert_eq!(enc.steps(1000), 1);
70    }
71
72    #[test]
73    fn slow_turn_gives_one_step() {
74        let mut enc = EncoderAccel::new();
75        enc.steps(0);
76        assert_eq!(enc.steps(200), 1);
77        assert_eq!(enc.steps(500), 1);
78    }
79
80    #[test]
81    fn moderate_turn_gives_two_steps() {
82        let mut enc = EncoderAccel::new();
83        enc.steps(0);
84        assert_eq!(enc.steps(80), 2);
85        assert_eq!(enc.steps(150), 2); // 70ms since last
86    }
87
88    #[test]
89    fn fast_turn_gives_four_steps() {
90        let mut enc = EncoderAccel::new();
91        enc.steps(0);
92        assert_eq!(enc.steps(30), 4);
93    }
94
95    #[test]
96    fn very_fast_turn_gives_eight_steps() {
97        let mut enc = EncoderAccel::new();
98        enc.steps(0);
99        assert_eq!(enc.steps(10), 8);
100        assert_eq!(enc.steps(15), 8); // 5ms since last
101    }
102
103    #[test]
104    fn reset_makes_next_return_one() {
105        let mut enc = EncoderAccel::new();
106        enc.steps(0);
107        enc.steps(10); // 8 steps
108        enc.reset();
109        assert_eq!(enc.steps(20), 1); // first after reset
110    }
111
112    #[test]
113    fn acceleration_slows_down() {
114        let mut enc = EncoderAccel::new();
115        enc.steps(0);
116        assert_eq!(enc.steps(10), 8); // fast burst
117        assert_eq!(enc.steps(110), 1); // slowed down (100ms gap)
118    }
119
120    #[test]
121    fn wrapping_timestamp() {
122        let mut enc = EncoderAccel::new();
123        enc.steps(u32::MAX - 10);
124        // 20ms later (wraps)
125        assert_eq!(enc.steps(9), 4); // wrapping_sub gives 20ms → but < 20 is 8...
126                                     // Actually: MAX-10 to 9 = 20ms. interval < 20 → 8 steps? No, 20 is not < 20.
127                                     // interval_ms = 9 - (MAX-10) wrapping = 9 + 11 = 20. 20 < 50 → 4 steps.
128    }
129}