Skip to main content

record_player/
event.rs

1use crate::{DeckId, LoadStatus, SurfaceRegion};
2use serde::{Deserialize, Serialize};
3
4fn default_scratch_grip() -> f32 {
5    1.0
6}
7
8fn default_scratch_grab_impulse() -> f32 {
9    0.22
10}
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13#[serde(tag = "type", rename_all = "snake_case")]
14pub enum PlayerEvent {
15    HydrateDeck {
16        deck: DeckId,
17        loaded: bool,
18        status: LoadStatus,
19        duration_seconds: f64,
20        current_seconds: f64,
21        playing: bool,
22        transport_on: bool,
23        needle_lifted: bool,
24        playback_rate: f32,
25        channel_gain: f32,
26    },
27    SetActiveDeck {
28        deck: DeckId,
29    },
30    SetLoadState {
31        deck: DeckId,
32        status: LoadStatus,
33        loaded: bool,
34        duration_seconds: f64,
35    },
36    SetSourceSampleRate {
37        deck: DeckId,
38        sample_rate: f64,
39    },
40    PlaybackPositionObserved {
41        deck: DeckId,
42        seconds: f64,
43    },
44    PlaybackEnded {
45        deck: DeckId,
46    },
47    ToggleTransport {
48        deck: DeckId,
49    },
50    SetTransport {
51        deck: DeckId,
52        running: bool,
53    },
54    TogglePlayback {
55        deck: DeckId,
56    },
57    SetNeedle {
58        deck: DeckId,
59        lifted: bool,
60        observed_playback_seconds: f64,
61    },
62    Seek {
63        deck: DeckId,
64        seconds: f64,
65    },
66    SetPlaybackRate {
67        deck: DeckId,
68        rate: f32,
69    },
70    StartTimedRegion {
71        region: SurfaceRegion,
72        now_ms: f64,
73        duration_seconds: f64,
74    },
75    StopTimedRegion {
76        region: SurfaceRegion,
77        completed: bool,
78    },
79    TimedRegionElapsed {
80        region: SurfaceRegion,
81    },
82    StartClipLoop {
83        clip_id: String,
84    },
85    StopClipLoop,
86    BeginScratch {
87        deck: DeckId,
88        pointer_id: i32,
89        playback_seconds: f64,
90        rotation_degrees: f64,
91        #[serde(default)]
92        rate: f32,
93        #[serde(default = "default_scratch_grab_impulse")]
94        impulse: f32,
95        #[serde(default = "default_scratch_grip")]
96        grip: f32,
97        #[serde(default)]
98        output_frame: u64,
99    },
100    MoveScratch {
101        deck: DeckId,
102        position_frames: f64,
103        rendered_position_frames: f64,
104        rate: f32,
105        rotation_degrees: f64,
106        impulse: f32,
107        #[serde(default = "default_scratch_grip")]
108        grip: f32,
109        #[serde(default)]
110        output_frame: u64,
111    },
112    ScratchRenderedPosition {
113        deck: DeckId,
114        rendered_position_frames: f64,
115    },
116    EndScratch {
117        deck: DeckId,
118        rendered_position_frames: f64,
119        rotation_degrees: f64,
120        resume_playback: bool,
121        save_sample: bool,
122        can_platter_handoff: bool,
123        #[serde(default)]
124        output_frame: u64,
125    },
126    SetCrossfader {
127        value: f32,
128    },
129    SetChannelGain {
130        deck: DeckId,
131        value: f32,
132    },
133    SetTwoDeckMode {
134        enabled: bool,
135    },
136    Tick {
137        now_ms: f64,
138    },
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn historical_scratch_events_default_to_compatible_begin_intent() {
147        let begin: PlayerEvent = serde_json::from_value(serde_json::json!({
148            "type": "begin_scratch",
149            "deck": "a",
150            "pointer_id": 1,
151            "playback_seconds": 2.0,
152            "rotation_degrees": 0.0
153        }))
154        .unwrap();
155        let movement: PlayerEvent = serde_json::from_value(serde_json::json!({
156            "type": "move_scratch",
157            "deck": "a",
158            "position_frames": 96_000.0,
159            "rendered_position_frames": 96_000.0,
160            "rate": -1.0,
161            "rotation_degrees": -20.0,
162            "impulse": 0.0
163        }))
164        .unwrap();
165        let end: PlayerEvent = serde_json::from_value(serde_json::json!({
166            "type": "end_scratch",
167            "deck": "a",
168            "rendered_position_frames": 96_000.0,
169            "rotation_degrees": 0.0,
170            "resume_playback": true,
171            "save_sample": false,
172            "can_platter_handoff": true
173        }))
174        .unwrap();
175
176        assert!(matches!(
177            begin,
178            PlayerEvent::BeginScratch { rate, impulse, grip, output_frame, .. }
179                if rate == 0.0 && impulse == 0.22 && grip == 1.0 && output_frame == 0
180        ));
181        assert!(matches!(
182            movement,
183            PlayerEvent::MoveScratch { grip, output_frame, .. }
184                if grip == 1.0 && output_frame == 0
185        ));
186        assert!(matches!(
187            end,
188            PlayerEvent::EndScratch { output_frame, .. } if output_frame == 0
189        ));
190    }
191}