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}