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 {}