use super::*;
pub(crate) const GESTURE_COUNT: usize = 5;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub(crate) enum GestureKind {
Bloom,
Submerge,
Echo,
Thin,
Lift,
}
impl GestureKind {
pub(crate) const ALL: [Self; GESTURE_COUNT] = [
Self::Bloom,
Self::Submerge,
Self::Echo,
Self::Thin,
Self::Lift,
];
pub(crate) fn from_key(key: char) -> Option<Self> {
Self::ALL.into_iter().find(|kind| kind.key() == key)
}
pub(crate) const fn key(self) -> char {
match self {
Self::Bloom => 'z',
Self::Submerge => 'c',
Self::Echo => 'v',
Self::Thin => 'b',
Self::Lift => 'x',
}
}
pub(crate) const fn name(self) -> &'static str {
match self {
Self::Bloom => "Bloom",
Self::Submerge => "Submerge",
Self::Echo => "Echo",
Self::Thin => "Thin",
Self::Lift => "Lift",
}
}
fn rise_seconds(self) -> f64 {
match self {
Self::Bloom => 1.5,
Self::Submerge => 1.2,
Self::Echo => 0.7,
Self::Thin => 1.0,
Self::Lift => 0.55,
}
}
fn return_seconds(self) -> f64 {
0.05
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub(crate) struct GestureEnvelope {
pub(crate) amount: f32,
pub(crate) at_seconds: f64,
pub(crate) held: bool,
pub(crate) restored: bool,
}
impl GestureEnvelope {
pub(crate) fn amount_at(self, kind: GestureKind, now_seconds: f64) -> f32 {
let elapsed = (now_seconds - self.at_seconds).max(0.0);
if self.held {
(self.amount as f64 + elapsed / kind.rise_seconds()).min(1.0) as f32
} else {
(self.amount as f64 - elapsed / kind.return_seconds()).max(0.0) as f32
}
}
fn release(&mut self, kind: GestureKind, now_seconds: f64) {
if self.held {
self.amount = self.amount_at(kind, now_seconds);
self.at_seconds = now_seconds;
self.held = false;
self.restored = false;
}
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub(crate) struct GestureState {
pub(crate) lanes: [[GestureEnvelope; GESTURE_COUNT]; TAB_COUNT],
}
impl GestureState {
pub(crate) fn envelope(&self, tab: Tab, kind: GestureKind) -> &GestureEnvelope {
&self.lanes[tab as usize][kind as usize]
}
pub(crate) fn amounts(&self, tab: Tab, now_seconds: f64) -> [f32; GESTURE_COUNT] {
GestureKind::ALL.map(|kind| self.envelope(tab, kind).amount_at(kind, now_seconds))
}
pub(crate) fn has_held(&self) -> bool {
self.lanes.iter().flatten().any(|envelope| envelope.held)
}
pub(crate) fn held_target(&self, kind: GestureKind) -> Option<Tab> {
Tab::all()
.into_iter()
.find(|tab| self.envelope(*tab, kind).held)
}
pub(crate) fn press(&mut self, kind: GestureKind, tab: Tab, now_seconds: f64) {
if let Some(held_tab) = self.held_target(kind) {
self.lanes[held_tab as usize][kind as usize].restored = false;
return;
}
let envelope = &mut self.lanes[tab as usize][kind as usize];
*envelope = GestureEnvelope {
amount: envelope.amount_at(kind, now_seconds),
at_seconds: now_seconds,
held: true,
restored: false,
};
}
pub(crate) fn release(&mut self, kind: GestureKind, now_seconds: f64) {
for layer in &mut self.lanes {
layer[kind as usize].release(kind, now_seconds);
}
}
pub(crate) fn release_all(&mut self, now_seconds: f64) {
for kind in GestureKind::ALL {
self.release(kind, now_seconds);
}
}
pub(crate) fn snapshot_at(&self, now_seconds: f64) -> Self {
let mut saved = Self::default();
for tab in Tab::all() {
for kind in GestureKind::ALL {
let envelope = self.envelope(tab, kind);
let amount = envelope.amount_at(kind, now_seconds);
if envelope.held || amount > 0.0 {
saved.lanes[tab as usize][kind as usize] = GestureEnvelope {
amount,
at_seconds: 0.0,
held: envelope.held,
restored: envelope.held,
};
}
}
}
saved
}
pub(crate) fn restored(&self) -> Self {
let mut state = self.clone();
for envelope in state.lanes.iter_mut().flatten() {
envelope.restored = envelope.held;
}
state
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn short_touch_release_and_repress_are_continuous() {
let mut state = GestureState::default();
state.press(GestureKind::Submerge, Tab::Chords, 10.0);
let rising = state.amounts(Tab::Chords, 10.3)[1];
assert!((rising - 0.25).abs() < 1e-6);
state.release(GestureKind::Submerge, 10.3);
assert_eq!(state.amounts(Tab::Chords, 10.3)[1], rising);
let returning = state.amounts(Tab::Chords, 10.35)[1];
assert!(returning < rising);
state.press(GestureKind::Submerge, Tab::Chords, 10.35);
assert_eq!(state.amounts(Tab::Chords, 10.35)[1], returning);
assert!(state.amounts(Tab::Chords, 10.4)[1] > returning);
}
#[test]
fn every_gesture_returns_to_rest_within_fifty_milliseconds() {
for kind in GestureKind::ALL {
let mut state = GestureState::default();
state.press(kind, Tab::Chords, 0.0);
state.release(kind, kind.rise_seconds());
assert!(
state.amounts(Tab::Chords, kind.rise_seconds() + 0.05)[kind as usize]
<= f32::EPSILON,
"{} did not return within 50 ms",
kind.name()
);
}
}
#[test]
fn duplicate_press_keeps_original_target_and_trajectory() {
let mut state = GestureState::default();
state.press(GestureKind::Bloom, Tab::Chords, 2.0);
state.press(GestureKind::Bloom, Tab::Perc, 2.5);
assert_eq!(state.held_target(GestureKind::Bloom), Some(Tab::Chords));
assert_eq!(state.amounts(Tab::Perc, 3.0), [0.0; GESTURE_COUNT]);
assert!((state.amounts(Tab::Chords, 2.75)[0] - 0.5).abs() < 1e-6);
}
#[test]
fn old_target_returns_while_new_target_rises() {
let mut state = GestureState::default();
state.press(GestureKind::Bloom, Tab::Chords, 0.0);
state.release(GestureKind::Bloom, 1.5);
state.press(GestureKind::Bloom, Tab::Perc, 1.52);
assert!(state.amounts(Tab::Chords, 1.54)[0] > 0.0);
assert!(state.amounts(Tab::Perc, 1.54)[0] > 0.0);
assert_eq!(state.held_target(GestureKind::Bloom), Some(Tab::Perc));
}
#[test]
fn snapshot_resumes_held_and_returning_trajectories_at_zero() {
let mut state = GestureState::default();
state.press(GestureKind::Bloom, Tab::Chords, 8.0);
state.press(GestureKind::Thin, Tab::Master, 7.0);
state.release(GestureKind::Thin, 8.4);
let saved = state.snapshot_at(8.5);
for offset in [0.0, 0.1, 0.2] {
for tab in [Tab::Chords, Tab::Master] {
for kind in GestureKind::ALL {
assert!(
(state.envelope(tab, kind).amount_at(kind, 8.5 + offset)
- saved.envelope(tab, kind).amount_at(kind, offset))
.abs()
< 1e-6
);
}
}
}
assert!(saved.envelope(Tab::Chords, GestureKind::Bloom).restored);
}
#[test]
fn restored_hold_is_claimed_on_its_saved_target_then_releases() {
let mut state = GestureState::default();
state.press(GestureKind::Echo, Tab::Tonal, 0.0);
let mut saved = state.snapshot_at(0.2);
saved.press(GestureKind::Echo, Tab::Master, 0.1);
assert_eq!(saved.held_target(GestureKind::Echo), Some(Tab::Tonal));
assert!(!saved.envelope(Tab::Tonal, GestureKind::Echo).restored);
saved.release_all(0.2);
assert!(!saved.has_held());
assert_eq!(saved.snapshot_at(1.0), GestureState::default());
}
}