Skip to main content

pfx_input/
rumble.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::device::{Motors, PadId};
6
7#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
8pub struct Rumble {
9    pub low: f32,
10    pub high: f32,
11    pub duration_ms: u32,
12    pub attack_ms: u32,
13    pub release_ms: u32,
14}
15
16impl Rumble {
17    pub fn new(strength: f32, duration_ms: u32) -> Self {
18        Self::motors(strength, strength, duration_ms)
19    }
20
21    pub fn motors(low: f32, high: f32, duration_ms: u32) -> Self {
22        Self {
23            low: unit(low),
24            high: unit(high),
25            duration_ms,
26            attack_ms: 0,
27            release_ms: 0,
28        }
29    }
30
31    pub fn attack(mut self, ms: u32) -> Self {
32        self.attack_ms = ms;
33        self
34    }
35
36    pub fn release(mut self, ms: u32) -> Self {
37        self.release_ms = ms;
38        self
39    }
40
41    pub fn level_at(&self, elapsed_us: u64) -> [f32; 2] {
42        let duration = u64::from(self.duration_ms) * 1000;
43        if elapsed_us >= duration {
44            return [0.0, 0.0];
45        }
46        let mut gain = 1.0f32;
47        let attack = u64::from(self.attack_ms) * 1000;
48        if attack > 0 && elapsed_us < attack {
49            gain = gain.min(elapsed_us as f32 / attack as f32);
50        }
51        let release = u64::from(self.release_ms) * 1000;
52        let left = duration - elapsed_us;
53        if release > 0 && left < release {
54            gain = gain.min(left as f32 / release as f32);
55        }
56        [self.low * gain, self.high * gain]
57    }
58}
59
60fn unit(value: f32) -> f32 {
61    if value.is_finite() {
62        value.clamp(0.0, 1.0)
63    } else {
64        0.0
65    }
66}
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
69pub struct MotorCommand {
70    pub pad: PadId,
71    pub low: u16,
72    pub high: u16,
73    pub hold_ms: u32,
74}
75
76#[derive(Clone, Copy, Debug)]
77struct Playing {
78    rumble: Rumble,
79    elapsed_us: u64,
80}
81
82#[derive(Clone, Debug)]
83pub struct Rumbler {
84    intensity: u8,
85    step_us: u32,
86    playing: BTreeMap<PadId, (Motors, Vec<Playing>)>,
87    sent: BTreeMap<PadId, (u16, u16)>,
88}
89
90impl Rumbler {
91    pub fn new(step_us: u32, intensity: u8) -> Self {
92        Self {
93            intensity: intensity.min(100),
94            step_us: step_us.max(1),
95            playing: BTreeMap::new(),
96            sent: BTreeMap::new(),
97        }
98    }
99
100    pub fn intensity(&self) -> u8 {
101        self.intensity
102    }
103
104    pub fn set_intensity(&mut self, percent: u8) {
105        self.intensity = percent.min(100);
106        if self.intensity == 0 {
107            self.playing.clear();
108        }
109    }
110
111    pub fn play(&mut self, pad: PadId, motors: Motors, rumble: Rumble) -> bool {
112        if self.intensity == 0 || motors == Motors::None || rumble.duration_ms == 0 {
113            return false;
114        }
115        if rumble.low <= 0.0 && rumble.high <= 0.0 {
116            return false;
117        }
118        let entry = self
119            .playing
120            .entry(pad)
121            .or_insert_with(|| (motors, Vec::new()));
122        entry.0 = motors;
123        entry.1.push(Playing {
124            rumble,
125            elapsed_us: 0,
126        });
127        true
128    }
129
130    pub fn stop(&mut self, pad: PadId) {
131        self.playing.remove(&pad);
132    }
133
134    pub fn forget(&mut self, pad: PadId) {
135        self.playing.remove(&pad);
136        self.sent.remove(&pad);
137    }
138
139    pub fn is_playing(&self, pad: PadId) -> bool {
140        self.playing.contains_key(&pad)
141    }
142
143    pub fn scale(&self, value: f32) -> u16 {
144        let scaled = unit(value) * f32::from(self.intensity) / 100.0;
145        (scaled * 65535.0).round() as u16
146    }
147
148    pub fn step(&mut self) -> Vec<MotorCommand> {
149        let mut commands = Vec::new();
150        let mut levels = BTreeMap::new();
151        for (pad, (motors, events)) in &mut self.playing {
152            let mut low = 0.0f32;
153            let mut high = 0.0f32;
154            let mut hold_us = u64::MAX;
155            for event in events.iter() {
156                let [l, h] = event.rumble.level_at(event.elapsed_us);
157                low = low.max(l);
158                high = high.max(h);
159                let duration = u64::from(event.rumble.duration_ms) * 1000;
160                hold_us = hold_us.min(duration.saturating_sub(event.elapsed_us));
161            }
162            if *motors == Motors::One {
163                let both = low.max(high);
164                low = both;
165                high = both;
166            }
167            let step = u64::from(self.step_us);
168            for event in events.iter_mut() {
169                event.elapsed_us += step;
170            }
171            events.retain(|event| event.elapsed_us < u64::from(event.rumble.duration_ms) * 1000);
172            let hold_ms = (hold_us.min(u64::from(u32::MAX)) + step).div_ceil(1000) as u32;
173            levels.insert(*pad, (low, high, hold_ms));
174        }
175        self.playing.retain(|_, (_, events)| !events.is_empty());
176        let sounding: Vec<PadId> = levels.keys().copied().collect();
177        for (pad, (low, high, hold_ms)) in levels {
178            let value = (self.scale(low), self.scale(high));
179            if self.sent.get(&pad) != Some(&value) {
180                commands.push(MotorCommand {
181                    pad,
182                    low: value.0,
183                    high: value.1,
184                    hold_ms,
185                });
186                self.sent.insert(pad, value);
187            }
188        }
189        let silent: Vec<PadId> = self
190            .sent
191            .iter()
192            .filter(|(pad, value)| **value != (0, 0) && !sounding.contains(pad))
193            .map(|(pad, _)| *pad)
194            .collect();
195        for pad in silent {
196            commands.push(MotorCommand {
197                pad,
198                low: 0,
199                high: 0,
200                hold_ms: 0,
201            });
202            self.sent.insert(pad, (0, 0));
203        }
204        commands.sort_by_key(|command| command.pad);
205        commands
206    }
207}