redact_composer_core/timing.rs
1use crate::derive::Element;
2use std::collections::Bound;
3use std::collections::Bound::{Excluded, Included, Unbounded};
4use std::ops::{Range, RangeBounds};
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9/// The default beat length which divides evenly for many common factors.
10pub const STANDARD_BEAT_LENGTH: i32 = 480;
11/// Higher precision beat length if greater divisibility is required.
12pub const HIGH_PRECISION_BEAT_LENGTH: i32 = 960;
13
14/// Types implementing [`Element`].
15pub mod elements {
16 pub use super::Tempo;
17}
18
19/// The speed of a (or part of a) composition in beats per minute.
20#[derive(Element, Debug, Copy, Clone)]
21#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
22pub struct Tempo {
23 pub(super) bpm: u32,
24}
25
26impl Tempo {
27 /// Creates a [`Tempo`] from beats per measure.
28 pub fn from_bpm(bpm: u32) -> Tempo {
29 Tempo { bpm }
30 }
31
32 /// Returns this tempo with units of microseconds per beat.
33 pub fn microseconds_per_beat(&self) -> u32 {
34 60_000_000 / self.bpm
35 }
36
37 /// Returns this tempo with units of beats per minute.
38 pub fn bpm(&self) -> u32 {
39 self.bpm
40 }
41}
42
43/// A start-inclusive, end-exclusive [`i32`] range (like [`Range<i32>`]) that is copyable,
44/// and implements several utility methods.
45#[derive(Debug, Copy, Clone, Eq, PartialEq)]
46#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
47pub struct Timing {
48 /// The inclusive start of this interval.
49 pub start: i32,
50 /// The exclusive end of this interval.
51 pub end: i32,
52}
53
54impl RangeBounds<i32> for Timing {
55 fn start_bound(&self) -> Bound<&i32> {
56 Bound::Included(&self.start)
57 }
58
59 fn end_bound(&self) -> Bound<&i32> {
60 Bound::Excluded(&self.end)
61 }
62}
63
64impl From<&Timing> for Timing {
65 fn from(value: &Timing) -> Self {
66 *value
67 }
68}
69
70impl From<Range<i32>> for Timing {
71 fn from(value: Range<i32>) -> Self {
72 Self {
73 start: value.start,
74 end: value.end,
75 }
76 }
77}
78
79impl From<&Range<i32>> for Timing {
80 fn from(value: &Range<i32>) -> Self {
81 Self {
82 start: value.start,
83 end: value.end,
84 }
85 }
86}
87
88impl From<Timing> for Range<i32> {
89 fn from(value: Timing) -> Self {
90 value.start..value.end
91 }
92}
93
94impl From<&Timing> for Range<i32> {
95 fn from(value: &Timing) -> Self {
96 value.start..value.end
97 }
98}
99
100impl Timing {
101 /// Returns the length of this timing (`self.end` - `self.start`).
102 pub fn len(&self) -> i32 {
103 self.end - self.start
104 }
105 /// Splits this timing into sequentual pieces of a given `size`.
106 /// Returns the resulting `Vec`.
107 /// ```
108 /// # use redact_composer_core::timing::Timing;
109 /// let split = Timing::from(0..10).divide_into(5);
110 /// assert_eq!(split[0], Timing::from(0..5));
111 /// assert_eq!(split[1], Timing::from(5..10));
112 /// ```
113 #[inline]
114 pub fn divide_into(&self, size: i32) -> Vec<Timing> {
115 <Range<i32>>::from(self)
116 .step_by(size as usize)
117 .scan((0, self.start), |s, _| {
118 s.0 = s.1;
119 s.1 = s.0 + size;
120
121 Some(s.0..s.1)
122 })
123 .map(Timing::from)
124 .collect::<Vec<_>>()
125 }
126
127 /// Returns a new [`Timing`] with start/end shifted by the given `amount`.
128 /// ```
129 /// # use redact_composer_core::timing::Timing;
130 /// assert_eq!(Timing::from(0..10).shifted_by(10), Timing::from(10..20))
131 /// ```
132 /// May also accept a `(i32, i32)` tuple representing different start/end shifts.
133 /// ```
134 /// # use redact_composer_core::timing::Timing;
135 /// assert_eq!(Timing::from(1..10).shifted_by((-1, 1)), Timing::from(0..11))
136 /// ```
137 #[inline]
138 pub fn shifted_by<O: EndpointOffsets>(&self, amount: O) -> Timing {
139 Self {
140 start: self.start + amount.start_offset(),
141 end: self.end + amount.end_offset(),
142 }
143 }
144
145 /// Returns a new [`Timing`] with the start shifted by the given `amount`.
146 /// ```
147 /// # use redact_composer_core::timing::Timing;
148 /// assert_eq!(Timing::from(0..10).start_shifted_by(5), Timing::from(5..10))
149 /// ```
150 #[inline]
151 pub fn start_shifted_by(&self, amount: i32) -> Timing {
152 Self {
153 start: self.start + amount,
154 end: self.end,
155 }
156 }
157
158 /// Returns a new [`Timing`] with the end shifted by the given `amount`.
159 /// ```
160 /// # use redact_composer_core::timing::Timing;
161 /// assert_eq!(Timing::from(0..10).end_shifted_by(-5), Timing::from(0..5))
162 /// ```
163 #[inline]
164 pub fn end_shifted_by(&self, amount: i32) -> Timing {
165 Self {
166 start: self.start,
167 end: self.end + amount,
168 }
169 }
170
171 // Passthrough impls for RangeBounds<i32> so the trait doesn't need to be `use`ed.
172 /// Checks if a particular `&i32` is contained in this [`Timing`].
173 /// ```
174 /// # use redact_composer_core::timing::Timing;
175 /// assert_eq!(Timing::from(1..10).contains(&1), true);
176 /// assert_eq!(Timing::from(1..10).contains(&10), false);
177 /// ```
178 pub fn contains(&self, item: &i32) -> bool {
179 <Self as RangeBounds<i32>>::contains(self, item)
180 }
181
182 // Passthrough impls for RangeChecks so the trait doesn't need to be `use`ed.
183 /// Checks if this [`Timing`] is empty (including negative).
184 /// ```
185 /// # use redact_composer_core::timing::Timing;
186 /// assert_eq!(Timing::from(1..1).is_empty(), true);
187 /// assert_eq!(Timing::from(1..0).is_empty(), true);
188 /// assert_eq!(Timing::from(1..2).is_empty(), false);
189 /// ```
190 #[inline]
191 pub fn is_empty(&self) -> bool {
192 RangeOps::is_empty(self)
193 }
194
195 /// Checks if this [`Timing`] is before some other [`Timing`] (or other [`RangeBounds<i32>`]).
196 /// ```
197 /// # use redact_composer_core::timing::Timing;
198 /// assert_eq!(Timing::from(0..1).is_before(&Timing::from(1..2)), true);
199 /// assert_eq!(Timing::from(1..2).is_before(&Timing::from(0..1)), false);
200 /// ```
201 #[inline]
202 pub fn is_before(&self, other: &impl RangeBounds<i32>) -> bool {
203 RangeOps::is_before(self, other)
204 }
205
206 /// Checks if this [`Timing`] is after some other [`Timing`] (or other [`RangeBounds<i32>`]).
207 /// ```
208 /// # use redact_composer_core::timing::Timing;
209 /// assert_eq!(Timing::from(0..1).is_after(&Timing::from(1..2)), false);
210 /// assert_eq!(Timing::from(1..2).is_after(&Timing::from(0..1)), true);
211 /// ```
212 #[inline]
213 pub fn is_after(&self, other: &impl RangeBounds<i32>) -> bool {
214 RangeOps::is_after(self, other)
215 }
216
217 /// Checks if this [`Timing`] does not overlap with another [`Timing`] (or other
218 /// [`RangeBounds<i32>`]).
219 /// ```
220 /// # use redact_composer_core::timing::Timing;
221 /// assert_eq!(Timing::from(0..2).is_disjoint_from(&Timing::from(2..3)), true);
222 /// assert_eq!(Timing::from(0..2).is_disjoint_from(&Timing::from(1..3)), false);
223 /// ```
224 #[inline]
225 pub fn is_disjoint_from(&self, other: &impl RangeBounds<i32>) -> bool {
226 RangeOps::is_disjoint_from(self, other)
227 }
228
229 /// Checks if this [`Timing`] overlaps with another [`Timing`] (or other [`RangeBounds<i32>`]).
230 /// ```
231 /// # use redact_composer_core::timing::Timing;
232 /// assert_eq!(Timing::from(0..2).intersects(&Timing::from(1..3)), true);
233 /// assert_eq!(Timing::from(0..2).intersects(&Timing::from(2..3)), false);
234 /// ```
235 #[inline]
236 pub fn intersects(&self, other: &impl RangeBounds<i32>) -> bool {
237 RangeOps::intersects(self, other)
238 }
239
240 /// Checks if this [`Timing`] contains another [`Timing`] (or other [`RangeBounds<i32>`]).
241 /// ```
242 /// # use redact_composer_core::timing::Timing;
243 /// assert_eq!(Timing::from(0..2).contains_range(&Timing::from(0..1)), true);
244 /// assert_eq!(Timing::from(0..2).contains_range(&Timing::from(1..3)), false);
245 /// ```
246 #[inline]
247 pub fn contains_range(&self, other: &impl RangeBounds<i32>) -> bool {
248 RangeOps::contains_range(self, other)
249 }
250
251 /// Checks if this [`Timing`] is contained by another [`Timing`] (or other
252 /// [`RangeBounds<i32>`]).
253 /// ```
254 /// # use redact_composer_core::timing::Timing;
255 /// assert_eq!(Timing::from(0..2).is_contained_by(&Timing::from(0..3)), true);
256 /// assert_eq!(Timing::from(0..2).is_contained_by(&Timing::from(0..1)), false);
257 /// ```
258 #[inline]
259 pub fn is_contained_by(&self, other: &impl RangeBounds<i32>) -> bool {
260 RangeOps::is_contained_by(self, other)
261 }
262
263 /// Checks if this [`Timing`] begins within another [`Timing`] (or other [`RangeBounds<i32>`]).
264 /// ```
265 /// # use redact_composer_core::timing::Timing;
266 /// assert_eq!(Timing::from(0..1).begins_within(&Timing::from(0..2)), true);
267 /// assert_eq!(Timing::from(0..1).begins_within(&Timing::from(1..2)), false);
268 /// ```
269 #[inline]
270 pub fn begins_within(&self, other: &impl RangeBounds<i32>) -> bool {
271 RangeOps::begins_within(self, other)
272 }
273
274 /// Checks if this [`Timing`] ends within another [`Timing`] (or other [`RangeBounds<i32>`]).
275 /// ```
276 /// # use redact_composer_core::timing::Timing;
277 /// assert_eq!(Timing::from(0..2).ends_within(&Timing::from(0..2)), true);
278 /// assert_eq!(Timing::from(0..2).ends_within(&Timing::from(0..1)), false);
279 /// ```
280 #[inline]
281 pub fn ends_within(&self, other: &impl RangeBounds<i32>) -> bool {
282 RangeOps::ends_within(self, other)
283 }
284}
285
286/// Defines a start/end offsets for use when shifting a [`Timing`]'s boundaries.
287pub trait EndpointOffsets {
288 /// Offset to be applied to the [`Timing`]'s start bound.
289 fn start_offset(&self) -> i32;
290 /// Offset to be applied to the [`Timing`]'s end bound.
291 fn end_offset(&self) -> i32;
292}
293
294impl EndpointOffsets for (i32, i32) {
295 fn start_offset(&self) -> i32 {
296 self.0
297 }
298
299 fn end_offset(&self) -> i32 {
300 self.1
301 }
302}
303
304impl EndpointOffsets for i32 {
305 fn start_offset(&self) -> i32 {
306 *self
307 }
308
309 fn end_offset(&self) -> i32 {
310 *self
311 }
312}
313
314/// Convenient interval comparisons.
315pub trait RangeOps<T>: RangeBounds<T> {
316 /// Checks if an interval is empty.
317 fn is_empty(&self) -> bool;
318 /// Checks if an interval has no overlap with another.
319 fn is_disjoint_from(&self, other: &impl RangeBounds<T>) -> bool;
320 /// Checks if this interval has some overlap with another.
321 fn intersects(&self, other: &impl RangeBounds<T>) -> bool;
322 /// Checks if this interval ends before the start of another.
323 fn is_before(&self, other: &impl RangeBounds<T>) -> bool;
324 /// Checks if this interval starts after the end of another.
325 fn is_after(&self, other: &impl RangeBounds<T>) -> bool;
326 /// Checks if this interval starts within another.
327 fn begins_within(&self, other: &impl RangeBounds<T>) -> bool;
328 /// Checks if this interval ends within another.
329 fn ends_within(&self, other: &impl RangeBounds<T>) -> bool;
330 /// Checks if this interval contains another.
331 fn contains_range(&self, other: &impl RangeBounds<T>) -> bool;
332 /// Checks if this interval is contained by another.
333 fn is_contained_by(&self, other: &impl RangeBounds<T>) -> bool;
334}
335
336impl<T, R> RangeOps<T> for R
337where
338 T: PartialOrd,
339 R: RangeBounds<T>,
340{
341 #[inline]
342 fn is_empty(&self) -> bool {
343 match (self.start_bound(), self.end_bound()) {
344 (Included(s), Excluded(e))
345 | (Excluded(s), Included(e))
346 | (Excluded(s), Excluded(e)) => e <= s,
347 (Included(s), Included(e)) => e < s,
348 (Included(_), Unbounded)
349 | (Excluded(_), Unbounded)
350 | (Unbounded, Included(_))
351 | (Unbounded, Excluded(_))
352 | (Unbounded, Unbounded) => false,
353 }
354 }
355
356 #[inline]
357 fn is_before(&self, other: &impl RangeBounds<T>) -> bool {
358 <(Bound<&T>, Bound<&T>) as RangeOps<T>>::is_empty(&(other.start_bound(), self.end_bound()))
359 }
360
361 #[inline]
362 fn is_after(&self, other: &impl RangeBounds<T>) -> bool {
363 <(Bound<&T>, Bound<&T>) as RangeOps<T>>::is_empty(&(self.start_bound(), other.end_bound()))
364 }
365
366 #[inline]
367 fn is_disjoint_from(&self, other: &impl RangeBounds<T>) -> bool {
368 self.is_before(other) || self.is_after(other)
369 }
370
371 #[inline]
372 fn intersects(&self, other: &impl RangeBounds<T>) -> bool {
373 !self.is_disjoint_from(other)
374 }
375
376 #[inline]
377 fn contains_range(&self, other: &impl RangeBounds<T>) -> bool {
378 (match self.end_bound() {
379 Included(b) => other.is_before(&(Excluded(b), Unbounded)),
380 Excluded(b) => other.is_before(&(Included(b), Unbounded)),
381 Unbounded => true,
382 } && match self.start_bound() {
383 Included(b) => other.is_after(&(Unbounded, Excluded(b))),
384 Excluded(b) => other.is_after(&(Unbounded, Included(b))),
385 Unbounded => true,
386 })
387 }
388
389 #[inline]
390 fn is_contained_by(&self, other: &impl RangeBounds<T>) -> bool {
391 other.contains_range(self)
392 }
393
394 #[inline]
395 fn begins_within(&self, other: &impl RangeBounds<T>) -> bool {
396 !self.is_after(other) && other.contains_range(&(self.start_bound(), other.end_bound()))
397 }
398
399 #[inline]
400 fn ends_within(&self, other: &impl RangeBounds<T>) -> bool {
401 !self.is_before(other) && other.contains_range(&(other.start_bound(), self.end_bound()))
402 }
403}
404
405/// Convenience methods for `[Vec<Timing>]`.
406pub trait TimingSequenceUtil {
407 /// Joins the sequence of `Timing`s, merging overlapping/continuous regions.
408 fn join(&self) -> Vec<Timing>;
409}
410
411impl TimingSequenceUtil for Vec<Timing> {
412 /// Joins a sequence of [`Timing`]s. Overlapping or continuously sequential
413 /// (i.e. end == start) are merged and the resulting sequence is returned.
414 /// ```
415 /// # use redact_composer_core::timing::Timing;
416 /// # use redact_composer_core::timing::TimingSequenceUtil;
417 /// assert_eq!(
418 /// vec![Timing::from(0..4), Timing::from(2..5), Timing::from(6..10)].join(),
419 /// vec![Timing::from(0..5), Timing::from(6..10)]
420 /// );
421 /// ```
422 fn join(&self) -> Vec<Timing> {
423 if let Some(first) = self.first().copied() {
424 let mut joined = vec![first];
425
426 for next in (0..self.len()).skip(1) {
427 let joined_len = joined.len();
428 if joined[joined_len - 1].contains(&self[next].start)
429 || joined[joined_len - 1].end == self[next].start
430 {
431 joined[joined_len - 1].end = self[next].end;
432 } else {
433 joined.push(Timing::from(self[next].start..self[next].end));
434 }
435 }
436
437 joined
438 } else {
439 vec![]
440 }
441 }
442}