use crate::{DeckId, LoadStatus, SurfaceRegion};
use serde::{Deserialize, Serialize};
fn default_scratch_grip() -> f32 {
1.0
}
fn default_scratch_grab_impulse() -> f32 {
0.22
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum PlayerEvent {
HydrateDeck {
deck: DeckId,
loaded: bool,
status: LoadStatus,
duration_seconds: f64,
current_seconds: f64,
playing: bool,
transport_on: bool,
needle_lifted: bool,
playback_rate: f32,
channel_gain: f32,
},
SetActiveDeck {
deck: DeckId,
},
SetLoadState {
deck: DeckId,
status: LoadStatus,
loaded: bool,
duration_seconds: f64,
},
SetSourceSampleRate {
deck: DeckId,
sample_rate: f64,
},
PlaybackPositionObserved {
deck: DeckId,
seconds: f64,
},
PlaybackEnded {
deck: DeckId,
},
ToggleTransport {
deck: DeckId,
},
SetTransport {
deck: DeckId,
running: bool,
},
TogglePlayback {
deck: DeckId,
},
SetNeedle {
deck: DeckId,
lifted: bool,
observed_playback_seconds: f64,
},
Seek {
deck: DeckId,
seconds: f64,
},
SetPlaybackRate {
deck: DeckId,
rate: f32,
},
StartTimedRegion {
region: SurfaceRegion,
now_ms: f64,
duration_seconds: f64,
},
StopTimedRegion {
region: SurfaceRegion,
completed: bool,
},
TimedRegionElapsed {
region: SurfaceRegion,
},
StartClipLoop {
clip_id: String,
},
StopClipLoop,
BeginScratch {
deck: DeckId,
pointer_id: i32,
playback_seconds: f64,
rotation_degrees: f64,
#[serde(default)]
rate: f32,
#[serde(default = "default_scratch_grab_impulse")]
impulse: f32,
#[serde(default = "default_scratch_grip")]
grip: f32,
#[serde(default)]
output_frame: u64,
},
MoveScratch {
deck: DeckId,
position_frames: f64,
rendered_position_frames: f64,
rate: f32,
rotation_degrees: f64,
impulse: f32,
#[serde(default = "default_scratch_grip")]
grip: f32,
#[serde(default)]
output_frame: u64,
},
ScratchRenderedPosition {
deck: DeckId,
rendered_position_frames: f64,
},
EndScratch {
deck: DeckId,
rendered_position_frames: f64,
rotation_degrees: f64,
resume_playback: bool,
save_sample: bool,
can_platter_handoff: bool,
#[serde(default)]
output_frame: u64,
},
SetCrossfader {
value: f32,
},
SetChannelGain {
deck: DeckId,
value: f32,
},
SetTwoDeckMode {
enabled: bool,
},
Tick {
now_ms: f64,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn historical_scratch_events_default_to_compatible_begin_intent() {
let begin: PlayerEvent = serde_json::from_value(serde_json::json!({
"type": "begin_scratch",
"deck": "a",
"pointer_id": 1,
"playback_seconds": 2.0,
"rotation_degrees": 0.0
}))
.unwrap();
let movement: PlayerEvent = serde_json::from_value(serde_json::json!({
"type": "move_scratch",
"deck": "a",
"position_frames": 96_000.0,
"rendered_position_frames": 96_000.0,
"rate": -1.0,
"rotation_degrees": -20.0,
"impulse": 0.0
}))
.unwrap();
let end: PlayerEvent = serde_json::from_value(serde_json::json!({
"type": "end_scratch",
"deck": "a",
"rendered_position_frames": 96_000.0,
"rotation_degrees": 0.0,
"resume_playback": true,
"save_sample": false,
"can_platter_handoff": true
}))
.unwrap();
assert!(matches!(
begin,
PlayerEvent::BeginScratch { rate, impulse, grip, output_frame, .. }
if rate == 0.0 && impulse == 0.22 && grip == 1.0 && output_frame == 0
));
assert!(matches!(
movement,
PlayerEvent::MoveScratch { grip, output_frame, .. }
if grip == 1.0 && output_frame == 0
));
assert!(matches!(
end,
PlayerEvent::EndScratch { output_frame, .. } if output_frame == 0
));
}
}