sim_lib_stream_clock/
tempo.rs1use sim_kernel::{Error, Result};
2
3use crate::Instant;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct TempoSegment {
23 pub start_tick: u64,
25 pub us_per_quarter: u32,
27}
28
29impl TempoSegment {
30 pub fn new(start_tick: u64, us_per_quarter: u32) -> Result<Self> {
34 if us_per_quarter == 0 {
35 return Err(Error::Eval(
36 "tempo segment microseconds-per-quarter must be non-zero".to_owned(),
37 ));
38 }
39 Ok(Self {
40 start_tick,
41 us_per_quarter,
42 })
43 }
44}
45
46#[derive(Clone, Debug, PartialEq, Eq)]
64pub struct TempoMap {
65 segments: Vec<TempoSegment>,
66}
67
68impl TempoMap {
69 pub fn new(segments: Vec<TempoSegment>) -> Result<Self> {
75 let first = segments
76 .first()
77 .ok_or_else(|| Error::Eval("tempo map must contain at least one segment".to_owned()))?;
78 if first.start_tick != 0 {
79 return Err(Error::Eval(
80 "tempo map must start with a segment at tick 0".to_owned(),
81 ));
82 }
83 for pair in segments.windows(2) {
84 if pair[0].start_tick >= pair[1].start_tick {
85 return Err(Error::Eval(
86 "tempo map segment ticks must be strictly increasing".to_owned(),
87 ));
88 }
89 }
90 Ok(Self { segments })
91 }
92
93 pub fn single(us_per_quarter: u32) -> Result<Self> {
97 Self::new(vec![TempoSegment::new(0, us_per_quarter)?])
98 }
99
100 pub fn segments(&self) -> &[TempoSegment] {
102 &self.segments
103 }
104
105 pub(crate) fn segment_start_instants(&self, tpq: u32) -> Result<Vec<Instant>> {
106 if tpq == 0 {
107 return Err(Error::Eval("midi clock TPQ must be non-zero".to_owned()));
108 }
109 let mut starts = Vec::with_capacity(self.segments.len());
110 starts.push(Instant::seconds(0));
111 let mut current = Instant::seconds(0);
112 for pair in self.segments.windows(2) {
113 let delta_ticks = pair[1].start_tick - pair[0].start_tick;
114 let duration = midi_tick_duration(delta_ticks, tpq, pair[0].us_per_quarter)?;
115 current = current.checked_add(duration)?;
116 starts.push(current);
117 }
118 Ok(starts)
119 }
120}
121
122pub(crate) fn midi_tick_duration(ticks: u64, tpq: u32, us_per_quarter: u32) -> Result<Instant> {
123 if tpq == 0 {
124 return Err(Error::Eval("midi clock TPQ must be non-zero".to_owned()));
125 }
126 if us_per_quarter == 0 {
127 return Err(Error::Eval(
128 "tempo segment microseconds-per-quarter must be non-zero".to_owned(),
129 ));
130 }
131 let numerator = i128::from(ticks)
132 .checked_mul(i128::from(us_per_quarter))
133 .ok_or_else(|| Error::Eval("midi tick duration overflowed".to_owned()))?;
134 let denominator = i128::from(tpq) * 1_000_000;
135 Instant::new(numerator, denominator)
136}