1pub fn apply_deadzone_i16(v: i16, deadzone: i16) -> i16 {
11 let dz = deadzone.clamp(0, 32767);
12 let vi = v as i32;
13 let sign = vi.signum();
14 let mag = vi.unsigned_abs() as i32;
15 if mag <= (dz as i32) {
16 0
17 } else {
18 let rem = 32767 - (dz as i32);
19 let adj = ((mag - (dz as i32)) * 32767) / rem.max(1);
20 (adj * sign).clamp(-32768, 32767) as i16
21 }
22}
23
24pub fn apply_outer_deadzone_i16(v: i16, outer: i16) -> i16 {
27 let outer = outer.clamp(0, 32767) as i32;
28 if outer == 0 {
29 return v;
30 }
31 let vi = v as i32;
32 let sign = vi.signum();
33 let mag = vi.unsigned_abs() as i32;
34 let lim = 32767 - outer;
35 let rem = lim.max(1);
36 let scaled = (mag.min(lim) * 32767) / rem;
37 (scaled * sign).clamp(-32768, 32767) as i16
38}
39
40pub fn apply_response_curve_i16(v: i16, gamma: f32) -> i16 {
43 if gamma <= 0.0 {
44 return v;
45 }
46 let n = (v as f32) / 32767.0; let sign = if n >= 0.0 { 1.0 } else { -1.0 };
48 let mag = n.abs().min(1.0);
49 let curved = mag.powf(gamma) * sign;
50 (curved * 32767.0).round().clamp(-32768.0, 32767.0) as i16
51}
52
53pub fn square_to_circle(lx: i16, ly: i16) -> (i16, i16) {
55 let nx = (lx as f32) / 32767.0;
56 let ny = (ly as f32) / 32767.0;
57 let sx = (nx * (1.0 - 0.5 * ny * ny).sqrt()).clamp(-1.0, 1.0);
58 let sy = (ny * (1.0 - 0.5 * nx * nx).sqrt()).clamp(-1.0, 1.0);
59 ((sx * 32767.0).round() as i16, (sy * 32767.0).round() as i16)
60}
61
62pub fn apply_radial_deadzone(lx: i16, ly: i16, deadzone: i16) -> (i16, i16) {
64 let dz = deadzone.clamp(0, 32767) as f32;
65 let x = lx as f32;
66 let y = ly as f32;
67 let r = (x * x + y * y).sqrt();
68 if r <= dz {
69 return (0, 0);
70 }
71 let r2 = ((r - dz) * 32767.0) / (32767.0 - dz).max(1.0);
72 if r == 0.0 {
73 return (0, 0);
74 }
75 let scale = r2 / r;
76 let nx = (x * scale).clamp(-32767.0, 32767.0);
77 let ny = (y * scale).clamp(-32767.0, 32767.0);
78 (nx.round() as i16, ny.round() as i16)
79}
80
81pub fn postprocess_stick(
83 lx: i16,
84 ly: i16,
85 use_circle: bool,
86 radial_deadzone_v: i16,
87 anti_deadzone_v: i16
88) -> (i16, i16) {
89 let (mut x, mut y) = if use_circle { square_to_circle(lx, ly) } else { (lx, ly) };
90 (x, y) = apply_radial_deadzone(x, y, radial_deadzone_v);
91 x = apply_anti_deadzone_i16(x, anti_deadzone_v);
92 y = apply_anti_deadzone_i16(y, anti_deadzone_v);
93 (x, y)
94}
95
96pub fn postprocess_stick_ex(
98 lx: i16,
99 ly: i16,
100 use_circle: bool,
101 radial_deadzone_v: i16,
102 anti_deadzone_v: i16,
103 outer_deadzone_v: i16,
104 response_gamma: f32
105) -> (i16, i16) {
106 let (mut x, mut y) = if use_circle { square_to_circle(lx, ly) } else { (lx, ly) };
107 (x, y) = apply_radial_deadzone(x, y, radial_deadzone_v);
108 x = apply_anti_deadzone_i16(x, anti_deadzone_v);
109 y = apply_anti_deadzone_i16(y, anti_deadzone_v);
110 x = apply_outer_deadzone_i16(x, outer_deadzone_v);
111 y = apply_outer_deadzone_i16(y, outer_deadzone_v);
112 if response_gamma != 1.0 {
113 x = apply_response_curve_i16(x, response_gamma);
114 y = apply_response_curve_i16(y, response_gamma);
115 }
116 (x, y)
117}
118
119pub fn apply_anti_deadzone_i16(v: i16, anti: i16) -> i16 {
122 let anti = anti.clamp(0, 32767) as i32;
123 let vi = v as i32;
124 if vi == 0 || anti == 0 {
125 return v;
126 }
127 let sign = vi.signum();
128 let mag = vi.unsigned_abs() as i32;
129 let boosted = mag.max(anti);
130 (boosted * sign).clamp(-32768, 32767) as i16
131}
132
133pub fn trigger_pressed(v: u8, thresh: u8) -> bool {
135 v >= thresh
136}
137
138pub fn trigger_hysteresis(last_pressed: bool, v: u8, press_thresh: u8, release_thresh: u8) -> bool {
142 if last_pressed { v >= release_thresh } else { v >= press_thresh }
143}
144
145pub fn button_edges(prev: u16, curr: u16) -> (u16, u16) {
149 let changed = prev ^ curr;
150 let pressed = changed & curr; let released = changed & !curr; (pressed, released)
153}
154
155pub fn debounce_bit(prev_out: bool, curr_in: bool, counter: u8, hold_count: u8) -> (bool, u8) {
161 if curr_in == prev_out {
162 (prev_out, 0)
163 } else {
164 let c = counter.saturating_add(1);
165 if c >= hold_count {
166 (curr_in, 0)
167 } else {
168 (prev_out, c)
169 }
170 }
171}
172
173pub fn autorepeat_fire(
176 is_down: bool,
177 elapsed_ms_since_change: u32,
178 first_delay_ms: u32,
179 repeat_rate_ms: u32
180) -> bool {
181 if !is_down {
182 return false;
183 }
184 if elapsed_ms_since_change == 0 {
185 return true;
186 }
187 if elapsed_ms_since_change < first_delay_ms {
188 return false;
189 }
190 let after = elapsed_ms_since_change - first_delay_ms;
191 repeat_rate_ms != 0 && after % repeat_rate_ms == 0
192}
193
194pub fn autorepeat_fire_window(
203 is_down: bool,
204 elapsed_down_ms: u32,
205 elapsed_since_last_fire_ms: u32,
206 first_delay_ms: u32,
207 repeat_rate_ms: u32,
208 window_ms: u32
209) -> bool {
210 if !is_down {
211 return false;
212 }
213 if elapsed_down_ms == 0 {
214 return true;
215 } if elapsed_down_ms < first_delay_ms {
217 return false;
218 }
219 if repeat_rate_ms == 0 {
220 return false;
221 }
222
223 let target = repeat_rate_ms;
227 let e = elapsed_since_last_fire_ms;
228 e >= target && e.saturating_sub(target) <= window_ms
230}
231
232pub fn remap_buttons(mask: u16, remap: &[(u16, u16)]) -> u16 {
235 let mut out = 0u16;
236 for &(src, dst) in remap {
237 if (mask & src) != 0 {
238 out |= dst;
239 }
240 }
241 out
242}
243
244pub fn merge_masks(masks: &[u16]) -> u16 {
246 masks.iter().fold(0u16, |acc, &m| acc | m)
247}
248
249pub fn set_bit(mask: u16, bit: u16, on: bool) -> u16 {
251 if on { mask | bit } else { mask & !bit }
252}
253pub fn toggle_bit(mask: u16, bit: u16) -> u16 {
254 mask ^ bit
255}
256
257pub fn apply_u8_zones(v: u8, anti: u8, outer: u8) -> u8 {
262 let mut out = v as i32;
263 if out > 0 && anti > 0 {
264 out = out.max(anti as i32);
265 }
266 if outer > 0 {
267 let lim = 255 - (outer as i32);
268 out = (out.min(lim) * 255) / lim.max(1);
269 }
270 out.clamp(0, 255) as u8
271}