Skip to main content

speech_prep/
time.rs

1//! Stream-relative time types for audio processing.
2
3use std::time::{Duration, Instant};
4
5/// Duration in the audio pipeline. Alias for `std::time::Duration`.
6pub type AudioDuration = Duration;
7
8/// Monotonic instant for measuring elapsed time.
9pub type AudioInstant = Instant;
10
11/// Timestamp stored as nanoseconds from a stream-local origin.
12///
13/// `AudioTimestamp::EPOCH` and `AudioTimestamp::ZERO` are both the zero point
14/// for a stream. Use `from_secs` and `as_secs` when an API prefers `f64`
15/// seconds, while keeping integer nanoseconds as the canonical representation.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
17pub struct AudioTimestamp(u64);
18
19impl AudioTimestamp {
20    pub const ZERO: Self = Self(0);
21    pub const EPOCH: Self = Self(0);
22
23    pub fn from_secs(secs: f64) -> Self {
24        if !secs.is_finite() || secs <= 0.0 {
25            return Self::ZERO;
26        }
27
28        let nanos = (secs * 1_000_000_000.0).min(u64::MAX as f64);
29        Self(nanos as u64)
30    }
31
32    pub fn from_nanos(nanos: u64) -> Self {
33        Self(nanos)
34    }
35
36    pub fn from_samples(samples: u64, sample_rate: u32) -> Self {
37        if sample_rate == 0 {
38            return Self::ZERO;
39        }
40
41        Self(samples.saturating_mul(1_000_000_000) / sample_rate as u64)
42    }
43
44    pub fn as_secs(&self) -> f64 {
45        self.0 as f64 / 1_000_000_000.0
46    }
47
48    pub fn as_millis(&self) -> f64 {
49        self.0 as f64 / 1_000_000.0
50    }
51
52    pub fn nanos(&self) -> u64 {
53        self.0
54    }
55
56    pub fn add_duration(&self, d: Duration) -> Self {
57        let nanos = d.as_nanos().min(u128::from(u64::MAX)) as u64;
58        Self(self.0.saturating_add(nanos))
59    }
60
61    pub fn duration_since(&self, earlier: Self) -> Option<Duration> {
62        self.0.checked_sub(earlier.0).map(Duration::from_nanos)
63    }
64}
65
66impl std::ops::Add<Duration> for AudioTimestamp {
67    type Output = Self;
68    fn add(self, rhs: Duration) -> Self {
69        self.add_duration(rhs)
70    }
71}
72
73impl std::ops::Sub for AudioTimestamp {
74    type Output = Duration;
75    fn sub(self, rhs: Self) -> Duration {
76        Duration::from_nanos(self.0.saturating_sub(rhs.0))
77    }
78}
79
80impl std::fmt::Display for AudioTimestamp {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        write!(f, "{:.3}s", self.as_secs())
83    }
84}
85
86/// Range validation helper.
87pub(crate) fn validate_in_range<T: PartialOrd + std::fmt::Display>(
88    value: T,
89    min: T,
90    max: T,
91    name: &str,
92) -> crate::error::Result<()> {
93    if value < min || value > max {
94        return Err(crate::error::Error::configuration(format!(
95            "{name} = {value} is outside valid range [{min}, {max}]"
96        )));
97    }
98    Ok(())
99}