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}