1use crate::rational::Rational;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9pub struct TimeBase(pub Rational);
10
11impl TimeBase {
12 pub const fn new(num: i64, den: i64) -> Self {
13 Self(Rational::new(num, den))
14 }
15
16 pub fn as_rational(&self) -> Rational {
17 self.0
18 }
19
20 pub fn seconds_of(&self, ticks: i64) -> f64 {
22 ticks as f64 * self.0.as_f64()
23 }
24
25 pub fn rescale(&self, ts: i64, target: TimeBase) -> i64 {
27 rescale(ts, self.0, target.0)
28 }
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
33pub struct Timestamp {
34 pub value: i64,
35 pub base: TimeBase,
36}
37
38impl Timestamp {
39 pub const fn new(value: i64, base: TimeBase) -> Self {
40 Self { value, base }
41 }
42
43 pub fn seconds(&self) -> f64 {
44 self.base.seconds_of(self.value)
45 }
46
47 pub fn rescale(&self, target: TimeBase) -> Self {
48 Self {
49 value: self.base.rescale(self.value, target),
50 base: target,
51 }
52 }
53}
54
55pub fn rescale(value: i64, from: Rational, to: Rational) -> i64 {
59 let num = from.num as i128 * to.den as i128;
62 let den = from.den as i128 * to.num as i128;
63 if den == 0 {
64 return 0;
65 }
66 let prod = value as i128 * num;
67 let half = den.abs() / 2;
68 let rounded = if (prod >= 0) == (den > 0) {
69 (prod + half) / den
70 } else {
71 (prod - half) / den
72 };
73 rounded as i64
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn rescale_samples_to_pts() {
82 assert_eq!(
84 rescale(48000, Rational::new(1, 48000), Rational::new(1, 1000)),
85 1000
86 );
87 }
88
89 #[test]
90 fn timestamp_seconds() {
91 let ts = Timestamp::new(48000, TimeBase::new(1, 48000));
92 assert!((ts.seconds() - 1.0).abs() < 1e-9);
93 }
94}