use std::collections::HashMap;
use soa_rs::{Soa, SoaClone, Soars};
mod edit;
pub mod query;
#[cfg(test)]
mod test;
use crate::collections::IntervalTree;
use crate::notes::{GroupId, Interval};
use crate::rhythm::{ClockTime, ScrollPosition, Time, TrackId};
#[expect(unused)]
pub use edit::*;
pub trait LongNoteData {
type ValueData: Soars + SoaClone + Clone + 'static;
type RuntimeData: Soars + SoaClone + Clone + Default + 'static;
fn start_time(&self) -> Time;
fn duration(&self) -> Time;
fn group_id(&self) -> &GroupId;
fn value_data(&self) -> Self::ValueData;
fn reconstruct<'a>(
start_time: Time,
duration: Time,
value_data: <Self::ValueData as Soars>::Ref<'a>,
group_id: GroupId,
) -> Self;
fn apply_event_track(
_state: &mut Self::RuntimeData,
_tempo_track: &crate::rhythm::TempoTrack,
_scroll_track: &crate::rhythm::ScrollSpeedTrack,
) {
}
}
#[derive(Debug, Clone, Copy)]
struct NoteLocation {
group_index: usize,
index_in_time_sorted_group: usize,
index_in_scroll_sorted_group: usize,
}
#[derive(Clone)]
pub struct LongNoteTypeStorage<Note: LongNoteData> {
note_id_counter: u64,
note_id_lookup: HashMap<u64, NoteLocation>,
time_sorted_groups: Vec<TimeIndexedLongNoteGroup<Note>>,
scroll_sorted_groups: Vec<ScrollIndexedLongNoteGroup<Note>>,
}
impl<N: std::fmt::Debug + LongNoteData> std::fmt::Debug for LongNoteTypeStorage<N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LongNoteTypeStorage")
.field("note_id_counter", &self.note_id_counter)
.field("note_id_lookup", &self.note_id_lookup)
.field("time_sorted_groups", &self.time_sorted_groups)
.field("scroll_sorted_groups", &self.scroll_sorted_groups)
.finish()
}
}
macro_rules! define_long_note_group {
($name:ident, $axis:ty, $index:ident, $intervals:ident, $query:ident, $doc:literal) => {
#[doc = $doc]
#[derive(Clone)]
pub struct $name<Note: LongNoteData> {
group_id: GroupId,
$index: IntervalTree<$axis>,
note_id: Vec<u64>,
start_time: Vec<Time>,
duration: Vec<Time>,
clock_time: Vec<Interval<ClockTime>>,
scroll_position: Vec<Interval<ScrollPosition>>,
value_data: Soa<Note::ValueData>,
runtime_data: Soa<Note::RuntimeData>,
}
impl<Note: LongNoteData> std::fmt::Debug for $name<Note> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(stringify!($name))
.field("group_id", &self.group_id)
.field("note_id", &self.note_id)
.field("start_time", &self.start_time)
.field("duration", &self.duration)
.field("clock_time", &self.clock_time)
.field("scroll_position", &self.scroll_position)
.finish()
}
}
impl<Note: LongNoteData> $name<Note> {
pub fn new(group_id: GroupId) -> Self {
Self {
group_id,
$index: IntervalTree::new(),
note_id: vec![],
start_time: vec![],
duration: vec![],
clock_time: vec![],
scroll_position: vec![],
value_data: Default::default(),
runtime_data: Default::default(),
}
}
pub fn group_id(&self) -> &GroupId {
&self.group_id
}
pub fn $index(&self) -> &IntervalTree<$axis> {
&self.$index
}
pub fn $query(&self, range: Interval<$axis>) -> impl Iterator<Item = usize> + '_ {
self.$index.query_intersecting(range)
}
pub fn note_id(&self) -> &[u64] {
&self.note_id
}
#[inline]
pub fn start_time(&self) -> &[Time] {
&self.start_time
}
#[inline]
pub fn duration(&self) -> &[Time] {
&self.duration
}
#[inline]
pub fn clock_time(&self) -> &[Interval<ClockTime>] {
&self.clock_time
}
#[inline]
pub fn value_data(&self) -> &Soa<Note::ValueData> {
&self.value_data
}
#[inline]
pub fn runtime_data(&self) -> &Soa<Note::RuntimeData> {
&self.runtime_data
}
#[inline]
pub fn scroll_position(&self) -> &[Interval<ScrollPosition>] {
&self.scroll_position
}
fn remove(&mut self, i: usize) -> Note::RuntimeData {
self.note_id.remove(i);
self.start_time.remove(i);
self.duration.remove(i);
self.clock_time.remove(i);
self.scroll_position.remove(i);
self.value_data.remove(i);
self.runtime_data.remove(i)
}
fn rebuild(&mut self) {
self.$index.intervals.clone_from(&self.$intervals);
self.$index.rebuild(|i, j| {
self.note_id.swap(i, j);
self.start_time.swap(i, j);
self.duration.swap(i, j);
self.clock_time.swap(i, j);
self.scroll_position.swap(i, j);
self.value_data.swap(i, j);
self.runtime_data.swap(i, j);
});
}
fn health_check(&self) {
self.$index.health_check();
debug_assert_eq!(self.$index.intervals, self.$intervals);
let len = self.note_id.len();
debug_assert!(len > 0);
debug_assert_eq!(self.start_time.len(), len);
debug_assert_eq!(self.duration.len(), len);
debug_assert_eq!(self.clock_time.len(), len);
debug_assert_eq!(self.scroll_position.len(), len);
debug_assert_eq!(self.value_data.len(), len);
debug_assert_eq!(self.runtime_data.len(), len);
}
}
};
}
define_long_note_group!(
TimeIndexedLongNoteGroup,
ClockTime,
time_index,
clock_time,
query_time_intersecting,
"Long-note components indexed only by clock time."
);
define_long_note_group!(
ScrollIndexedLongNoteGroup,
ScrollPosition,
scroll_index,
scroll_position,
query_scroll_intersecting,
"Long-note components indexed only by scroll position."
);
impl<Note: LongNoteData> LongNoteTypeStorage<Note> {
#[inline]
pub fn note_id(&self) -> impl Iterator<Item = u64> {
self.note_id_lookup.keys().copied()
}
#[inline]
pub fn time_sorted_groups(&self) -> &[TimeIndexedLongNoteGroup<Note>] {
&self.time_sorted_groups
}
#[inline]
pub fn scroll_sorted_groups(&self) -> &[ScrollIndexedLongNoteGroup<Note>] {
&self.scroll_sorted_groups
}
}