sim_lib_music_core/
time.rs1use sim_kernel::{Ref, Symbol, Tick as KernelTick};
2use sim_lib_midi_core::{DEFAULT_US_PER_QUARTER, TickTime};
3
4use crate::{MusicError, Time};
5
6pub type Beat = Time;
8pub type MusicalTime = Time;
10pub type Tick = TickTime;
12
13#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct TempoMapRef {
16 pub id: Symbol,
18 pub us_per_quarter: u32,
20}
21
22impl TempoMapRef {
23 pub fn constant(id: impl Into<String>, us_per_quarter: u32) -> Self {
25 Self {
26 id: Symbol::qualified("music/tempo", id.into()),
27 us_per_quarter,
28 }
29 }
30}
31
32impl Default for TempoMapRef {
33 fn default() -> Self {
34 Self::constant("default", DEFAULT_US_PER_QUARTER)
35 }
36}
37
38#[derive(Copy, Clone, Debug, PartialEq, Eq)]
40pub struct TimeRange {
41 pub start: Tick,
43 pub end: Tick,
45}
46
47impl TimeRange {
48 pub fn new(start: Tick, end: Tick) -> Result<Self, MusicError> {
52 let end = end.quantize(start.tpq);
53 if end.ticks < start.ticks {
54 return Err(MusicError::InvalidTimeRange);
55 }
56 Ok(Self { start, end })
57 }
58
59 pub fn from_ticks(start: i64, end: i64, ppq: u32) -> Result<Self, MusicError> {
63 if ppq == 0 {
64 return Err(MusicError::InvalidPpq);
65 }
66 Self::new(
67 Tick {
68 ticks: start,
69 tpq: ppq,
70 },
71 Tick {
72 ticks: end,
73 tpq: ppq,
74 },
75 )
76 }
77
78 pub fn starts_at_zero(end: Tick) -> Result<Self, MusicError> {
80 Self::new(
81 Tick {
82 ticks: 0,
83 tpq: end.tpq,
84 },
85 end,
86 )
87 }
88
89 pub fn contains(self, tick: Tick) -> bool {
91 let tick = tick.quantize(self.start.tpq);
92 tick.ticks >= self.start.ticks && tick.ticks < self.end.ticks
93 }
94
95 pub fn clip_span(self, start: Tick, duration: Tick) -> Option<(Tick, Tick)> {
100 let start = start.quantize(self.start.tpq);
101 let end = (start + duration).quantize(self.start.tpq);
102 if end.ticks <= self.start.ticks || start.ticks >= self.end.ticks {
103 return None;
104 }
105 let clipped_start = Tick::new(start.ticks.max(self.start.ticks), self.start.tpq).ok()?;
106 let clipped_end = Tick::new(end.ticks.min(self.end.ticks), self.start.tpq).ok()?;
107 let clipped_duration =
108 Tick::new(clipped_end.ticks - clipped_start.ticks, self.start.tpq).ok()?;
109 Some((clipped_start, clipped_duration))
110 }
111}
112
113pub fn time_to_tick(time: MusicalTime, ppq: u32) -> Result<Tick, MusicError> {
117 if ppq == 0 {
118 return Err(MusicError::InvalidPpq);
119 }
120 let ticks = *time.numer() * 4 * i64::from(ppq) / *time.denom();
121 Ok(Tick { ticks, tpq: ppq })
122}
123
124pub fn tick_to_kernel_tick(tick: Tick, clock: Symbol) -> KernelTick {
126 KernelTick::new(
127 clock,
128 Ref::Symbol(Symbol::qualified(
129 "music/tick",
130 format!("{}@{}", tick.ticks, tick.tpq),
131 )),
132 )
133}