///|
/// A named event attached to a motion timeline.
pub struct MotionMarker {
time : Double
name : String
payload : String
} derive(Debug)
///|
pub fn marker(
time : Double,
name : String,
payload? : String = "",
) -> MotionMarker raise MotionError {
ensure_finite(time)
if name.is_empty() {
raise MotionError::InvalidMarkerName
}
{ time, name, payload }
}
///|
pub fn MotionMarker::time(self : MotionMarker) -> Double {
self.time
}
///|
pub fn MotionMarker::name(self : MotionMarker) -> String {
self.name
}
///|
pub fn MotionMarker::payload(self : MotionMarker) -> String {
self.payload
}
///|
/// Direction of playback when a marker window is traversed.
pub(all) enum MarkerDirection {
Forward
Backward
} derive(Eq, Debug)
///|
/// A marker together with the direction in which it was crossed.
pub(all) struct MarkerHit {
marker : MotionMarker
direction : MarkerDirection
} derive(Debug)
///|
pub fn MarkerHit::marker(self : MarkerHit) -> MotionMarker {
self.marker
}
///|
pub fn MarkerHit::direction(self : MarkerHit) -> MarkerDirection {
self.direction
}
///|
pub fn MarkerHit::time(self : MarkerHit) -> Double {
self.marker.time
}
///|
pub fn MarkerHit::name(self : MarkerHit) -> String {
self.marker.name
}
///|
/// A sorted marker index for transport controls, editors, and event dispatch.
pub struct MarkerTimeline {
markers : Array[MotionMarker]
} derive(Debug)
///|
pub fn MarkerTimeline::new(markers : Array[MotionMarker]) -> MarkerTimeline {
let sorted = markers.copy()
sorted.sort_by(fn(left, right) {
let order = left.time.compare(right.time)
if order == 0 {
left.name.compare(right.name)
} else {
order
}
})
{ markers: sorted }
}
///|
pub fn MarkerTimeline::empty() -> MarkerTimeline {
{ markers: [] }
}
///|
pub fn MarkerTimeline::markers(self : MarkerTimeline) -> Array[MotionMarker] {
self.markers.copy()
}
///|
pub fn MarkerTimeline::length(self : MarkerTimeline) -> Int {
self.markers.length()
}
///|
pub fn MarkerTimeline::duration(self : MarkerTimeline) -> Double {
if self.markers.length() == 0 {
0.0
} else {
self.markers[self.markers.length() - 1].time
}
}
///|
pub fn MarkerTimeline::add(self : MarkerTimeline, value : MotionMarker) -> Unit {
self.markers.push(value)
self.markers.sort_by(fn(left, right) {
let order = left.time.compare(right.time)
if order == 0 {
left.name.compare(right.name)
} else {
order
}
})
}
///|
pub fn MarkerTimeline::remove_at(
self : MarkerTimeline,
index : Int,
) -> MotionMarker? {
if index < 0 || index >= self.markers.length() {
None
} else {
Some(self.markers.remove(index))
}
}
///|
pub fn MarkerTimeline::find(
self : MarkerTimeline,
name : String,
) -> Array[MotionMarker] {
self.markers.filter(fn(item) { item.name == name })
}
///|
pub fn MarkerTimeline::next_after(
self : MarkerTimeline,
time : Double,
) -> MotionMarker? {
for item in self.markers {
if item.time > time {
return Some(item)
}
}
None
}
///|
pub fn MarkerTimeline::previous_before(
self : MarkerTimeline,
time : Double,
) -> MotionMarker? {
for index in 0.. Array[MarkerHit] {
if start == end {
return []
}
if end > start {
let result : Array[MarkerHit] = []
for item in self.markers {
if item.time > start && item.time <= end {
result.push({ marker: item, direction: Forward })
}
}
result
} else {
let forward : Array[MarkerHit] = []
for item in self.markers {
if item.time >= end && item.time < start {
forward.push({ marker: item, direction: Backward })
}
}
let result : Array[MarkerHit] = []
for index in 0.. Array[MotionMarker] {
let radius = tolerance.max(0.0)
self.markers.filter(fn(item) { (item.time - time).abs() <= radius })
}
///|
pub fn MarkerTimeline::window(
self : MarkerTimeline,
start : Double,
end : Double,
) -> Array[MarkerHit] {
self.between(start, end)
}
///|
/// A cursor for deterministic marker dispatch in a transport loop.
pub struct MarkerCursor {
timeline : MarkerTimeline
mut time : Double
} derive(Debug)
///|
pub fn marker_cursor(
timeline : MarkerTimeline,
start_time? : Double = 0.0,
) -> MarkerCursor {
{ timeline, time: start_time }
}
///|
pub fn MarkerCursor::time(self : MarkerCursor) -> Double {
self.time
}
///|
pub fn MarkerCursor::timeline(self : MarkerCursor) -> MarkerTimeline {
self.timeline
}
///|
pub fn MarkerCursor::seek(self : MarkerCursor, time : Double) -> Unit {
self.time = time
}
///|
pub fn MarkerCursor::advance(
self : MarkerCursor,
time : Double,
) -> Array[MarkerHit] {
let result = self.timeline.between(self.time, time)
self.time = time
result
}
///|
pub fn MarkerCursor::reset(self : MarkerCursor, time? : Double = 0.0) -> Unit {
self.time = time
}
///|
/// Build a timeline from a regular sequence of marker names.
pub fn marker_sequence(
names : Array[String],
start : Double,
interval : Double,
) -> MarkerTimeline raise MotionError {
ensure_finite(start)
ensure_finite(interval)
let values : Array[MotionMarker] = []
for index in 0.. MarkerTimeline raise MotionError {
ensure_finite(offset)
let values : Array[MotionMarker] = []
for item in timeline.markers() {
values.push(marker(item.time + offset, item.name, payload=item.payload))
}
MarkerTimeline::new(values)
}
///|
/// Return a copy with marker times scaled around the origin.
pub fn scale_markers(
timeline : MarkerTimeline,
factor : Double,
) -> MarkerTimeline raise MotionError {
ensure_finite(factor)
let values : Array[MotionMarker] = []
for item in timeline.markers() {
values.push(marker(item.time * factor, item.name, payload=item.payload))
}
MarkerTimeline::new(values)
}