use super::{GroupId, LongNoteData, Note, NoteState};
use crate::rhythm::{ClockTime, Time};
pub struct NoteRef<'a, N: LongNoteData> {
pub id: u64,
pub start_time: Time,
pub duration: Time,
pub clock_time: Interval<ClockTime>,
pub group_id: GroupId,
pub value_data: <N::ValueData as soa_rs::Soars>::Ref<'a>,
pub runtime_data: <N::RuntimeData as soa_rs::Soars>::Ref<'a>,
}
pub struct NoteMut<'a, N: LongNoteData> {
pub id: u64,
pub start_time: Time,
pub duration: Time,
pub clock_time: Interval<ClockTime>,
pub group_id: GroupId,
pub value_data: <N::ValueData as soa_rs::Soars>::Ref<'a>,
pub runtime_data: <N::RuntimeData as soa_rs::Soars>::RefMut<'a>,
}
impl<N: LongNoteData> NoteRef<'_, N> {
pub fn to_snapshot(&self) -> NoteSnapshot<N> {
NoteSnapshot {
id: self.id,
note: N::reconstruct(
self.start_time,
self.duration,
soa_rs::AsSoaRef::as_soa_ref(&self.value_data),
self.group_id.clone(),
),
runtime_data: soa_rs::SoaClone::soa_clone(soa_rs::AsSoaRef::as_soa_ref(
&self.runtime_data,
)),
clock_time: self.clock_time,
}
}
}
impl<N: LongNoteData> NoteMut<'_, N> {
pub fn as_ref(&self) -> NoteRef<'_, N> {
NoteRef {
id: self.id,
start_time: self.start_time,
duration: self.duration,
clock_time: self.clock_time,
group_id: self.group_id.clone(),
value_data: soa_rs::AsSoaRef::as_soa_ref(&self.value_data),
runtime_data: soa_rs::AsSoaRef::as_soa_ref(&self.runtime_data),
}
}
pub fn to_snapshot(&self) -> NoteSnapshot<N> {
NoteSnapshot {
id: self.id,
note: N::reconstruct(
self.start_time,
self.duration,
soa_rs::AsSoaRef::as_soa_ref(&self.value_data),
self.group_id.clone(),
),
runtime_data: soa_rs::SoaClone::soa_clone(soa_rs::AsSoaRef::as_soa_ref(
&self.runtime_data,
)),
clock_time: self.clock_time,
}
}
}
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NoteSnapshot<N: LongNoteData> {
pub id: u64,
pub note: N,
pub runtime_data: N::RuntimeData,
pub clock_time: Interval<ClockTime>,
}
impl<N: LongNoteData + std::fmt::Debug> std::fmt::Debug for NoteSnapshot<N>
where
N::RuntimeData: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NoteSnapshot")
.field("id", &self.id)
.field("note", &self.note)
.field("runtime_data", &self.runtime_data)
.field("clock_time", &self.clock_time)
.finish()
}
}
impl<N: LongNoteData + PartialEq> PartialEq for NoteSnapshot<N>
where
N::RuntimeData: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.id == other.id
&& self.note == other.note
&& self.runtime_data == other.runtime_data
&& self.clock_time == other.clock_time
}
}
impl<N: LongNoteData> Note for NoteSnapshot<N> {
fn start_time(&self) -> Time {
self.note.start_time()
}
fn duration(&self) -> Time {
self.note.duration()
}
fn group_id(&self) -> &GroupId {
self.note.group_id()
}
}
impl<N: LongNoteData + Clone> NoteState for NoteSnapshot<N> {
type Snapshot = Self;
fn to_snapshot(&self) -> Self {
self.clone()
}
fn id(&self) -> u64 {
self.id
}
fn clock_time_interval(&self) -> Interval<ClockTime> {
self.clock_time
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Interval<T> {
start: T,
end: T,
}
impl<T> Interval<T> {
pub fn try_new(a: T, b: T) -> Option<Self>
where
T: PartialOrd,
{
match a.partial_cmp(&b) {
Some(std::cmp::Ordering::Greater) => Some(Self { start: b, end: a }),
Some(_) => Some(Self { start: a, end: b }),
None => None,
}
}
pub fn new(a: T, b: T) -> Self
where
T: Ord,
{
if a <= b {
Self { start: a, end: b }
} else {
Self { start: b, end: a }
}
}
pub fn start(&self) -> T
where
T: Copy,
{
self.start
}
pub fn end(&self) -> T
where
T: Copy,
{
self.end
}
pub fn intersect(&self, other: &Interval<T>) -> bool
where
T: Ord,
{
self.start <= other.end && self.end >= other.start
}
pub fn contains(&self, other: &T) -> bool
where
T: Ord,
{
&self.start <= other && other <= &self.end
}
}
impl<T: Default + Ord> Default for Interval<T> {
fn default() -> Self {
Self::new(T::default(), T::default())
}
}
impl<T> std::fmt::Debug for Interval<T>
where
T: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(std::format_args!("[{:?}, {:?}]", self.start, self.end))
}
}
#[cfg(test)]
mod tests {
use super::Interval;
use crate::rhythm::{BeatTime, ClockTime, Time};
use proptest::prelude::*;
proptest! {
#[test]
fn endpoint_order_does_not_change_membership(a: i32, b: i32, point: i32) {
let interval = Interval::new(a, b);
prop_assert!(interval.start() <= interval.end());
prop_assert_eq!(interval, Interval::new(b, a));
prop_assert_eq!(Some(interval), Interval::try_new(a, b));
prop_assert!(interval.contains(&a));
prop_assert!(interval.contains(&b));
prop_assert_eq!(interval.contains(&point),
(a <= point && point <= b) || (b <= point && point <= a));
}
}
#[test]
fn equal_endpoints_and_touching_intervals_are_inclusive() {
let point = Interval::new(3, 3);
assert!(point.contains(&3));
assert!(point.intersect(&Interval::new(5, 3)));
assert!(!point.intersect(&Interval::new(5, 4)));
assert_eq!(Interval::<i32>::default(), Interval::new(0, 0));
}
#[test]
fn time_endpoints_are_sorted_within_their_unit() {
let interval =
Interval::try_new(Time::Clock(ClockTime::ONE), Time::Clock(ClockTime::ZERO)).unwrap();
assert_eq!(interval.start(), Time::Clock(ClockTime::ZERO));
assert_eq!(interval.end(), Time::Clock(ClockTime::ONE));
}
#[test]
fn mixed_time_units_are_rejected() {
assert_eq!(
Interval::try_new(Time::Clock(ClockTime::ZERO), Time::Beat(BeatTime::ZERO)),
None
);
}
#[test]
fn nan_endpoint_is_rejected() {
assert!(Interval::try_new(0.0, f64::NAN).is_none());
assert!(Interval::try_new(f64::NAN, 0.0).is_none());
assert!(Interval::try_new(f64::NAN, f64::NAN).is_none());
}
}