sampled_data_duration/
lib.rs

1//! Work with durations of time-sampled data, like digital audio.
2//!
3//! The `sampled_data_duration` crate provides two stucts:
4//! `ConstantRateDuration` and `MixedRateDuration`.
5//!
6//! A `ConstantRateDuraiton` can be used to represents the duration of
7//! any data-set which has been sampled at a constant frequency, a
8//! prime example might be an audio file sampled at 44.1kHz.
9//!
10//! A `MixedRateDuration` can be used to represent the duration of a
11//! collection of data-sets which have different sampling
12//! frequencies. A typical example might be a playlist of audio files
13//! where some have been sampled at 44.1kHz, and others at 48kHz or
14//! 96kHz, etc.
15//!
16//! # Example
17//!
18//! ```
19//! use sampled_data_duration::ConstantRateDuration;
20//! use sampled_data_duration::MixedRateDuration;
21//!
22//! // Consider an audio file which consists of `12_345_678` samples per
23//! // channel recorded at a sampling rate of 44.1kHz.
24//! let crd = ConstantRateDuration::new(12_345_678, 44100);
25//!
26//! // The default string representation is of the form
27//! // `hh:mm:ss;samples`
28//! assert_eq!(crd.to_string(), "00:04:39;41778");
29//!
30//! // Get the duration in various different time-units
31//! assert_eq!(crd.as_hours(), 0);
32//! assert_eq!(crd.as_mins(), 4);
33//! assert_eq!(crd.submin_secs(), 39);
34//! assert_eq!(crd.as_secs(), 4 * 60 + 39);
35//! assert_eq!(crd.subsec_samples(), 41778);
36//! assert_eq!(crd.subsec_secs(), 0.9473469387755102);
37//!
38//! // Consider and audio playlist which already consists of a file
39//! // recorded at 96kHz.
40//! let mut mrd = MixedRateDuration::from(ConstantRateDuration::new(87654321, 96000));
41//!
42//! // The default string representation of the a MixedRateDuration
43//! // which consits of only one entry is of the form `hh:mm:ss;samples`
44//! assert_eq!(mrd.to_string(), "00:15:13;6321");
45//!
46//! // However if we add-assign `crd` to `mrd`
47//! mrd += crd;
48//!
49//! // Then we have a duration which is made up of different sampling
50//! // rates and the default string representation changes to be of the
51//! // form `hh:mm:ss.s`
52//! assert_eq!(mrd.to_string(), "00:19:53.013190688");
53//!```
54//!
55//! An attempt has been made to follow the naming conventions defined
56//! by [`std::time::Duration`].
57
58// Uncomment to get pedantic warnings when linting with `cargo clippy`.
59// #![warn(clippy::pedantic)]
60
61use std::collections::HashMap;
62use std::fmt;
63use std::ops::Add;
64use std::ops::AddAssign;
65use std::ops::Div;
66use std::ops::DivAssign;
67use std::ops::Mul;
68use std::ops::MulAssign;
69use std::ops::Sub;
70use std::ops::SubAssign;
71use std::time::Duration;
72
73// Various constants for converting between different units of time
74const NANOS_PER_SEC: f64 = 1_000_000_000.0;
75const SECS_PER_MIN: u64 = 60;
76const SECS_PER_HOUR: u64 = 3600;
77const SECS_PER_DAY: u64 = 86400;
78const SECS_PER_WEEK: u64 = 604_800;
79const DAYS_PER_WEEK: u64 = 7;
80const HOURS_PER_DAY: u64 = 24;
81const MINS_PER_HOUR: u64 = 60;
82
83/// Represents the duration of a dataset which has been sampled at a
84/// constant rate.
85///
86/// # Examples
87///
88/// Consider an audio file which consists of `8_394_223` samples per
89/// channel recorded with a 48kHz sampling frequency. We can see what
90/// the duration of this is by using the default implementation of
91/// `std::fmt::Display` for a `ConstantRateDuration` which outputs the
92/// duration in the form `hh:mm:ss;samples`.
93///
94/// ```
95/// use sampled_data_duration::ConstantRateDuration;
96///
97/// let crd = ConstantRateDuration::new(8_394_223, 48000);
98/// assert_eq!(crd.to_string(), "00:02:54;42223");
99///```
100#[derive(Clone, Copy, Debug, PartialEq)]
101pub struct ConstantRateDuration {
102    count: u64,
103    rate: u64,
104}
105impl ConstantRateDuration {
106    /// Construct a new `ConstantRateDuration`, where `count`
107    /// corresponds to the number of samples and `rate` is the
108    /// sampling rate in Hertz.
109    #[must_use]
110    pub fn new(count: u64, rate: u64) -> ConstantRateDuration {
111        ConstantRateDuration { count, rate }
112    }
113
114    /// Returns the number of _whole_ seconds contained by this `ConstantRateDuration`.
115    ///
116    /// The returned value does not include the fractional part of the
117    /// duration which can be obtained with [`subsec_secs`].
118    ///
119    /// # Example
120    ///
121    /// ```
122    /// use sampled_data_duration::ConstantRateDuration;
123    ///
124    /// let crd = ConstantRateDuration::new(48000 * 62 + 123, 48000);
125    /// assert_eq!(crd.to_string(), "00:01:02;123");
126    /// assert_eq!(crd.as_secs(), 62);
127    /// ```
128    /// [`subsec_secs`]: ConstantRateDuration::subsec_secs
129    #[must_use]
130    pub fn as_secs(&self) -> u64 {
131        self.count / self.rate
132    }
133
134    /// Returns the number of _whole_ minutes contained by this `ConstantRateDuration`.
135    ///
136    /// The returned value does not include the fractional part of the
137    /// duration, and can be a value greater than 59.
138    ///
139    /// # Example
140    ///
141    /// ```
142    /// use sampled_data_duration::ConstantRateDuration;
143    ///
144    /// let crd = ConstantRateDuration::new(48000 * 60 * 91, 48000);
145    /// assert_eq!(crd.to_string(), "01:31:00;0");
146    /// assert_eq!(crd.as_mins(), 91);
147    /// ```
148    #[must_use]
149    pub fn as_mins(&self) -> u64 {
150        self.as_secs() / SECS_PER_MIN
151    }
152
153    /// Returns the number of _whole_ hours contained by this `ConstantRateDuration`.
154    ///
155    /// The returned value does not include the fractional part of the
156    /// duration, and can be a value greater than 23.
157    ///
158    /// # Example
159    ///
160    /// ```
161    /// use sampled_data_duration::ConstantRateDuration;
162    ///
163    /// let crd = ConstantRateDuration::new(48000 * 60 * 60 * 48 + 12345, 48000);
164    /// assert_eq!(crd.to_string(), "48:00:00;12345");
165    /// assert_eq!(crd.as_hours(), 48);
166    /// ```
167    #[must_use]
168    pub fn as_hours(&self) -> u64 {
169        self.as_secs() / SECS_PER_HOUR
170    }
171
172    /// Returns the number of _whole_ days contained by this `ConstantRateDuration`.
173    ///
174    /// The returned value does not include the fractional part of the
175    /// duration, and can be a value greater than 6.
176    ///
177    /// # Example
178    ///
179    /// ```
180    /// use sampled_data_duration::ConstantRateDuration;
181    ///
182    /// let crd = ConstantRateDuration::new(48000 * 60 * 60 * 48 + 12345, 48000);
183    /// assert_eq!(crd.to_string(), "48:00:00;12345");
184    /// assert_eq!(crd.as_days(), 2);
185    /// ```
186    #[must_use]
187    pub fn as_days(&self) -> u64 {
188        self.as_secs() / SECS_PER_DAY
189    }
190
191    /// Returns the number of _whole_ weeks contained by this `ConstantRateDuration`.
192    ///
193    /// The returned value does not include the fractional part of the
194    /// duration.
195    ///
196    /// # Example
197    ///
198    /// ```
199    /// use sampled_data_duration::ConstantRateDuration;
200    ///
201    /// let crd = ConstantRateDuration::new(48000 * 60 * 60 * 24 * 21 + 12345, 48000);
202    /// assert_eq!(crd.to_string(), "504:00:00;12345");
203    /// assert_eq!(crd.as_weeks(), 3);
204    /// ```
205    #[must_use]
206    pub fn as_weeks(&self) -> u64 {
207        self.as_secs() / SECS_PER_WEEK
208    }
209
210    /// Returns the number of samples in the sub-second part of this
211    /// `ConstantRateDuration`.
212    ///
213    /// The returned value will always be less than the sampling rate.
214    ///
215    /// # Example
216    ///
217    /// ```
218    /// use sampled_data_duration::ConstantRateDuration;
219    ///
220    /// let crd = ConstantRateDuration::new(48000 + 12345, 48000);
221    /// assert_eq!(crd.to_string(), "00:00:01;12345");
222    /// assert_eq!(crd.subsec_samples(), 12345);
223    /// ```
224    #[must_use]
225    pub fn subsec_samples(&self) -> u64 {
226        self.count % self.rate
227    }
228
229    /// Returns the _whole_ number of nanoseconds in the fractional
230    /// part of this `ConstantRateDuration`.
231    ///
232    /// The returned value will always be less than one second i.e. >
233    /// `1_000_000_000` nanoseconds.
234    ///
235    /// # Example
236    ///
237    /// ```
238    /// use sampled_data_duration::ConstantRateDuration;
239    ///
240    /// let crd = ConstantRateDuration::new(48000 + 24000, 48000);
241    /// assert_eq!(crd.to_string(), "00:00:01;24000");
242    /// assert_eq!(crd.subsec_nanos(), 500_000_000);
243    /// ```
244    #[must_use]
245    pub fn subsec_nanos(&self) -> u32 {
246        let nanos_as_f64 = self.subsec_secs() * NANOS_PER_SEC;
247
248        nanos_as_f64 as u32
249    }
250
251    /// Return the sub-second part of this duration in seconds.
252    ///
253    /// This will return a value in the range `0.0 <= subsec_secs < 1.0`.
254    ///
255    /// # Example
256    ///
257    /// ```
258    /// use sampled_data_duration::ConstantRateDuration;
259    ///
260    /// let crd = ConstantRateDuration::new(48000 + 24000, 48000);
261    /// assert_eq!(crd.to_string(), "00:00:01;24000");
262    /// assert_eq!(crd.subsec_secs(), 0.5);
263    /// ```
264    #[must_use]
265    pub fn subsec_secs(&self) -> f64 {
266        self.subsec_samples() as f64 / self.rate as f64
267    }
268
269    /// Returns the _whole_ number of seconds left over when this duration is measured in minutes.
270    ///
271    /// The returned value will always be 0 <= `submin_secs` <= 59.
272    ///
273    /// # Example
274    ///
275    /// ```
276    /// use sampled_data_duration::ConstantRateDuration;
277    ///
278    /// let crd = ConstantRateDuration::new(48000 * 65 + 32000, 48000);
279    /// assert_eq!(crd.to_string(), "00:01:05;32000");
280    /// assert_eq!(crd.submin_secs(), 5);
281    /// ```
282    #[must_use]
283    pub fn submin_secs(&self) -> u64 {
284        self.as_secs() % SECS_PER_MIN
285    }
286
287    /// Returns the _whole_ number of minutes left over when this duration is measured in hours.
288    ///
289    /// The returned value will always be 0 <= `subhour_mins` <= 59.
290    ///
291    /// # Example
292    ///
293    /// ```
294    /// use sampled_data_duration::ConstantRateDuration;
295    ///
296    /// let crd = ConstantRateDuration::new(48000 * 60 * 68, 48000);
297    /// assert_eq!(crd.to_string(), "01:08:00;0");
298    /// assert_eq!(crd.subhour_mins(), 8);
299    /// ```
300    #[must_use]
301    pub fn subhour_mins(&self) -> u64 {
302        self.as_mins() % MINS_PER_HOUR
303    }
304
305    /// Returns the _whole_ number of hours left over when this duration is measured in days.
306    ///
307    /// The returned value will always be 0 <= `subday_hours` <= 23.
308    ///
309    /// # Example
310    ///
311    /// ```
312    /// use sampled_data_duration::ConstantRateDuration;
313    ///
314    /// let crd = ConstantRateDuration::new(48000 * 60 * 60 * 25, 48000);
315    /// assert_eq!(crd.to_string(), "25:00:00;0");
316    /// assert_eq!(crd.subday_hours(), 1);
317    /// ```
318    #[must_use]
319    pub fn subday_hours(&self) -> u64 {
320        self.as_hours() % HOURS_PER_DAY
321    }
322
323    /// Returns the _whole_ number of days left over when this duration is measured in weeks.
324    ///
325    /// The returned value will always be 0 <= `subweek_days` <= 6.
326    ///
327    /// # Example
328    ///
329    /// ```
330    /// use sampled_data_duration::ConstantRateDuration;
331    ///
332    /// let crd = ConstantRateDuration::new(48000 * 60 * 60 * 24 * 9, 48000);
333    /// assert_eq!(crd.to_string(), "216:00:00;0");
334    /// assert_eq!(crd.subweek_days(), 2);
335    /// ```
336    #[must_use]
337    pub fn subweek_days(&self) -> u64 {
338        self.as_days() % DAYS_PER_WEEK
339    }
340
341    /// Returns this `ConstantRateDuration` as a `std::time::Duration`.
342    ///
343    /// # Example
344    ///
345    /// ```
346    /// use sampled_data_duration::ConstantRateDuration;
347    ///
348    /// let crd = ConstantRateDuration::new(48000 * 42, 48000);
349    /// assert_eq!(crd.to_string(), "00:00:42;0");
350    /// assert_eq!(crd.to_duration().as_secs_f64(), 42.0);
351    /// ```
352    #[must_use]
353    pub fn to_duration(&self) -> Duration {
354        Duration::new(self.as_secs(), self.subsec_nanos())
355    }
356
357    /// Computes `self + other` returning `Ok(ConstantRateDuration)`
358    /// if the sampling rates of `self` and `other` are the same, or
359    /// `Err(MixedRateDuration)` if they are not.
360    ///
361    /// If the sample count of the new duration exceeds the maximum
362    /// capacity of a `u64` then this method will panic with the error
363    /// message `"overflow when adding ConstantRateDurations"`.
364    ///
365    /// # Examples
366    ///
367    /// ```
368    /// use sampled_data_duration::*;
369    ///
370    /// let a = ConstantRateDuration::new(48000, 48000);
371    /// let b = ConstantRateDuration::new(48000 * 2, 48000);
372    ///
373    /// if let Ok(c) = a.try_add(b) {
374    ///   assert_eq!(c.as_secs(), 3);
375    /// } else {
376    ///   assert!(false);
377    /// }
378    ///```
379    ///
380    /// # Errors
381    ///
382    /// Will return a `Err(MixedRateDuration)` if the provided
383    /// `ConstantRateDurations` have incommensurate sampling rates.
384    pub fn try_add(
385        self,
386        other: ConstantRateDuration,
387    ) -> Result<ConstantRateDuration, MixedRateDuration> {
388        if self.rate == other.rate {
389            return Ok(ConstantRateDuration::new(
390                self.count
391                    .checked_add(other.count)
392                    .expect("overflow when adding ConstantRateDurations"),
393                self.rate,
394            ));
395        }
396
397        let mut map: HashMap<u64, u64> = HashMap::with_capacity(2);
398        map.insert(self.rate, self.count);
399        map.insert(other.rate, other.count);
400        let mut mrd = MixedRateDuration {
401            duration: Duration::new(0, 0),
402            map,
403        };
404        mrd.update_duration();
405
406        Err(mrd)
407    }
408
409    /// Add-assigns `crd` to this `ConstantRateDuration` returning
410    /// `Ok(())` if the sampling rates of `self` and `other` are the
411    /// same, or `Err(MixedRateDuration)` if they are not.
412    ///
413    /// If the sample count of updated duration exceeds the maximum
414    /// capacity of a `u64` then this method will panic with the error
415    /// message `"overflow when add-assigning ConstantRateDurations"`.
416    ///
417    /// # Examples
418    ///
419    /// ```
420    /// use sampled_data_duration::*;
421    ///
422    /// let mut a = ConstantRateDuration::new(48000, 48000);
423    /// let b = ConstantRateDuration::new(48000 * 2, 48000);
424    ///
425    /// if let Ok(()) = a.try_add_assign(b) {
426    ///   assert_eq!(a.as_secs(), 3);
427    /// } else {
428    ///   assert!(false);
429    /// }
430    ///```
431    ///
432    /// # Errors
433    ///
434    /// Will return a `Err(MixedRateDuration)` if the provided
435    /// `ConstantRateDurations` have incommensurate sampling rates.
436    pub fn try_add_assign(&mut self, crd: ConstantRateDuration) -> Result<(), MixedRateDuration> {
437        if self.rate == crd.rate {
438            self.count = self
439                .count
440                .checked_add(crd.count)
441                .expect("overflow when add-assigning ConstantRateDurations");
442            return Ok(());
443        }
444
445        let mut map: HashMap<u64, u64> = HashMap::with_capacity(2);
446        map.insert(self.rate, self.count);
447        map.insert(crd.rate, crd.count);
448
449        let mut mixed_rate_duration = MixedRateDuration {
450            duration: Duration::new(0, 0),
451            map,
452        };
453        mixed_rate_duration.update_duration();
454
455        Err(mixed_rate_duration)
456    }
457
458    /// Computes `self - other` returning `Ok(ConstantRateDuration)`
459    /// if the sampling rates of `self` and `other` are the same, or
460    /// `Err(())` if they are not.
461    ///
462    /// If the sample count of the new duration would be less than 0
463    /// then then returned `ConstantRateDuraiton` will have 0 duration
464    /// — this is a what is meant by saturating..
465    ///
466    /// # Examples
467    ///
468    /// ```
469    /// use sampled_data_duration::*;
470    ///
471    /// let a = ConstantRateDuration::new(48001, 48000);
472    /// let b = ConstantRateDuration::new(48000, 48000);
473    ///
474    /// assert_eq!(a.try_saturating_sub(b), Ok(ConstantRateDuration::new(1, 48000)));
475    ///
476    /// let c = ConstantRateDuration::new(48001, 48000);
477    /// let d = ConstantRateDuration::new(48000, 48000);    
478    /// assert_eq!(d.try_saturating_sub(c), Ok(ConstantRateDuration::new(0, 48000)));
479    ///
480    /// let e = ConstantRateDuration::new(96000, 96000);
481    /// let f = ConstantRateDuration::new(48000, 48000);
482    /// assert_eq!(e.try_saturating_sub(f), Err(Error::IncommensurateRates));
483    ///```
484    ///
485    /// # Errors
486    ///
487    /// Will return an `Error::IncommensurateRates` if the provided
488    /// `ConstantRateDurations` have incommensurate sampling rates.
489    pub fn try_saturating_sub(
490        self,
491        other: ConstantRateDuration,
492    ) -> Result<ConstantRateDuration, Error> {
493        if self.rate == other.rate {
494            return Ok(ConstantRateDuration::new(
495                self.count.saturating_sub(other.count),
496                self.rate,
497            ));
498        }
499
500        Err(Error::IncommensurateRates)
501    }
502
503    /// Sub-assigns `crd` from this `ConstantRateDuration` returning
504    /// `Ok(())` if the sampling rates of `self` and `other` are the
505    /// same, or `Err(())` if they are not.
506    ///
507    /// If the sample count of updated duration would be negative then
508    /// it "saturates" and becomes zero.
509    ///
510    /// # Examples
511    ///
512    /// ```
513    /// use sampled_data_duration::*;
514    ///
515    /// let mut a = ConstantRateDuration::new(10, 48000);
516    /// let b = ConstantRateDuration::new(2, 48000);
517    ///
518    /// if let Ok(()) = a.try_saturating_sub_assign(b) {
519    ///   assert_eq!(a.subsec_samples(), 8);
520    /// } else {
521    ///   assert!(false);
522    /// }
523    ///
524    /// let mut c = ConstantRateDuration::new(1, 48000);
525    /// let d = ConstantRateDuration::new(5, 48000);
526    ///
527    /// if let Ok(()) = c.try_saturating_sub_assign(d) {
528    ///   assert_eq!(c.subsec_samples(), 0);
529    /// } else {
530    ///   assert!(false);
531    /// }
532    ///
533    /// let mut e = ConstantRateDuration::new(96000, 96000);
534    /// let f = ConstantRateDuration::new(48000, 48000);
535    /// assert!(e.try_saturating_sub(f).is_err());
536    ///```
537    ///
538    /// # Errors
539    ///
540    /// Will return an `Error::IncommensurateRates` if the provided
541    /// `ConstantRateDurations` have incommensurate sampling rates.
542    pub fn try_saturating_sub_assign(&mut self, crd: ConstantRateDuration) -> Result<(), Error> {
543        if self.rate == crd.rate {
544            self.count = self.count.saturating_sub(crd.count);
545            return Ok(());
546        }
547
548        Err(Error::IncommensurateRates)
549    }
550}
551impl fmt::Display for ConstantRateDuration {
552    /// Display this `ConstantRateDuration` in the form `hh:mm:ss;samples`.
553    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
554        let hours = self.as_hours();
555
556        if hours < 10 {
557            return write!(
558                f,
559                "{:02}:{:02}:{:02};{}",
560                hours,
561                self.subhour_mins(),
562                self.submin_secs(),
563                self.subsec_samples()
564            );
565        }
566
567        write!(
568            f,
569            "{}:{:02}:{:02};{}",
570            hours,
571            self.subhour_mins(),
572            self.submin_secs(),
573            self.subsec_samples()
574        )
575    }
576}
577impl Div<u64> for ConstantRateDuration {
578    type Output = Self;
579
580    /// Divide a `ConstantRateDuration` by a `u64` returning a new
581    /// `ConstantRateDuration`.
582    fn div(self, rhs: u64) -> Self {
583        ConstantRateDuration::new(
584            self.count
585                .checked_div(rhs)
586                .expect("divide by zero when dividing a ConstantRateDuration"),
587            self.rate,
588        )
589    }
590}
591impl DivAssign<u64> for ConstantRateDuration {
592    /// Divide-assign a `ConstantRateDuration` by a `u64`.
593    fn div_assign(&mut self, rhs: u64) {
594        self.count = self
595            .count
596            .checked_div(rhs)
597            .expect("divide by zero when divide-assigning a ConstantRateDuration");
598    }
599}
600impl Mul<u64> for ConstantRateDuration {
601    type Output = Self;
602
603    /// Multiply a `ConstantRateDuration` by a `u64` returning a new
604    /// `ConstantRateDuration`.
605    fn mul(self, rhs: u64) -> Self {
606        ConstantRateDuration::new(
607            self.count
608                .checked_mul(rhs)
609                .expect("overflow when multiplying ConstantRateDuration"),
610            self.rate,
611        )
612    }
613}
614impl MulAssign<u64> for ConstantRateDuration {
615    /// Multiply-assign a `ConstantRateDuration` by a `u64`.
616    fn mul_assign(&mut self, rhs: u64) {
617        self.count = self
618            .count
619            .checked_mul(rhs)
620            .expect("overflow when multiply-assigning a ConstantRateDuration");
621    }
622}
623
624#[cfg(test)]
625mod constant_rate_duration {
626    use super::*;
627
628    #[test]
629    fn new() {
630        let count: u64 = 15_345_873;
631        let rate: u64 = 44100;
632        let mut crd: ConstantRateDuration = ConstantRateDuration::new(count, rate);
633
634        assert_eq!(crd.count, count);
635        assert_eq!(crd.rate, rate);
636
637        let updated_count: u64 = 26_326_888;
638        crd.count = updated_count;
639        assert_eq!(crd.count, updated_count);
640
641        let updated_rate: u64 = 48000;
642        crd.rate = updated_rate;
643        assert_eq!(crd.rate, updated_rate);
644    }
645
646    #[test]
647    fn as_and_sub_time_unit_methods() {
648        let subsec_samples: u64 = 24_000;
649        let submin_secs: u64 = 32;
650        let subhour_mins: u64 = 23;
651        let subday_hours: u64 = 19;
652        let subweek_days: u64 = 4;
653        let weeks: u64 = 1;
654        let rate: u64 = 48_000;
655
656        let as_days = weeks * DAYS_PER_WEEK + subweek_days;
657        let as_hours = as_days * HOURS_PER_DAY + subday_hours;
658        let as_mins = as_hours * MINS_PER_HOUR + subhour_mins;
659        let as_secs = as_mins * SECS_PER_MIN + submin_secs;
660
661        let total_secs: u64 = weeks * SECS_PER_WEEK
662            + subweek_days * SECS_PER_DAY
663            + subday_hours * SECS_PER_HOUR
664            + subhour_mins * SECS_PER_MIN
665            + submin_secs;
666        let count: u64 = total_secs * rate + subsec_samples;
667        let crd: ConstantRateDuration = ConstantRateDuration::new(count, rate);
668
669        assert_eq!(crd.count, count);
670        assert_eq!(crd.rate, rate);
671        assert_eq!(crd.as_secs(), as_secs);
672        assert_eq!(crd.as_mins(), as_mins);
673        assert_eq!(crd.as_hours(), as_hours);
674        assert_eq!(crd.as_days(), as_days);
675        assert_eq!(crd.as_weeks(), weeks);
676        assert_eq!(crd.subsec_samples(), subsec_samples);
677        assert_eq!(crd.subsec_nanos(), 500_000_000);
678        assert_eq!(crd.subsec_secs(), 0.5);
679        assert_eq!(crd.submin_secs(), submin_secs);
680        assert_eq!(crd.subhour_mins(), subhour_mins);
681        assert_eq!(crd.subday_hours(), subday_hours);
682        assert_eq!(crd.subweek_days(), subweek_days);
683
684        // Check sample roll-over to seconds
685        let mut crd: ConstantRateDuration = ConstantRateDuration::new(47999, 48000);
686        assert_eq!(crd.as_secs(), 0);
687        assert_eq!(crd.subsec_samples(), 47999);
688        assert_eq!(crd.subsec_nanos(), 1_000_000_000 - 20834);
689
690        crd.count += 1;
691        assert_eq!(crd.as_secs(), 1);
692        assert_eq!(crd.subsec_samples(), 0);
693        assert_eq!(crd.subsec_nanos(), 0);
694
695        crd.count += 1;
696        assert_eq!(crd.as_secs(), 1);
697        assert_eq!(crd.subsec_samples(), 1);
698        assert_eq!(crd.subsec_nanos(), 20833);
699    }
700
701    #[test]
702    fn to_duration() {
703        let crd = ConstantRateDuration::new(48000, 48000);
704        assert_eq!(crd.to_duration(), Duration::new(1, 0));
705
706        let crd = ConstantRateDuration::new(48001, 48000);
707        assert_eq!(crd.to_duration(), Duration::new(1, 20833));
708    }
709
710    #[test]
711    fn try_add() {
712        // Add ConstantRateDurations with the same sample rate
713        let a = ConstantRateDuration::new(48000, 48000);
714        assert_eq!(a.as_secs(), 1);
715        assert_eq!(a.subsec_samples(), 0);
716
717        let b = ConstantRateDuration::new(1, 48000);
718        assert_eq!(b.as_secs(), 0);
719        assert_eq!(b.subsec_samples(), 1);
720
721        let result = a.try_add(b);
722        assert!(result.is_ok());
723        let c = result.unwrap();
724        assert_eq!(c.as_secs(), 1);
725        assert_eq!(c.subsec_samples(), 1);
726
727        // Add ConstantRateDurations with different sample rates
728        let a = ConstantRateDuration::new(48000, 48000);
729        assert_eq!(a.as_secs(), 1);
730        assert_eq!(a.subsec_samples(), 0);
731
732        let b = ConstantRateDuration::new(96000, 96000);
733        assert_eq!(b.as_secs(), 1);
734        assert_eq!(b.subsec_samples(), 0);
735
736        let result = a.try_add(b);
737        assert!(result.is_err());
738        let c = result.unwrap_err();
739        assert_eq!(c.as_secs(), 2);
740    }
741
742    #[test]
743    #[should_panic(expected = "overflow when adding ConstantRateDurations")]
744    fn try_add_overflow() {
745        // Add ConstantRateDurations which overflow
746        let a = ConstantRateDuration::new(u64::MAX, 48000);
747        let b = ConstantRateDuration::new(1, 48000);
748        let _c = a.try_add(b);
749    }
750
751    #[test]
752    fn try_add_assign() {
753        // Add-assign ConstantRateDurations with the same sampling rate
754        let mut a = ConstantRateDuration::new(48000, 48000);
755        let b = ConstantRateDuration::new(48000, 48000);
756        assert_eq!(a.as_secs(), 1);
757        assert_eq!(a.subsec_samples(), 0);
758        assert_eq!(b.as_secs(), 1);
759        assert_eq!(b.subsec_samples(), 0);
760        assert!(a.try_add_assign(b).is_ok());
761        assert_eq!(a.as_secs(), 2);
762        assert_eq!(a.subsec_samples(), 0);
763
764        // Add-assign ConstantRateDurations with different sampling rates
765        let mut a = ConstantRateDuration::new(48000, 48000);
766        let b = ConstantRateDuration::new(96000, 96000);
767        assert_eq!(a.as_secs(), 1);
768        assert_eq!(a.subsec_samples(), 0);
769        assert_eq!(b.as_secs(), 1);
770        assert_eq!(b.subsec_samples(), 0);
771
772        let result = a.try_add_assign(b);
773        assert!(result.is_err());
774        let c = result.unwrap_err();
775        assert_eq!(c.as_secs(), 2);
776    }
777
778    #[test]
779    #[should_panic(expected = "overflow when add-assigning ConstantRateDurations")]
780    fn try_add_assign_overflow() {
781        // Add-assign ConstantRateDurations which overflow
782        let mut a = ConstantRateDuration::new(u64::MAX, 48000);
783        let b = ConstantRateDuration::new(1, 48000);
784        let _c = a.try_add_assign(b);
785    }
786
787    #[test]
788    fn display() {
789        let crd = ConstantRateDuration::new(48000 + 12345, 48000);
790        assert_eq!(crd.to_string(), "00:00:01;12345");
791    }
792
793    #[test]
794    fn div() {
795        let a = ConstantRateDuration::new(96002, 48000);
796        assert_eq!(a.as_secs(), 2);
797        assert_eq!(a.subsec_samples(), 2);
798
799        let b = a / 2;
800        assert_eq!(b.as_secs(), 1);
801        assert_eq!(b.subsec_samples(), 1);
802    }
803
804    #[test]
805    #[should_panic(expected = "divide by zero when dividing a ConstantRateDuration")]
806    fn div_by_zero() {
807        // Divide a ConstantRateDurations by zero
808        let a = ConstantRateDuration::new(48000, 48000);
809        let _ = a / 0;
810    }
811
812    #[test]
813    fn div_assign() {
814        let mut a = ConstantRateDuration::new(96002, 48000);
815        assert_eq!(a.as_secs(), 2);
816        assert_eq!(a.subsec_samples(), 2);
817
818        a /= 2;
819        assert_eq!(a.as_secs(), 1);
820        assert_eq!(a.subsec_samples(), 1);
821    }
822
823    #[test]
824    #[should_panic(expected = "divide by zero when divide-assigning a ConstantRateDuration")]
825    fn div_assign_by_zero() {
826        // Divide-assign a ConstantRateDurations by zero
827        let mut a = ConstantRateDuration::new(48000, 48000);
828        a /= 0;
829    }
830
831    #[test]
832    fn mul() {
833        let a = ConstantRateDuration::new(48001, 48000);
834        assert_eq!(a.as_secs(), 1);
835        assert_eq!(a.subsec_samples(), 1);
836
837        let b = a * 2;
838        assert_eq!(b.as_secs(), 2);
839        assert_eq!(b.subsec_samples(), 2);
840    }
841
842    #[test]
843    #[should_panic(expected = "overflow when multiplying ConstantRateDuration")]
844    fn mul_overflow() {
845        // Multiply a ConstantRateDurations to overflow
846        let a = ConstantRateDuration::new(u64::MAX, 48000);
847        let _ = a * 2;
848    }
849
850    #[test]
851    fn mul_assign() {
852        let mut a = ConstantRateDuration::new(48001, 48000);
853        assert_eq!(a.as_secs(), 1);
854        assert_eq!(a.subsec_samples(), 1);
855
856        a *= 2;
857        assert_eq!(a.as_secs(), 2);
858        assert_eq!(a.subsec_samples(), 2);
859    }
860
861    #[test]
862    #[should_panic(expected = "overflow when multiply-assigning a ConstantRateDuration")]
863    fn mul_assign_overflow() {
864        // Multiply-assign a ConstantRateDurations to overflow
865        let mut a = ConstantRateDuration::new(u64::MAX, 48000);
866        a *= 2;
867    }
868
869    #[test]
870    fn try_saturating_sub() {
871        let a = ConstantRateDuration::new(2, 48000);
872        let b = ConstantRateDuration::new(1, 48000);
873
874        assert_eq!(
875            a.try_saturating_sub(b),
876            Ok(ConstantRateDuration::new(1, 48000))
877        );
878
879        let a = ConstantRateDuration::new(10, 48000);
880        let b = ConstantRateDuration::new(1, 48000);
881        assert_eq!(
882            b.try_saturating_sub(a),
883            Ok(ConstantRateDuration::new(0, 48000))
884        );
885
886        let a = ConstantRateDuration::new(96000, 96000);
887        let b = ConstantRateDuration::new(48000, 48000);
888        assert_eq!(a.try_saturating_sub(b), Err(Error::IncommensurateRates));
889    }
890
891    #[test]
892    fn try_saturating_sub_assign() {
893        let mut a = ConstantRateDuration::new(10, 48000);
894        let b = ConstantRateDuration::new(2, 48000);
895
896        assert!(a.try_saturating_sub_assign(b).is_ok());
897        assert_eq!(a.subsec_samples(), 8);
898
899        let mut a = ConstantRateDuration::new(1, 48000);
900        let b = ConstantRateDuration::new(5, 48000);
901
902        assert!(a.try_saturating_sub_assign(b).is_ok());
903        assert_eq!(a.subsec_samples(), 0);
904
905        let a = ConstantRateDuration::new(96000, 96000);
906        let b = ConstantRateDuration::new(48000, 48000);
907        assert!(a.try_saturating_sub(b).is_err());
908    }
909}
910
911/// Represents a duration of a collection of datasets which may have
912/// been sampled at different rates.
913///
914/// # Examples
915///
916/// Consider an audio playlist which at first consists of a single
917/// audio file of `12_345_678` samples per channel recorded with at
918/// 44.1kHz sampling frequency. We then add another audio file to this
919/// playlist which is recorded at 96kHz.
920///
921/// ```
922/// use sampled_data_duration::ConstantRateDuration;
923/// use sampled_data_duration::MixedRateDuration;
924///
925/// let mut mrd = MixedRateDuration::from(ConstantRateDuration::new(12_345_678, 48000));
926///
927/// // Note that the default string representation when there is only
928/// // a single sampling rate is of the form `hh:mm:ss;samples`.
929/// assert_eq!(mrd.to_string(), "00:04:17;9678");
930///
931/// // Now we add a 96kHz file
932/// let crd = ConstantRateDuration::new(31_415_926, 96000);
933/// assert_eq!(crd.to_string(), "00:05:27;23926");
934///
935/// mrd += crd;
936///
937/// // Note that the default string representation when there are
938/// // multiple sampling rates is of the form `hh:mm:ss.s`.
939/// assert_eq!(mrd.to_string(), "00:09:44.450854166");
940///```
941#[derive(Clone, Debug, PartialEq)]
942pub struct MixedRateDuration {
943    duration: Duration,
944    map: HashMap<u64, u64>,
945}
946impl MixedRateDuration {
947    /// Construct an empty `MixedRateDuration`.
948    ///
949    /// # Examples
950    ///
951    /// ```
952    /// use sampled_data_duration::MixedRateDuration;
953    ///
954    /// let mut mrd = MixedRateDuration::new();
955    ///
956    /// assert_eq!(mrd.to_string(), "00:00:00");
957    ///```
958    #[must_use]
959    pub fn new() -> MixedRateDuration {
960        MixedRateDuration {
961            duration: Duration::new(0, 0),
962            map: HashMap::new(),
963        }
964    }
965
966    /// Returns the number of _whole_ seconds contained by this
967    /// `MixedRateDuration`.
968    ///
969    /// The returned value does not include the fractional part of the
970    /// duration which can be obtained with [`subsec_secs`].
971    ///
972    /// # Example
973    ///
974    /// ```
975    /// use sampled_data_duration::ConstantRateDuration;
976    /// use sampled_data_duration::MixedRateDuration;
977    ///
978    /// let mrd = MixedRateDuration::from(ConstantRateDuration::new(48000 * 62 + 123, 48000));
979    /// assert_eq!(mrd.to_string(), "00:01:02;123");
980    /// assert_eq!(mrd.as_secs(), 62);
981    /// ```
982    /// [`subsec_secs`]: MixedRateDuration::subsec_secs
983    #[must_use]
984    pub fn as_secs(&self) -> u64 {
985        self.duration.as_secs()
986    }
987
988    /// Returns the number of _whole_ minutes contained by this `MixedRateDuration`.
989    ///
990    /// The returned value does not include the fractional part of the
991    /// duration, and can be a value greater than 59.
992    ///
993    /// # Example
994    ///
995    /// ```
996    /// use sampled_data_duration::ConstantRateDuration;
997    /// use sampled_data_duration::MixedRateDuration;
998    ///
999    /// let mrd = MixedRateDuration::from(ConstantRateDuration::new(48000 * 60 * 91, 48000));
1000    /// assert_eq!(mrd.to_string(), "01:31:00;0");
1001    /// assert_eq!(mrd.as_mins(), 91);
1002    /// ```
1003    #[must_use]
1004    pub fn as_mins(&self) -> u64 {
1005        self.as_secs() / SECS_PER_MIN
1006    }
1007
1008    /// Returns the number of _whole_ hours contained by this `MixedRateDuration`.
1009    ///
1010    /// The returned value does not include the fractional part of the
1011    /// duration, and can be a value greater than 23.
1012    ///
1013    /// # Example
1014    ///
1015    /// ```
1016    /// use sampled_data_duration::ConstantRateDuration;
1017    /// use sampled_data_duration::MixedRateDuration;
1018    ///
1019    /// let mrd = MixedRateDuration::from(ConstantRateDuration::new(48000 * 60 * 60 * 48 + 12345, 48000));
1020    /// assert_eq!(mrd.to_string(), "48:00:00;12345");
1021    /// assert_eq!(mrd.as_hours(), 48);
1022    /// ```
1023    #[must_use]
1024    pub fn as_hours(&self) -> u64 {
1025        self.as_secs() / SECS_PER_HOUR
1026    }
1027
1028    /// Returns the number of _whole_ days contained by this `MixedRateDuration`.
1029    ///
1030    /// The returned value does not include the fractional part of the
1031    /// duration, and can be a value greater than 6.
1032    ///
1033    /// # Example
1034    ///
1035    /// ```
1036    /// use sampled_data_duration::ConstantRateDuration;
1037    /// use sampled_data_duration::MixedRateDuration;
1038    ///
1039    /// let mrd = MixedRateDuration::from(ConstantRateDuration::new(48000 * 60 * 60 * 48 + 12345, 48000));
1040    /// assert_eq!(mrd.to_string(), "48:00:00;12345");
1041    /// assert_eq!(mrd.as_days(), 2);
1042    /// ```
1043    #[must_use]
1044    pub fn as_days(&self) -> u64 {
1045        self.as_secs() / SECS_PER_DAY
1046    }
1047
1048    /// Returns the number of _whole_ weeks contained by this `MixedRateDuration`.
1049    ///
1050    /// The returned value does not include the fractional part of the
1051    /// duration.
1052    ///
1053    /// # Example
1054    ///
1055    /// ```
1056    /// use sampled_data_duration::ConstantRateDuration;
1057    /// use sampled_data_duration::MixedRateDuration;
1058    ///
1059    /// let mrd = MixedRateDuration::from(ConstantRateDuration::new(48000 * 60 * 60 * 24 * 21 + 12345, 48000));
1060    /// assert_eq!(mrd.to_string(), "504:00:00;12345");
1061    /// assert_eq!(mrd.as_weeks(), 3);
1062    /// ```
1063    #[must_use]
1064    pub fn as_weeks(&self) -> u64 {
1065        self.as_secs() / SECS_PER_WEEK
1066    }
1067
1068    /// Return the number of different sampling rates used in this
1069    /// `MixedRateDuration`.
1070    ///
1071    /// # Example
1072    ///
1073    /// ```
1074    /// use sampled_data_duration::ConstantRateDuration;
1075    /// use sampled_data_duration::MixedRateDuration;
1076    ///
1077    /// let mut mrd = MixedRateDuration::from(ConstantRateDuration::new(1, 44100));
1078    /// assert_eq!(mrd.num_rates(), 1);
1079    ///
1080    /// mrd += ConstantRateDuration::new(1, 48000);
1081    /// assert_eq!(mrd.num_rates(), 2);
1082    ///
1083    /// mrd += ConstantRateDuration::new(1, 96000);
1084    /// assert_eq!(mrd.num_rates(), 3);
1085    ///
1086    /// mrd += ConstantRateDuration::new(2, 44100);
1087    /// assert_eq!(mrd.num_rates(), 3);
1088    /// ```
1089    #[must_use]
1090    pub fn num_rates(&self) -> usize {
1091        self.map.len()
1092    }
1093
1094    /// Returns the _whole_ number of nanoseconds in the fractional part of this `MixedRateDuration`.
1095    ///
1096    /// The returned value will always be less than one second i.e. >
1097    /// `1_000_000_000` nanoseconds.
1098    ///
1099    /// # Example
1100    ///
1101    /// ```
1102    /// use sampled_data_duration::ConstantRateDuration;
1103    /// use sampled_data_duration::MixedRateDuration;
1104    ///
1105    /// let mrd = MixedRateDuration::from(ConstantRateDuration::new(48000 + 24000, 48000));
1106    /// assert_eq!(mrd.to_string(), "00:00:01;24000");
1107    /// assert_eq!(mrd.subsec_nanos(), 500_000_000);
1108    /// ```
1109    #[must_use]
1110    pub fn subsec_nanos(&self) -> u32 {
1111        let nanos_as_f64 = self.subsec_secs() * NANOS_PER_SEC;
1112
1113        nanos_as_f64 as u32
1114    }
1115
1116    /// Returns the _whole_ number of seconds in the fractional part of this `MixedRateDuration`.
1117    ///
1118    /// The returned value will always be less than one second
1119    /// i.e. 0.0 <= `subsec_secs` < 1.0.
1120    ///
1121    /// # Example
1122    ///
1123    /// ```
1124    /// use sampled_data_duration::ConstantRateDuration;
1125    /// use sampled_data_duration::MixedRateDuration;
1126    ///
1127    /// let mrd = MixedRateDuration::from(ConstantRateDuration::new(48000 + 24000, 48000));
1128    /// assert_eq!(mrd.to_string(), "00:00:01;24000");
1129    /// assert_eq!(mrd.subsec_secs(), 0.5);
1130    /// ```
1131    #[must_use]
1132    pub fn subsec_secs(&self) -> f64 {
1133        f64::from(self.duration.subsec_nanos()) / NANOS_PER_SEC
1134    }
1135
1136    /// Returns the _whole_ number of seconds left over when this
1137    /// duration is measured in minutes.
1138    ///
1139    /// The returned value will always be 0 <= `submin_secs` <= 59.
1140    ///
1141    /// # Example
1142    ///
1143    /// ```
1144    /// use sampled_data_duration::ConstantRateDuration;
1145    /// use sampled_data_duration::MixedRateDuration;
1146    ///
1147    /// let mrd = MixedRateDuration::from(ConstantRateDuration::new(48000 * 65 + 32000, 48000));
1148    /// assert_eq!(mrd.to_string(), "00:01:05;32000");
1149    /// assert_eq!(mrd.submin_secs(), 5);
1150    /// ```
1151    #[must_use]
1152    pub fn submin_secs(&self) -> u64 {
1153        self.as_secs() % SECS_PER_MIN
1154    }
1155
1156    /// Returns the _whole_ number of minutes left over when this duration is measured in hours.
1157    ///
1158    /// The returned value will always be 0 <= `subhour_mins` <= 59.
1159    ///
1160    /// # Example
1161    ///
1162    /// ```
1163    /// use sampled_data_duration::ConstantRateDuration;
1164    /// use sampled_data_duration::MixedRateDuration;
1165    ///
1166    /// let mrd = MixedRateDuration::from(ConstantRateDuration::new(48000 * 60 * 68, 48000));
1167    /// assert_eq!(mrd.to_string(), "01:08:00;0");
1168    /// assert_eq!(mrd.subhour_mins(), 8);
1169    /// ```
1170    #[must_use]
1171    pub fn subhour_mins(&self) -> u64 {
1172        self.as_mins() % MINS_PER_HOUR
1173    }
1174
1175    /// Returns the _whole_ number of hours left over when this duration is measured in days.
1176    ///
1177    /// The returned value will always be 0 <= `subday_hours` <= 23.
1178    ///
1179    /// # Example
1180    ///
1181    /// ```
1182    /// use sampled_data_duration::ConstantRateDuration;
1183    /// use sampled_data_duration::MixedRateDuration;
1184    ///
1185    /// let mrd = MixedRateDuration::from(ConstantRateDuration::new(48000 * 60 * 60 * 25, 48000));
1186    /// assert_eq!(mrd.to_string(), "25:00:00;0");
1187    /// assert_eq!(mrd.subday_hours(), 1);
1188    /// ```
1189    #[must_use]
1190    pub fn subday_hours(&self) -> u64 {
1191        self.as_hours() % HOURS_PER_DAY
1192    }
1193
1194    /// Returns the _whole_ number of days left over when this duration is measured in weeks.
1195    ///
1196    /// The returned value will always be 0 <= `subweek_days` <= 6.
1197    ///
1198    /// # Example
1199    ///
1200    /// ```
1201    /// use sampled_data_duration::ConstantRateDuration;
1202    /// use sampled_data_duration::MixedRateDuration;
1203    ///
1204    /// let mrd = MixedRateDuration::from(ConstantRateDuration::new(48000 * 60 * 60 * 24 * 9, 48000));
1205    /// assert_eq!(mrd.to_string(), "216:00:00;0");
1206    /// assert_eq!(mrd.subweek_days(), 2);
1207    /// ```
1208    #[must_use]
1209    pub fn subweek_days(&self) -> u64 {
1210        self.as_days() % DAYS_PER_WEEK
1211    }
1212
1213    /// Return this `MixedRateDuration` as a `std::time::Duration`.
1214    ///
1215    /// # Example
1216    ///
1217    /// ```
1218    /// use sampled_data_duration::ConstantRateDuration;
1219    /// use sampled_data_duration::MixedRateDuration;
1220    ///
1221    /// let mrd = MixedRateDuration::from(ConstantRateDuration::new(48000 * 42, 48000));
1222    /// assert_eq!(mrd.to_string(), "00:00:42;0");
1223    /// assert_eq!(mrd.to_duration().as_secs_f64(), 42.0);
1224    /// ```
1225    #[must_use]
1226    pub fn to_duration(&self) -> Duration {
1227        self.duration
1228    }
1229
1230    /// Update this `MixedRateDuration`'s duration field by summing
1231    /// over all enteries in the internal `HashMap`.
1232    fn update_duration(&mut self) {
1233        let mut duration = Duration::new(0, 0);
1234
1235        for (rate, count) in &self.map {
1236            let crd = ConstantRateDuration::new(*count, *rate);
1237            duration += crd.to_duration();
1238        }
1239
1240        self.duration = duration;
1241    }
1242}
1243impl Default for MixedRateDuration {
1244    fn default() -> Self {
1245        MixedRateDuration::new()
1246    }
1247}
1248impl From<ConstantRateDuration> for MixedRateDuration {
1249    /// Construct a `MixedRateDuration` from a `ConstantRateDuration`.
1250    fn from(crd: ConstantRateDuration) -> Self {
1251        let mut map: HashMap<u64, u64> = HashMap::with_capacity(1);
1252        map.insert(crd.rate, crd.count);
1253
1254        MixedRateDuration {
1255            duration: crd.to_duration(),
1256            map,
1257        }
1258    }
1259}
1260impl fmt::Display for MixedRateDuration {
1261    /// Display this `MixedRateDuration` in the form
1262    /// `hh:mm:ss;samples` if there is only one sampling rate used,
1263    /// otherwise display in the form `hh:mm:ss.s`.
1264    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1265        if self.num_rates() == 1 {
1266            if let Some((rate, count)) = self.map.iter().next() {
1267                let crd = ConstantRateDuration::new(*count, *rate);
1268                return write!(
1269                    f,
1270                    "{:02}:{:02}:{:02};{}",
1271                    crd.as_hours(),
1272                    crd.subhour_mins(),
1273                    crd.submin_secs(),
1274                    crd.subsec_samples()
1275                );
1276            }
1277        }
1278
1279        if self.submin_secs() < 10 {
1280            return write!(
1281                f,
1282                "{:02}:{:02}:0{}",
1283                self.as_hours(),
1284                self.subhour_mins(),
1285                self.submin_secs() as f64 + self.subsec_secs(),
1286            );
1287        }
1288
1289        return write!(
1290            f,
1291            "{:02}:{:02}:{:2.}",
1292            self.as_hours(),
1293            self.subhour_mins(),
1294            self.submin_secs() as f64 + self.subsec_secs(),
1295        );
1296    }
1297}
1298impl Add<ConstantRateDuration> for MixedRateDuration {
1299    type Output = MixedRateDuration;
1300
1301    /// Add a `ConstantRateDuration` to this `MixedRateDuration`
1302    /// returning a new `MixedRateDuration`.
1303    fn add(self, crd: ConstantRateDuration) -> MixedRateDuration {
1304        let mut result_map = self.map;
1305
1306        if let Some(current_count) = result_map.get_mut(&crd.rate) {
1307            *current_count = current_count
1308                .checked_add(crd.count)
1309                .expect("overflow when adding a ConstantRateDuration to a MixedRateDuration");
1310        } else {
1311            result_map.insert(crd.rate, crd.count);
1312        }
1313
1314        let mut result_mrd = MixedRateDuration {
1315            duration: Duration::new(0, 0),
1316            map: result_map,
1317        };
1318        result_mrd.update_duration();
1319
1320        result_mrd
1321    }
1322}
1323impl Add<MixedRateDuration> for MixedRateDuration {
1324    type Output = MixedRateDuration;
1325
1326    /// Add a `MixedRateDuration` to this `MixedRateDuration`
1327    /// returning a new `MixedRateDuration`.
1328    fn add(self, mrd: MixedRateDuration) -> MixedRateDuration {
1329        let mut result_map = self.map;
1330
1331        for (rate, count) in &mrd.map {
1332            if let Some(current_count) = result_map.get_mut(rate) {
1333                *current_count = current_count
1334                    .checked_add(*count)
1335                    .expect("overflow when adding a ConstantRateDuration to a MixedRateDuration");
1336            } else {
1337                result_map.insert(*rate, *count);
1338            }
1339        }
1340
1341        let mut result_mrd = MixedRateDuration {
1342            duration: Duration::new(0, 0),
1343            map: result_map,
1344        };
1345        result_mrd.update_duration();
1346
1347        result_mrd
1348    }
1349}
1350impl AddAssign<ConstantRateDuration> for MixedRateDuration {
1351    /// Add a `ConstantRateDuration` to this `MixedRateDuration`.
1352    fn add_assign(&mut self, crd: ConstantRateDuration) {
1353        if let Some(current_count) = self.map.get_mut(&crd.rate) {
1354            *current_count = current_count.checked_add(crd.count).expect(
1355                "overflow when add-assigning a ConstantRateDuration to a MixedRateDuration",
1356            );
1357        } else {
1358            self.map.insert(crd.rate, crd.count);
1359        }
1360
1361        self.update_duration();
1362    }
1363}
1364impl AddAssign<MixedRateDuration> for MixedRateDuration {
1365    /// Add a `MixedRateDuration` to this `MixedRateDuration`.
1366    fn add_assign(&mut self, rhs: MixedRateDuration) {
1367        for (rhs_rate, rhs_count) in &rhs.map {
1368            if let Some(lhs_count) = self.map.get_mut(rhs_rate) {
1369                *lhs_count = lhs_count
1370                    .checked_add(*rhs_count)
1371                    .expect("overflow when add-assigning MixedRateDurations");
1372            } else {
1373                self.map.insert(*rhs_rate, *rhs_count);
1374            }
1375        }
1376
1377        self.update_duration();
1378    }
1379}
1380impl Div<u64> for MixedRateDuration {
1381    type Output = Self;
1382
1383    /// Divide a `MixedRateDuration` by a `u64` returning a new
1384    /// `MixedRateDuration`.
1385    fn div(self, rhs: u64) -> Self {
1386        let mut result_map: HashMap<u64, u64> = HashMap::with_capacity(self.map.len());
1387
1388        for (rate, count) in &self.map {
1389            result_map.insert(
1390                *rate,
1391                count
1392                    .checked_div(rhs)
1393                    .expect("divide by zero when dividing a MixedRateDuration"),
1394            );
1395        }
1396
1397        let mut result_mrd = MixedRateDuration {
1398            duration: Duration::new(0, 0),
1399            map: result_map,
1400        };
1401        result_mrd.update_duration();
1402
1403        result_mrd
1404    }
1405}
1406impl DivAssign<u64> for MixedRateDuration {
1407    /// Divide-assign a `MixedRateDuration` by a `u64`.
1408    fn div_assign(&mut self, rhs: u64) {
1409        for count in self.map.values_mut() {
1410            *count = count
1411                .checked_div(rhs)
1412                .expect("divide by zero when divide-assigning a MixedRateDuration");
1413        }
1414
1415        self.update_duration();
1416    }
1417}
1418impl Mul<u64> for MixedRateDuration {
1419    type Output = Self;
1420
1421    /// Multiply a `MixedRateDuration` by a `u64` returning a new
1422    /// `MixedRateDuration`.
1423    fn mul(self, rhs: u64) -> Self {
1424        let mut result_map: HashMap<u64, u64> = HashMap::with_capacity(self.map.len());
1425
1426        for (rate, count) in &self.map {
1427            result_map.insert(
1428                *rate,
1429                count
1430                    .checked_mul(rhs)
1431                    .expect("overflow when multiplying a MixedRateDuration"),
1432            );
1433        }
1434
1435        let mut result_mrd = MixedRateDuration {
1436            duration: Duration::new(0, 0),
1437            map: result_map,
1438        };
1439        result_mrd.update_duration();
1440
1441        result_mrd
1442    }
1443}
1444impl MulAssign<u64> for MixedRateDuration {
1445    /// Multiply-assign a `MixedRateDuration` by a `u64`.
1446    fn mul_assign(&mut self, rhs: u64) {
1447        for count in self.map.values_mut() {
1448            *count = count
1449                .checked_mul(rhs)
1450                .expect("overflow when multiply-assigning a MixedRateDuration");
1451        }
1452
1453        self.update_duration();
1454    }
1455}
1456impl Sub<ConstantRateDuration> for MixedRateDuration {
1457    type Output = MixedRateDuration;
1458
1459    /// Perform a saturating subtraction of a `ConstantRateDuration`
1460    /// from this `MixedRateDuration` returning a new
1461    /// `MixedRateDuration`.
1462    fn sub(self, crd: ConstantRateDuration) -> MixedRateDuration {
1463        let mut result_map = self.map;
1464
1465        if let Some(current_count) = result_map.get_mut(&crd.rate) {
1466            *current_count = current_count.saturating_sub(crd.count);
1467        }
1468
1469        let mut result_mrd = MixedRateDuration {
1470            duration: Duration::new(0, 0),
1471            map: result_map,
1472        };
1473        result_mrd.update_duration();
1474
1475        result_mrd
1476    }
1477}
1478impl Sub<MixedRateDuration> for MixedRateDuration {
1479    type Output = MixedRateDuration;
1480
1481    /// Perform a saturating subtraction of a `MixedRateDuration` from
1482    /// this `MixedRateDuration` returning a new `MixedRateDuration`.
1483    fn sub(self, mrd: MixedRateDuration) -> MixedRateDuration {
1484        let mut result_map = self.map;
1485
1486        for (rate, count) in &mrd.map {
1487            if let Some(current_count) = result_map.get_mut(rate) {
1488                *current_count = current_count.saturating_sub(*count);
1489            }
1490        }
1491
1492        let mut result_mrd = MixedRateDuration {
1493            duration: Duration::new(0, 0),
1494            map: result_map,
1495        };
1496        result_mrd.update_duration();
1497
1498        result_mrd
1499    }
1500}
1501impl SubAssign<ConstantRateDuration> for MixedRateDuration {
1502    /// Perform a saturating subtraction of a `ConstantRateDuration`
1503    /// from this `MixedRateDuration`.
1504    fn sub_assign(&mut self, crd: ConstantRateDuration) {
1505        if let Some(current_count) = self.map.get_mut(&crd.rate) {
1506            *current_count = current_count.saturating_sub(crd.count);
1507            self.update_duration();
1508        }
1509    }
1510}
1511impl SubAssign<MixedRateDuration> for MixedRateDuration {
1512    /// Perform a saturating subtraction of a `MixedRateDuration` from
1513    /// this `MixedRateDuration`.
1514    fn sub_assign(&mut self, rhs: MixedRateDuration) {
1515        for (rhs_rate, rhs_count) in &rhs.map {
1516            if let Some(lhs_count) = self.map.get_mut(rhs_rate) {
1517                *lhs_count = lhs_count.saturating_sub(*rhs_count);
1518            }
1519        }
1520
1521        self.update_duration();
1522    }
1523}
1524
1525#[cfg(test)]
1526mod mixed_rate_duration {
1527    use super::*;
1528
1529    #[test]
1530    fn new() {
1531        let count: u64 = 12345;
1532        let rate: u64 = 48000;
1533        let mrd = MixedRateDuration::from(ConstantRateDuration::new(count, rate));
1534
1535        assert_eq!(mrd.map.len(), 1);
1536        assert!(mrd.map.contains_key(&rate));
1537        assert_eq!(mrd.map.get(&rate), Some(&count));
1538    }
1539
1540    #[test]
1541    fn as_and_sub_time_unit_methods() {
1542        let subsec_samples: u64 = 24_000;
1543        let submin_secs: u64 = 32;
1544        let subhour_mins: u64 = 23;
1545        let subday_hours: u64 = 19;
1546        let subweek_days: u64 = 4;
1547        let weeks: u64 = 1;
1548        let rate: u64 = 48_000;
1549
1550        let as_days = weeks * DAYS_PER_WEEK + subweek_days;
1551        let as_hours = as_days * HOURS_PER_DAY + subday_hours;
1552        let as_mins = as_hours * MINS_PER_HOUR + subhour_mins;
1553        let as_secs = as_mins * SECS_PER_MIN + submin_secs;
1554
1555        let total_secs: u64 = weeks * SECS_PER_WEEK
1556            + subweek_days * SECS_PER_DAY
1557            + subday_hours * SECS_PER_HOUR
1558            + subhour_mins * SECS_PER_MIN
1559            + submin_secs;
1560        let count: u64 = total_secs * rate + subsec_samples;
1561        let mrd = MixedRateDuration::from(ConstantRateDuration::new(count, rate));
1562
1563        assert_eq!(mrd.as_secs(), as_secs);
1564        assert_eq!(mrd.as_mins(), as_mins);
1565        assert_eq!(mrd.as_hours(), as_hours);
1566        assert_eq!(mrd.as_days(), as_days);
1567        assert_eq!(mrd.as_weeks(), weeks);
1568        assert_eq!(mrd.subsec_nanos(), 500_000_000);
1569        assert_eq!(mrd.subsec_secs(), 0.5);
1570        assert_eq!(mrd.submin_secs(), submin_secs);
1571        assert_eq!(mrd.subhour_mins(), subhour_mins);
1572        assert_eq!(mrd.subday_hours(), subday_hours);
1573        assert_eq!(mrd.subweek_days(), subweek_days);
1574
1575        // Check multiple rates
1576        let mut mrd = MixedRateDuration::from(ConstantRateDuration::new(48000, 48000));
1577        assert_eq!(mrd.as_secs(), 1);
1578        assert_eq!(mrd.subsec_nanos(), 0);
1579
1580        // Manually add 1.5 seconds of 96kHz samples
1581        mrd += ConstantRateDuration::new(96000 + 48000, 96000);
1582        assert_eq!(mrd.map.len(), 2);
1583        assert_eq!(mrd.as_secs(), 2);
1584        assert_eq!(mrd.subsec_nanos(), 500_000_000);
1585    }
1586
1587    #[test]
1588    fn to_duration() {
1589        let mrd = MixedRateDuration::from(ConstantRateDuration::new(48000, 48000));
1590        assert_eq!(mrd.to_duration(), Duration::new(1, 0));
1591
1592        //TODO more tests
1593    }
1594
1595    #[test]
1596    fn display() {
1597        let mut mrd = MixedRateDuration::from(ConstantRateDuration::new(48000 + 24000, 48000));
1598        assert_eq!(mrd.to_string(), "00:00:01;24000");
1599
1600        // Add 1 second of 96kHz samples
1601        mrd += ConstantRateDuration::new(96000, 96000);
1602        assert_eq!(mrd.map.len(), 2);
1603        assert_eq!(mrd.as_secs(), 2);
1604        assert_eq!(mrd.subsec_nanos(), 500_000_000);
1605        assert_eq!(mrd.to_string(), "00:00:02.5");
1606
1607        // Manually add 10 seconds of 44.1kHz samples
1608        mrd += ConstantRateDuration::new(441_000, 44100);
1609        assert_eq!(mrd.map.len(), 3);
1610        assert_eq!(mrd.as_secs(), 12);
1611        assert_eq!(mrd.subsec_nanos(), 500_000_000);
1612        assert_eq!(mrd.to_string(), "00:00:12.5");
1613    }
1614
1615    #[test]
1616    fn add() {
1617        // Add a ConstantRateDuration
1618        let a = MixedRateDuration::from(ConstantRateDuration::new(48000, 48000));
1619        let b = ConstantRateDuration::new(48000, 48000);
1620        let c = a + b;
1621        assert_eq!(c.to_string(), "00:00:02;0");
1622
1623        // Add a MixedRateDuraiton
1624        let a = MixedRateDuration::from(ConstantRateDuration::new(48000, 48000));
1625        let b = MixedRateDuration::from(ConstantRateDuration::new(48000, 48000));
1626        let c = a + b;
1627        assert_eq!(c.to_string(), "00:00:02;0");
1628
1629        // Add a MixedRateDuraiton with different rates
1630        let a = MixedRateDuration::from(ConstantRateDuration::new(48000, 48000));
1631        let b = MixedRateDuration::from(ConstantRateDuration::new(96000 + 48000, 96000));
1632        let c = a + b;
1633        assert_eq!(c.to_string(), "00:00:02.5");
1634    }
1635
1636    #[test]
1637    fn add_assign() {
1638        // Add-assign a ConstantRateDuration
1639        let mut a = MixedRateDuration::from(ConstantRateDuration::new(48000, 48000));
1640        let b = ConstantRateDuration::new(48000, 48000);
1641        a += b;
1642        assert_eq!(a.to_string(), "00:00:02;0");
1643
1644        // Add-assign a MixedRateDuraiton
1645        let c = MixedRateDuration::from(ConstantRateDuration::new(96000 + 48000, 96000));
1646        a += c;
1647        assert_eq!(a.to_string(), "00:00:03.5");
1648    }
1649
1650    #[test]
1651    fn div() {
1652        let a = MixedRateDuration::from(ConstantRateDuration::new(96002, 48000));
1653        assert_eq!(
1654            a / 2,
1655            MixedRateDuration::from(ConstantRateDuration::new(48001, 48000))
1656        );
1657    }
1658
1659    #[test]
1660    #[should_panic(expected = "divide by zero when dividing a MixedRateDuration")]
1661    fn div_by_zero() {
1662        let a = MixedRateDuration::from(ConstantRateDuration::new(48000, 48000));
1663        let b = a / 0;
1664
1665        unreachable!(
1666            "The above line should have caused a divide by zero error and b={} should be NaN.",
1667            b
1668        );
1669    }
1670
1671    #[test]
1672    fn div_assign() {
1673        let mut a = MixedRateDuration::from(ConstantRateDuration::new(96002, 48000));
1674        a /= 2;
1675        assert_eq!(
1676            a,
1677            MixedRateDuration::from(ConstantRateDuration::new(48001, 48000))
1678        );
1679    }
1680
1681    #[test]
1682    #[should_panic(expected = "divide by zero when divide-assigning a MixedRateDuration")]
1683    fn div_assign_by_zero() {
1684        let mut a = MixedRateDuration::from(ConstantRateDuration::new(48000, 48000));
1685        a /= 0;
1686    }
1687
1688    #[test]
1689    fn mul() {
1690        // Multiply a MixedRateDuration
1691        let a = MixedRateDuration::from(ConstantRateDuration::new(1, 48000));
1692        let b = a * 2;
1693        assert_eq!(
1694            b,
1695            MixedRateDuration::from(ConstantRateDuration::new(2, 48000))
1696        );
1697
1698        // Multiply a MixedRateDuraiton with multiple enteries.
1699        let a = MixedRateDuration::from(ConstantRateDuration::new(48000, 48000));
1700        let b = MixedRateDuration::from(ConstantRateDuration::new(48000, 96000));
1701        let c = (a + b) * 2;
1702        assert_eq!(c.to_string(), "00:00:03");
1703    }
1704
1705    #[test]
1706    #[should_panic(expected = "overflow when multiplying a MixedRateDuration")]
1707    fn mul_overflow() {
1708        // Multiply MixedRateDurations which overflows
1709        let a = MixedRateDuration::from(ConstantRateDuration::new(u64::MAX, 48000));
1710        let b = a * 2;
1711
1712        unreachable!(
1713            "The above line should have caused an overflow and b={} should have overflown.",
1714            b
1715        );
1716    }
1717
1718    #[test]
1719    fn mul_assign() {
1720        // Multiply a MixedRateDuration
1721        let mut a = MixedRateDuration::from(ConstantRateDuration::new(1, 48000));
1722        a *= 2;
1723        assert_eq!(
1724            a,
1725            MixedRateDuration::from(ConstantRateDuration::new(2, 48000))
1726        );
1727
1728        // Multiply a MixedRateDuraiton with multiple enteries.
1729        let a = MixedRateDuration::from(ConstantRateDuration::new(48000, 48000));
1730        let b = MixedRateDuration::from(ConstantRateDuration::new(48000, 96000));
1731        let mut c = a + b;
1732        c *= 2;
1733        assert_eq!(c.to_string(), "00:00:03");
1734    }
1735
1736    #[test]
1737    #[should_panic(expected = "overflow when multiply-assigning a MixedRateDuration")]
1738    fn mul_assign_overflow() {
1739        // Multiply-assign MixedRateDurations which overflows
1740        let mut a = MixedRateDuration::from(ConstantRateDuration::new(u64::MAX, 48000));
1741        a *= 2;
1742    }
1743
1744    #[test]
1745    fn sub() {
1746        // Subtract a ConstantRateDuration
1747        let a = MixedRateDuration::from(ConstantRateDuration::new(100, 48000));
1748        let b = ConstantRateDuration::new(25, 48000);
1749        let c = a - b;
1750        assert_eq!(
1751            c,
1752            MixedRateDuration::from(ConstantRateDuration::new(75, 48000))
1753        );
1754
1755        // Subtract a MixedRateDuraiton
1756        let a = MixedRateDuration::from(ConstantRateDuration::new(100, 48000));
1757        let b = MixedRateDuration::from(ConstantRateDuration::new(25, 48000));
1758        let c = a - b;
1759        assert_eq!(
1760            c,
1761            MixedRateDuration::from(ConstantRateDuration::new(75, 48000))
1762        );
1763
1764        // Subtract a MixedRateDuraiton with different rates
1765        let mut a = MixedRateDuration::from(ConstantRateDuration::new(100, 48000));
1766        a += ConstantRateDuration::new(42, 96000);
1767
1768        let mut b = MixedRateDuration::from(ConstantRateDuration::new(25, 48000));
1769        b += ConstantRateDuration::new(123, 44100);
1770
1771        let c = a - b;
1772        let mut expected_c = MixedRateDuration::from(ConstantRateDuration::new(75, 48000));
1773        expected_c += ConstantRateDuration::new(42, 96000);
1774        assert_eq!(c, expected_c);
1775    }
1776
1777    #[test]
1778    fn sub_assign() {
1779        // Sub-assign a ConstantRateDuration
1780        let mut a = MixedRateDuration::from(ConstantRateDuration::new(10, 48000));
1781        let b = ConstantRateDuration::new(1, 48000);
1782        a -= b;
1783        assert_eq!(
1784            a,
1785            MixedRateDuration::from(ConstantRateDuration::new(9, 48000))
1786        );
1787
1788        // Sub-assign a MixedRateDuraiton
1789        let mut c = MixedRateDuration::from(ConstantRateDuration::new(1, 96000));
1790        c += ConstantRateDuration::new(2, 48000);
1791
1792        a -= c;
1793        assert_eq!(
1794            a,
1795            MixedRateDuration::from(ConstantRateDuration::new(7, 48000))
1796        );
1797    }
1798}
1799
1800#[derive(Debug, PartialEq)]
1801pub enum Error {
1802    IncommensurateRates,
1803}
1804impl fmt::Display for Error {
1805    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1806        write!(
1807            f,
1808            "Incommensurate sampling rates, the sampling rates must be equal."
1809        )
1810    }
1811}
1812impl std::error::Error for Error {}