use std::mem;
use chrono::Duration;
use crate::discovery::ENCODING_CONFIG;
use super::{
beats::{Beats, BEATS_SIZE},
ghostxform::GhostXForm,
payload::PayloadEntryHeader,
tempo::{Tempo, TEMPO_SIZE},
Result,
};
pub const TIMELINE_HEADER_KEY: u32 = u32::from_be_bytes(*b"tmln");
pub const TIMELINE_SIZE: u32 = TEMPO_SIZE + BEATS_SIZE + mem::size_of::<u64>() as u32;
pub const TIMELINE_HEADER: PayloadEntryHeader = PayloadEntryHeader {
key: TIMELINE_HEADER_KEY,
size: TIMELINE_SIZE,
};
#[derive(PartialEq, Clone, Debug, Copy)]
pub struct Timeline {
pub tempo: Tempo,
pub beat_origin: Beats,
pub time_origin: Duration,
}
impl Default for Timeline {
fn default() -> Self {
Self {
tempo: Default::default(),
beat_origin: Default::default(),
time_origin: Duration::zero(),
}
}
}
impl Timeline {
pub fn to_beats(&self, time: Duration) -> Beats {
self.beat_origin + self.tempo.micros_to_beats(time - self.time_origin)
}
pub fn from_beats(&self, beats: Beats) -> Duration {
self.time_origin + self.tempo.beats_to_micros(beats - self.beat_origin)
}
pub fn encode(&self) -> Result<Vec<u8>> {
let mut encoded = TIMELINE_HEADER.encode()?;
encoded.append(&mut bincode::encode_to_vec(self, ENCODING_CONFIG)?);
Ok(encoded)
}
}
impl bincode::Encode for Timeline {
fn encode<E: bincode::enc::Encoder>(
&self,
encoder: &mut E,
) -> std::result::Result<(), bincode::error::EncodeError> {
bincode::Encode::encode(
&(
self.tempo,
self.beat_origin,
self.time_origin.num_microseconds().unwrap(),
),
encoder,
)
}
}
impl bincode::Decode<()> for Timeline {
fn decode<D: bincode::de::Decoder>(
decoder: &mut D,
) -> std::result::Result<Self, bincode::error::DecodeError> {
let tempo_micros: i64 = bincode::Decode::decode(decoder)?;
let beat_origin_micro_beats: i64 = bincode::Decode::decode(decoder)?;
let time_origin_micros: i64 = bincode::Decode::decode(decoder)?;
Ok(Self {
tempo: Tempo::from(Duration::microseconds(tempo_micros)),
beat_origin: Beats {
value: beat_origin_micro_beats,
},
time_origin: Duration::microseconds(time_origin_micros),
})
}
}
pub fn clamp_tempo(timeline: Timeline) -> Timeline {
const MIN_BPM: f64 = 20.0;
const MAX_BPM: f64 = 999.0;
Timeline {
tempo: Tempo {
value: timeline.tempo.bpm().clamp(MIN_BPM, MAX_BPM),
},
beat_origin: timeline.beat_origin,
time_origin: timeline.time_origin,
}
}
pub fn update_client_timeline_from_session(
cur_client: Timeline,
session: Timeline,
at_time: Duration,
x_form: GhostXForm,
) -> Timeline {
let temp_tl = Timeline {
tempo: session.tempo,
beat_origin: cur_client.to_beats(at_time),
time_origin: at_time,
};
let host_beat_zero = x_form.ghost_to_host(session.from_beats(Beats { value: 0i64 }));
Timeline {
tempo: temp_tl.tempo,
beat_origin: temp_tl.to_beats(host_beat_zero),
time_origin: host_beat_zero,
}
}
pub fn update_session_timeline_from_client(
cur_session: Timeline,
client: Timeline,
at_time: Duration,
x_form: GhostXForm,
) -> Timeline {
let ghost_beat_0 = x_form.host_to_ghost(client.time_origin);
let zero = Beats { value: 0i64 };
if cur_session.to_beats(ghost_beat_0) == zero && client.tempo == cur_session.tempo {
return cur_session;
}
let temp_tl = Timeline {
tempo: client.tempo,
beat_origin: zero,
time_origin: ghost_beat_0,
};
let new_beat_origin = Beats {
value: i64::max(
cur_session.to_beats(x_form.host_to_ghost(at_time)).value,
cur_session.beat_origin.value + 1_i64,
),
};
Timeline {
tempo: client.tempo,
beat_origin: new_beat_origin,
time_origin: temp_tl.from_beats(new_beat_origin),
}
}
pub fn shift_client_timeline(client: Timeline, shift: Beats) -> Timeline {
use super::phase::shift_client_timeline;
shift_client_timeline(client, shift)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn session_to_client_updates_tempo() {
let b0 = Beats::new(-1.0);
let t0 = Duration::microseconds(-1);
let x_form = GhostXForm {
slope: 1.0,
intercept: Duration::microseconds(-1000000),
};
let cur_client = Timeline {
tempo: Tempo { value: 60.0 },
beat_origin: b0,
time_origin: t0,
};
let session = Timeline {
tempo: Tempo { value: 90.0 },
beat_origin: b0,
time_origin: x_form.host_to_ghost(t0),
};
let new_client = update_client_timeline_from_session(cur_client, session, t0, x_form);
assert_eq!(new_client.tempo, Tempo { value: 90.0 });
}
#[test]
fn time_to_beats() {
let t160 = Timeline {
tempo: Tempo::new(60.0),
beat_origin: Beats::new(-1.0),
time_origin: Duration::microseconds(1000000),
};
assert_eq!(
t160.to_beats(Duration::microseconds(4500000)),
Beats::new(2.5)
);
}
#[test]
fn beats_to_time() {
let t160 = Timeline {
tempo: Tempo::new(60.0),
beat_origin: Beats::new(-1.0),
time_origin: Duration::microseconds(1000000),
};
assert_eq!(
t160.from_beats(Beats::new(3.2)),
Duration::microseconds(5200000)
);
}
}