1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
// Symphonia
// Copyright (c) 2019-2022 The Project Symphonia Developers.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! The `units` module provides definitions for common units.

use std::fmt;

/// A `TimeStamp` represents an instantenous instant in time since the start of a stream. One
/// `TimeStamp` "tick" is equivalent to the stream's `TimeBase` in seconds.
pub type TimeStamp = u64;

/// A `Duration` indicates a positive span of time.
pub type Duration = u64;

/// `Time` represents a duration of time in seconds, or the number of seconds since an arbitrary
/// epoch. `Time` is stored as an integer number of seconds plus any remaining fraction of a second
/// as a floating point value.
#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd)]
pub struct Time {
    pub seconds: u64,
    pub frac: f64,
}

impl Time {
    const SECONDS_PER_MINUTE: u64 = 60;
    const SECONDS_PER_HOUR: u64 = 60 * 60;
    const NANOSECONDS_PER_SECOND: u32 = 1_000_000_000;
    const NANOSECONDS_PER_SECOND_INV: f64 = 1.0 / 1_000_000_000.0;

    pub fn new(seconds: u64, frac: f64) -> Self {
        Time { seconds, frac }
    }

    pub fn from_ss(s: u8, ns: u32) -> Option<Time> {
        if s > 59 || ns >= Time::NANOSECONDS_PER_SECOND {
            return None;
        }

        let seconds = u64::from(s);
        let frac = Time::NANOSECONDS_PER_SECOND_INV * f64::from(ns);

        Some(Time { seconds, frac })
    }

    pub fn from_mmss(m: u8, s: u8, ns: u32) -> Option<Time> {
        if m > 59 || s > 59 || ns >= Time::NANOSECONDS_PER_SECOND {
            return None;
        }

        let seconds = (Time::SECONDS_PER_MINUTE * u64::from(m)) + u64::from(s);
        let frac = Time::NANOSECONDS_PER_SECOND_INV * f64::from(ns);

        Some(Time { seconds, frac })
    }

    pub fn from_hhmmss(h: u32, m: u8, s: u8, ns: u32) -> Option<Time> {
        if m > 59 || s > 59 || ns >= Time::NANOSECONDS_PER_SECOND {
            return None;
        }

        let seconds = (Time::SECONDS_PER_HOUR * u64::from(h))
            + (Time::SECONDS_PER_MINUTE * u64::from(m))
            + u64::from(s);

        let frac = Time::NANOSECONDS_PER_SECOND_INV * f64::from(ns);

        Some(Time { seconds, frac })
    }
}

impl From<u8> for Time {
    fn from(seconds: u8) -> Self {
        Time::new(u64::from(seconds), 0.0)
    }
}

impl From<u16> for Time {
    fn from(seconds: u16) -> Self {
        Time::new(u64::from(seconds), 0.0)
    }
}

impl From<u32> for Time {
    fn from(seconds: u32) -> Self {
        Time::new(u64::from(seconds), 0.0)
    }
}

impl From<u64> for Time {
    fn from(seconds: u64) -> Self {
        Time::new(seconds, 0.0)
    }
}

impl From<f32> for Time {
    fn from(seconds: f32) -> Self {
        if seconds >= 0.0 {
            Time::new(seconds.trunc() as u64, f64::from(seconds.fract()))
        }
        else {
            Time::new(0, 0.0)
        }
    }
}

impl From<f64> for Time {
    fn from(seconds: f64) -> Self {
        if seconds >= 0.0 {
            Time::new(seconds.trunc() as u64, seconds.fract())
        }
        else {
            Time::new(0, 0.0)
        }
    }
}

impl From<std::time::Duration> for Time {
    fn from(duration: std::time::Duration) -> Self {
        Time::new(duration.as_secs(), f64::from(duration.subsec_nanos()) / 1_000_000_000.0)
    }
}

impl From<Time> for std::time::Duration {
    fn from(time: Time) -> Self {
        std::time::Duration::new(time.seconds, (1_000_000_000.0 * time.frac) as u32)
    }
}

/// A `TimeBase` is the conversion factor between time, expressed in seconds, and a `TimeStamp` or
/// `Duration`.
///
/// In other words, a `TimeBase` is the length in seconds of one tick of a `TimeStamp` or
/// `Duration`.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct TimeBase {
    /// The numerator.
    pub numer: u32,
    /// The denominator.
    pub denom: u32,
}

impl TimeBase {
    /// Creates a new `TimeBase`. Panics if either the numerator or denominator is 0.
    pub fn new(numer: u32, denom: u32) -> Self {
        if numer == 0 || denom == 0 {
            panic!("TimeBase cannot have 0 numerator or denominator");
        }

        TimeBase { numer, denom }
    }

    /// Accurately calculates a `Time` using the `TimeBase` and the provided `TimeStamp`. On
    /// overflow, the seconds field of `Time` wraps.
    pub fn calc_time(&self, ts: TimeStamp) -> Time {
        assert!(self.numer > 0 && self.denom > 0, "TimeBase numerator or denominator are 0.");

        // The dividend requires up-to 96-bits (32-bit timebase numerator * 64-bit timestamp).
        let dividend = u128::from(ts) * u128::from(self.numer);

        // For an accurate floating point division, both the dividend and divisor must have an
        // accurate floating point representation. A 64-bit floating point value has a mantissa of
        // 52 bits and can therefore accurately represent a 52-bit integer. The divisor (the
        // denominator of the timebase) is limited to 32-bits. Therefore, if the dividend
        // requires less than 52-bits, a straight-forward floating point division can be used to
        // calculate the time.
        if dividend < (1 << 52) {
            let seconds = (dividend as f64) / f64::from(self.denom);

            Time::new(seconds.trunc() as u64, seconds.fract())
        }
        else {
            // If the dividend requires more than 52 bits, calculate the integer portion using
            // integer arithmetic, then calculate the fractional part separately.
            let quotient = dividend / u128::from(self.denom);

            // The remainder is the fractional portion before being divided by the divisor (the
            // denominator). The remainder will never equal or exceed the divisor (or else the
            // fractional part would be >= 1.0), so the remainder must fit within a u32.
            let rem = (dividend - (quotient * u128::from(self.denom))) as u32;

            // Calculate the fractional portion. Since both the remainder and denominator are 32-bit
            // integers now, 64-bit floating point division will provide enough accuracy.
            let frac = f64::from(rem) / f64::from(self.denom);

            Time::new(quotient as u64, frac)
        }
    }

    /// Accurately calculates a `TimeStamp` from the given `Time` using the `TimeBase` as the
    /// conversion factor. On overflow, the `TimeStamp` wraps.
    pub fn calc_timestamp(&self, time: Time) -> TimeStamp {
        assert!(self.numer > 0 && self.denom > 0, "TimeBase numerator or denominator are 0.");
        assert!(time.frac >= 0.0 && time.frac < 1.0, "Invalid range for Time fractional part.");

        // The dividing factor.
        let k = 1.0 / f64::from(self.numer);

        // Multiplying seconds by the denominator requires up-to 96-bits (32-bit timebase
        // denominator * 64-bit timestamp).
        let product = u128::from(time.seconds) * u128::from(self.denom);

        // Like calc_time, a 64-bit floating-point value only has 52-bits of integer precision.
        // If the product requires more than 52-bits, split the product into upper and lower parts
        // and multiply by k separately, before adding back together.
        let a = if product > (1 << 52) {
            // Split the 96-bit product into 48-bit halves.
            let u = ((product & !0xffff_ffff_ffff) >> 48) as u64;
            let l = ((product & 0xffff_ffff_ffff) >> 0) as u64;

            let uk = (u as f64) * k;
            let ul = (l as f64) * k;

            // Add the upper and lower halves.
            ((uk as u64) << 48).wrapping_add(ul as u64)
        }
        else {
            ((product as f64) * k) as u64
        };

        // The fractional portion can be calculate directly using floating point arithemtic.
        let b = (k * f64::from(self.denom) * time.frac) as u64;

        a.wrapping_add(b)
    }
}

impl From<TimeBase> for f64 {
    fn from(timebase: TimeBase) -> Self {
        f64::from(timebase.numer) / f64::from(timebase.denom)
    }
}

impl fmt::Display for TimeBase {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}/{}", self.numer, self.denom)
    }
}

#[cfg(test)]
mod tests {
    use super::{Time, TimeBase};
    use std::time::Duration;

    #[test]
    fn verify_timebase() {
        // Verify accuracy of timestamp -> time
        let tb1 = TimeBase::new(1, 320);

        assert_eq!(tb1.calc_time(0), Time::new(0, 0.0));
        assert_eq!(tb1.calc_time(12_345), Time::new(38, 0.578125));
        assert_eq!(tb1.calc_time(0x0f_ffff_ffff_ffff), Time::new(14_073_748_835_532, 0.796875));
        assert_eq!(tb1.calc_time(0x10_0000_0000_0001), Time::new(14_073_748_835_532, 0.803125));
        assert_eq!(tb1.calc_time(u64::MAX), Time::new(57_646_075_230_342_348, 0.796875));

        // Verify overflow wraps seconds
        let tb2 = TimeBase::new(320, 1);
        assert_eq!(tb2.calc_time(u64::MAX), Time::new(18_446_744_073_709_551_296, 0.0));

        // Verify accuracy of time -> timestamp
        assert_eq!(tb1.calc_timestamp(Time::new(0, 0.0)), 0);
        assert_eq!(tb1.calc_timestamp(Time::new(38, 0.578125)), 12_345);
        assert_eq!(
            tb1.calc_timestamp(Time::new(14_073_748_835_532, 0.796875)),
            0x0f_ffff_ffff_ffff
        );
        assert_eq!(
            tb1.calc_timestamp(Time::new(14_073_748_835_532, 0.803125)),
            0x10_0000_0000_0001
        );
        assert_eq!(tb1.calc_timestamp(Time::new(57_646_075_230_342_348, 0.796875)), u64::MAX);
    }

    #[test]
    fn verify_duration_to_time() {
        // Verify accuracy of Duration -> Time
        let dur1 = Duration::from_secs_f64(38.578125);
        let time1 = Time::from(dur1);

        assert_eq!(time1.seconds, 38);
        assert_eq!(time1.frac, 0.578125);
    }

    #[test]
    fn verify_time_to_duration() {
        // Verify accuracy of Time -> Duration
        let time1 = Time::new(38, 0.578125);
        let dur1 = Duration::from(time1);

        let seconds = dur1.as_secs_f64();

        assert_eq!(seconds.trunc(), 38.0);
        assert_eq!(seconds.fract(), 0.578125);
    }
}