rich_sdl2_rust/haptic/
effect.rs

1//! Controls the effect on the haptic device.
2
3use crate::bind;
4
5use super::direction::Direction;
6
7/// An effect on the haptic device.
8#[derive(Debug, Clone, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum HapticEffect {
11    /// Applies the constant force in the direction.
12    Constant(Direction, Play, Trigger, Level, Envelope),
13    /// Applies the force of the periodic waveform.
14    Periodic(Direction, Play, Trigger, Wave, Envelope),
15    /// Applies the force on fulfilled the condition.
16    Condition(Play, Trigger, Condition),
17    /// Applies the force by linear ramp.
18    Ramp(Direction, Play, Trigger, Ramp, Envelope),
19    /// Applies the left/right effect with two motors magnitude. One motor is high frequency, the other is low frequency.
20    LeftRight {
21        /// The length of the playing in milliseconds.
22        length: u32,
23        /// The magnitude for the large motor.
24        large_magnitude: u16,
25        /// The magnitude for the small motor.
26        small_magnitude: u16,
27    },
28    /// Applies the force of the periodic waveform from your sampled data.
29    Custom(Direction, Play, Trigger, Custom, Envelope),
30}
31
32impl HapticEffect {
33    pub(super) fn into_raw(self) -> bind::SDL_HapticEffect {
34        match self {
35            HapticEffect::Constant(dir, play, trigger, level, env) => bind::SDL_HapticEffect {
36                constant: bind::SDL_HapticConstant {
37                    type_: bind::SDL_HAPTIC_CONSTANT as u16,
38                    direction: dir.into_raw(),
39                    length: play.length,
40                    delay: play.delay,
41                    button: trigger.button,
42                    interval: trigger.interval,
43                    level: level.0,
44                    attack_length: env.attack_length,
45                    attack_level: env.attack_level,
46                    fade_length: env.fade_length,
47                    fade_level: env.fade_level,
48                },
49            },
50            HapticEffect::Periodic(dir, play, trigger, wave, env) => bind::SDL_HapticEffect {
51                periodic: bind::SDL_HapticPeriodic {
52                    type_: wave.kind.to_raw(),
53                    direction: dir.into_raw(),
54                    length: play.length,
55                    delay: play.delay,
56                    button: trigger.button,
57                    interval: trigger.interval,
58                    period: wave.period,
59                    magnitude: wave.magnitude,
60                    offset: wave.offset,
61                    phase: wave.phase,
62                    attack_length: env.attack_length,
63                    attack_level: env.attack_level,
64                    fade_length: env.fade_length,
65                    fade_level: env.fade_level,
66                },
67            },
68            HapticEffect::Condition(play, trigger, condition) => bind::SDL_HapticEffect {
69                condition: bind::SDL_HapticCondition {
70                    type_: 0,
71                    direction: bind::SDL_HapticDirection {
72                        type_: 0,
73                        dir: [0; 3],
74                    },
75                    length: play.length,
76                    delay: play.delay,
77                    button: trigger.button,
78                    interval: trigger.interval,
79                    right_sat: condition.positive_level.0,
80                    left_sat: condition.negative_level.0,
81                    right_coeff: condition.positive_coefficient.0,
82                    left_coeff: condition.negative_coefficient.0,
83                    deadband: condition.dead_band.0,
84                    center: condition.center.0,
85                },
86            },
87            HapticEffect::Ramp(dir, play, trigger, ramp, env) => bind::SDL_HapticEffect {
88                ramp: bind::SDL_HapticRamp {
89                    type_: bind::SDL_HAPTIC_RAMP as u16,
90                    direction: dir.into_raw(),
91                    length: play.length,
92                    delay: play.delay,
93                    button: trigger.button,
94                    interval: trigger.interval,
95                    start: ramp.start.0,
96                    end: ramp.end.0,
97                    attack_length: env.attack_length,
98                    attack_level: env.attack_level,
99                    fade_length: env.fade_length,
100                    fade_level: env.fade_level,
101                },
102            },
103            HapticEffect::LeftRight {
104                length,
105                large_magnitude,
106                small_magnitude,
107            } => bind::SDL_HapticEffect {
108                leftright: bind::SDL_HapticLeftRight {
109                    type_: bind::SDL_HAPTIC_LEFTRIGHT as u16,
110                    length,
111                    large_magnitude,
112                    small_magnitude,
113                },
114            },
115            HapticEffect::Custom(dir, play, trigger, mut custom, env) => bind::SDL_HapticEffect {
116                custom: bind::SDL_HapticCustom {
117                    type_: bind::SDL_HAPTIC_CUSTOM as u16,
118                    direction: dir.into_raw(),
119                    length: play.length,
120                    delay: play.delay,
121                    button: trigger.button,
122                    interval: trigger.interval,
123                    channels: custom.channels,
124                    period: custom.period,
125                    samples: custom.samples,
126                    data: custom.data.as_mut_ptr(),
127                    attack_length: env.attack_length,
128                    attack_level: env.attack_level,
129                    fade_length: env.fade_length,
130                    fade_level: env.fade_level,
131                },
132            },
133        }
134    }
135}
136
137impl From<bind::SDL_HapticEffect> for HapticEffect {
138    fn from(raw: bind::SDL_HapticEffect) -> Self {
139        match unsafe { raw.type_ } as u32 {
140            bind::SDL_HAPTIC_CONSTANT => {
141                let bind::SDL_HapticConstant {
142                    direction,
143                    length,
144                    delay,
145                    button,
146                    interval,
147                    level,
148                    attack_length,
149                    attack_level,
150                    fade_length,
151                    fade_level,
152                    ..
153                } = unsafe { raw.constant };
154                Self::Constant(
155                    direction.into(),
156                    Play { length, delay },
157                    Trigger { button, interval },
158                    Level(level),
159                    Envelope {
160                        attack_length,
161                        attack_level,
162                        fade_length,
163                        fade_level,
164                    },
165                )
166            }
167            bind::SDL_HAPTIC_SINE
168            | bind::SDL_HAPTIC_TRIANGLE
169            | bind::SDL_HAPTIC_SAWTOOTHUP
170            | bind::SDL_HAPTIC_SAWTOOTHDOWN => {
171                let bind::SDL_HapticPeriodic {
172                    type_,
173                    direction,
174                    length,
175                    delay,
176                    button,
177                    interval,
178                    period,
179                    magnitude,
180                    offset,
181                    phase,
182                    attack_length,
183                    attack_level,
184                    fade_length,
185                    fade_level,
186                    ..
187                } = unsafe { raw.periodic };
188                Self::Periodic(
189                    direction.into(),
190                    Play { length, delay },
191                    Trigger { button, interval },
192                    Wave {
193                        kind: type_.into(),
194                        period,
195                        magnitude,
196                        offset,
197                        phase,
198                    },
199                    Envelope {
200                        attack_length,
201                        attack_level,
202                        fade_length,
203                        fade_level,
204                    },
205                )
206            }
207            bind::SDL_HAPTIC_SPRING
208            | bind::SDL_HAPTIC_DAMPER
209            | bind::SDL_HAPTIC_INERTIA
210            | bind::SDL_HAPTIC_FRICTION => {
211                let bind::SDL_HapticCondition {
212                    type_,
213                    length,
214                    delay,
215                    button,
216                    interval,
217                    right_sat,
218                    left_sat,
219                    right_coeff,
220                    left_coeff,
221                    deadband,
222                    center,
223                    ..
224                } = unsafe { raw.condition };
225                Self::Condition(
226                    Play { length, delay },
227                    Trigger { button, interval },
228                    Condition {
229                        positive_level: Vector3(right_sat),
230                        negative_level: Vector3(left_sat),
231                        positive_coefficient: Vector3(right_coeff),
232                        negative_coefficient: Vector3(left_coeff),
233                        dead_band: Vector3(deadband),
234                        center: Vector3(center),
235                    },
236                )
237            }
238            bind::SDL_HAPTIC_RAMP => {
239                let bind::SDL_HapticRamp {
240                    direction,
241                    length,
242                    delay,
243                    button,
244                    interval,
245                    start,
246                    end,
247                    attack_length,
248                    attack_level,
249                    fade_length,
250                    fade_level,
251                    ..
252                } = unsafe { raw.ramp };
253                Self::Ramp(
254                    direction.into(),
255                    Play { length, delay },
256                    Trigger { button, interval },
257                    Ramp {
258                        start: Level(start),
259                        end: Level(end),
260                    },
261                    Envelope {
262                        attack_length,
263                        attack_level,
264                        fade_length,
265                        fade_level,
266                    },
267                )
268            }
269            bind::SDL_HAPTIC_LEFTRIGHT => {
270                let bind::SDL_HapticLeftRight {
271                    length,
272                    large_magnitude,
273                    small_magnitude,
274                    ..
275                } = unsafe { raw.leftright };
276                Self::LeftRight {
277                    length,
278                    large_magnitude,
279                    small_magnitude,
280                }
281            }
282            bind::SDL_HAPTIC_CUSTOM => {
283                let bind::SDL_HapticCustom {
284                    direction,
285                    length,
286                    delay,
287                    button,
288                    interval,
289                    channels,
290                    period,
291                    samples,
292                    data,
293                    attack_length,
294                    attack_level,
295                    fade_length,
296                    fade_level,
297                    ..
298                } = unsafe { raw.custom };
299                Self::Custom(
300                    direction.into(),
301                    Play { length, delay },
302                    Trigger { button, interval },
303                    Custom {
304                        channels,
305                        period,
306                        samples,
307                        data: unsafe {
308                            std::slice::from_raw_parts_mut(
309                                data,
310                                channels as usize * samples as usize,
311                            )
312                        }
313                        .to_vec(),
314                    },
315                    Envelope {
316                        attack_length,
317                        attack_level,
318                        fade_length,
319                        fade_level,
320                    },
321                )
322            }
323            _ => unreachable!(),
324        }
325    }
326}
327
328/// Length and delay of the playing.
329#[derive(Debug, Clone, PartialEq, Eq)]
330pub struct Play {
331    /// The length of the playing in milliseconds.
332    pub length: u32,
333    /// The delay of the playing.
334    pub delay: u16,
335}
336
337/// A trigger button to start the effect, and an interval between the effect.
338#[derive(Debug, Clone, PartialEq, Eq)]
339pub struct Trigger {
340    /// The trigger button to start the effect
341    pub button: u16,
342    /// The interval between the effect in milliseconds.
343    pub interval: u16,
344}
345
346/// A magnitude level of the force.
347#[derive(Debug, Clone, PartialEq, Eq)]
348pub struct Level(pub i16);
349
350/// An envelope to fade in/out the effect. If both `attack_length` and `fade_level` are `0`, the envelope is not used. An example of a constant effect evolution in time:
351///
352/// ```text
353/// Strength
354/// ^
355/// |
356/// |      effect level --> _________________
357/// |                      /                 \
358/// |                     /                   \
359/// |                    /                     \
360/// |                   /                       \
361/// | attack_level --> |                         \
362/// |                  |                          | <--- fade_level
363/// |
364/// +--------------------------------------------------> Time
365///
366///                    [--]                 [---]
367///                attack_length          fade_length
368///
369/// [------------------][-----------------------]
370///        delay                 length
371/// ```
372#[derive(Debug, Clone, PartialEq, Eq)]
373pub struct Envelope {
374    /// The length of the level from `attack_level` to the effect level in milliseconds.
375    pub attack_length: u16,
376    /// The initial force level on applying.
377    pub attack_level: u16,
378    /// The length of the level from the effect level to `attack_level`in milliseconds.
379    pub fade_length: u16,
380    /// The end force level on applying.
381    pub fade_level: u16,
382}
383
384/// A periodic waveform specification.
385#[derive(Debug, Clone, PartialEq, Eq)]
386pub struct Wave {
387    /// The kind of the waveform.
388    pub kind: WaveKind,
389    /// The period of the waveform.
390    pub period: u16,
391    /// The magnitude by peak-to-peak level of the waveform.
392    pub magnitude: i16,
393    /// The amplifier offset, mean value of the waveform.
394    pub offset: i16,
395    /// The phase shift in degrees times 100.
396    pub phase: u16,
397}
398
399/// A kind of the waveform.
400#[derive(Debug, Clone, PartialEq, Eq)]
401#[non_exhaustive]
402pub enum WaveKind {
403    /// A sine wave like:
404    ///
405    /// ```text
406    ///   __      __      __      __
407    ///  /  \    /  \    /  \    /
408    /// /    \__/    \__/    \__/
409    /// ```
410    Sine,
411    /// A triangle wave like:
412    ///
413    /// ```text
414    ///   /\    /\    /\    /\    /\
415    ///  /  \  /  \  /  \  /  \  /
416    /// /    \/    \/    \/    \/
417    /// ```
418    Triangle,
419    /// An upwards sawtooth wave like:
420    ///
421    /// ```text
422    ///   /|  /|  /|  /|  /|  /|  /|
423    ///  / | / | / | / | / | / | / |
424    /// /  |/  |/  |/  |/  |/  |/  |
425    /// ```
426    SawToothUp,
427    /// A downwards sawtooth wave like:
428    ///
429    /// ```text
430    /// \  |\  |\  |\  |\  |\  |\  |
431    ///  \ | \ | \ | \ | \ | \ | \ |
432    ///   \|  \|  \|  \|  \|  \|  \|
433    /// ```
434    SwaToothDown,
435}
436
437impl WaveKind {
438    fn to_raw(&self) -> u16 {
439        (match *self {
440            WaveKind::Sine => bind::SDL_HAPTIC_SINE,
441            WaveKind::Triangle => bind::SDL_HAPTIC_TRIANGLE,
442            WaveKind::SawToothUp => bind::SDL_HAPTIC_SAWTOOTHUP,
443            WaveKind::SwaToothDown => bind::SDL_HAPTIC_SAWTOOTHDOWN,
444        }) as u16
445    }
446}
447
448impl From<u16> for WaveKind {
449    fn from(raw: u16) -> Self {
450        match raw as u32 {
451            bind::SDL_HAPTIC_SINE => Self::Sine,
452            bind::SDL_HAPTIC_TRIANGLE => Self::Triangle,
453            bind::SDL_HAPTIC_SAWTOOTHUP => Self::SawToothUp,
454            bind::SDL_HAPTIC_SAWTOOTHDOWN => Self::SwaToothDown,
455            _ => unreachable!(),
456        }
457    }
458}
459
460/// A vector to represent the XYZ component for [`Condition`].
461#[derive(Debug, Clone, PartialEq, Eq)]
462pub struct Vector3<T>(pub [T; 3]);
463
464/// A condition to trigger the effect. Refer to [`Direction`] for which side is the positive/negative on the joystick.
465#[derive(Debug, Clone, PartialEq, Eq)]
466pub struct Condition {
467    /// The level when the joystick in the positive side.
468    pub positive_level: Vector3<u16>,
469    /// The level when the joystick in the negative side.
470    pub negative_level: Vector3<u16>,
471    /// How fast to increase the force towards the positive side.
472    pub positive_coefficient: Vector3<i16>,
473    /// How fast to increase the force towards the negative side.
474    pub negative_coefficient: Vector3<i16>,
475    /// The size of the dead zone.
476    pub dead_band: Vector3<u16>,
477    /// The position of the dead zone.
478    pub center: Vector3<i16>,
479}
480
481/// A linear ramp to interpolate the force of the effect.
482#[derive(Debug, Clone, PartialEq, Eq)]
483pub struct Ramp {
484    /// The level at the start to play.
485    pub start: Level,
486    /// The level at the end to play.
487    pub end: Level,
488}
489
490/// A custom periodic waveform by your sampled data.
491#[derive(Debug, Clone, PartialEq, Eq)]
492pub struct Custom {
493    /// The numbers of using axes.
494    pub channels: u8,
495    /// The period of your sampled data.
496    pub period: u16,
497    /// The numbers of the samples in your data.
498    pub samples: u16,
499    /// The sampled data. Its length must be equal to `channels * samples`.
500    pub data: Vec<u16>,
501}