use std::collections::BTreeSet;
use crate::{MeasureInfo, TimemapEvent, TimemapEventExact};
pub fn measure_by_id<'a>(measures: &'a [MeasureInfo], id: &str) -> Option<&'a MeasureInfo> {
measures.iter().find(|m| m.id == id)
}
pub fn measure_at_in(events: &[TimemapEventExact], ms: f64) -> Option<&str> {
let mut current: Option<&str> = None;
for ev in events.iter().take_while(|e| e.tstamp <= ms) {
if let Some(m) = ev.measure_on.as_deref() {
current = Some(m);
}
}
current
}
pub fn measures_from_events(events: &[TimemapEventExact]) -> Vec<MeasureInfo> {
let mut measures: Vec<MeasureInfo> = Vec::new();
for ev in events {
if let Some(id) = &ev.measure_on {
if let Some(prev) = measures.last_mut() {
prev.end_ms = ev.tstamp;
prev.end_qfrac = ev.qfrac;
}
measures.push(MeasureInfo {
id: id.clone(),
start_ms: ev.tstamp,
end_ms: ev.tstamp,
start_qfrac: ev.qfrac,
end_qfrac: ev.qfrac,
});
}
}
if let (Some(last_event), Some(last_measure)) = (events.last(), measures.last_mut()) {
last_measure.end_ms = last_event.tstamp;
last_measure.end_qfrac = last_event.qfrac;
}
measures
}
pub fn sounding_at(timemap: &[TimemapEvent], ms: f64) -> Vec<String> {
let mut active = BTreeSet::new();
walk_to(timemap, ms, &mut active);
active.into_iter().collect()
}
pub fn sounding_at_into(timemap: &[TimemapEvent], ms: f64, out: &mut Vec<String>) {
let mut active = BTreeSet::new();
walk_to(timemap, ms, &mut active);
out.clear();
out.extend(active);
}
pub fn sounding_count_at(timemap: &[TimemapEvent], ms: f64) -> usize {
let mut active = BTreeSet::new();
walk_to(timemap, ms, &mut active);
active.len()
}
pub fn distinct_element_count(timemap: &[TimemapEvent]) -> usize {
let mut seen = BTreeSet::new();
for ev in timemap {
for id in &ev.on {
seen.insert(id.as_str());
}
}
seen.len()
}
pub fn chord_at(timemap: &[TimemapEvent], ms: f64) -> Vec<String> {
timemap
.iter()
.rev()
.find(|ev| ev.tstamp <= ms && !ev.on.is_empty())
.map(|ev| ev.on.clone())
.unwrap_or_default()
}
pub fn note_duration(timemap: &[TimemapEvent], id: &str) -> Option<(f64, f64)> {
let on_ms = timemap
.iter()
.find(|ev| ev.on.iter().any(|s| s == id))
.map(|ev| ev.tstamp)?;
let off_ms = timemap
.iter()
.find(|ev| ev.off.iter().any(|s| s == id))
.map(|ev| ev.tstamp)
.or_else(|| timemap.last().map(|ev| ev.tstamp))
.unwrap_or(on_ms);
Some((on_ms, off_ms))
}
pub struct LoopCursor<'a> {
inner: PlaybackCursor<'a>,
start_ms: f64,
end_ms: f64,
}
impl<'a> LoopCursor<'a> {
pub fn new(timemap: &'a [TimemapEvent], start_ms: f64, end_ms: f64) -> Self {
assert!(
start_ms < end_ms,
"LoopCursor requires start_ms < end_ms ({start_ms} >= {end_ms})"
);
let mut inner = PlaybackCursor::new(timemap);
let _ = inner.seek_to(start_ms);
Self {
inner,
start_ms,
end_ms,
}
}
pub fn advance_to(&mut self, ms: f64) -> &BTreeSet<String> {
let region = self.end_ms - self.start_ms;
let phase = if ms <= self.start_ms {
0.0
} else {
((ms - self.start_ms) % region).max(0.0)
};
let target = self.start_ms + phase;
if target < self.inner.position_ms() {
let _ = self.inner.seek_to(self.start_ms);
}
self.inner.advance_to(target)
}
pub fn position_ms(&self) -> f64 {
self.inner.position_ms()
}
pub fn sounding(&self) -> &BTreeSet<String> {
self.inner.sounding()
}
}
pub fn duration_ms(timemap: &[TimemapEvent]) -> f64 {
timemap.last().map(|e| e.tstamp).unwrap_or(0.0)
}
pub fn events_in_range(timemap: &[TimemapEvent], start_ms: f64, end_ms: f64) -> &[TimemapEvent] {
if start_ms > end_ms {
return &[];
}
let start = timemap.partition_point(|e| e.tstamp < start_ms);
let end = timemap.partition_point(|e| e.tstamp <= end_ms);
&timemap[start..end]
}
pub fn next_event_after(timemap: &[TimemapEvent], ms: f64) -> Option<&TimemapEvent> {
let idx = timemap.partition_point(|e| e.tstamp <= ms);
timemap.get(idx)
}
pub fn prev_event_before(timemap: &[TimemapEvent], ms: f64) -> Option<&TimemapEvent> {
let idx = timemap.partition_point(|e| e.tstamp < ms);
if idx == 0 {
None
} else {
Some(&timemap[idx - 1])
}
}
pub struct PlaybackCursor<'a> {
timemap: &'a [TimemapEvent],
next: usize,
position_ms: f64,
sounding: BTreeSet<String>,
}
impl<'a> PlaybackCursor<'a> {
pub fn new(timemap: &'a [TimemapEvent]) -> Self {
Self {
timemap,
next: 0,
position_ms: f64::NEG_INFINITY,
sounding: BTreeSet::new(),
}
}
pub fn advance_to(&mut self, ms: f64) -> &BTreeSet<String> {
debug_assert!(
ms >= self.position_ms || self.position_ms == f64::NEG_INFINITY,
"PlaybackCursor::advance_to is monotonic ({ms} < {}); use seek_to to rewind",
self.position_ms
);
while self.next < self.timemap.len() {
let ev = &self.timemap[self.next];
if ev.tstamp < ms {
for id in &ev.off {
self.sounding.remove(id);
}
for id in &ev.on {
self.sounding.insert(id.clone());
}
self.next += 1;
} else if ev.tstamp == ms {
for id in &ev.on {
self.sounding.insert(id.clone());
}
break;
} else {
break;
}
}
self.position_ms = ms;
&self.sounding
}
pub fn seek_to(&mut self, ms: f64) -> &BTreeSet<String> {
self.next = 0;
self.position_ms = f64::NEG_INFINITY;
self.sounding.clear();
self.advance_to(ms)
}
pub fn position_ms(&self) -> f64 {
self.position_ms
}
pub fn sounding(&self) -> &BTreeSet<String> {
&self.sounding
}
}
fn walk_to(timemap: &[TimemapEvent], ms: f64, active: &mut BTreeSet<String>) {
for ev in timemap.iter().take_while(|e| e.tstamp <= ms) {
if ev.tstamp < ms {
for id in &ev.off {
active.remove(id);
}
for id in &ev.on {
active.insert(id.clone());
}
} else {
for id in &ev.on {
active.insert(id.clone());
}
}
}
}