pocketstation 1.0.0

Source-aware desktop audio Session SDK
//! Source, observation, and Session clock coordinates for one signal.

use crate::frame::FrameLineage;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SignalTiming {
    pub(crate) source_timestamp_ns: Option<u64>,
    pub(crate) observed_timestamp_ns: u64,
    pub(crate) session_timestamp_ns: Option<u64>,
    pub(crate) duration_ns: Option<u64>,
}

impl SignalTiming {
    pub fn try_new(
        source_timestamp_ns: Option<u64>,
        observed_timestamp_ns: u64,
        session_timestamp_ns: Option<u64>,
        duration_ns: Option<u64>,
    ) -> Result<Self, SignalTimingError> {
        if duration_ns == Some(0) {
            return Err(SignalTimingError::ZeroDuration);
        }
        if duration_ns.is_some_and(|duration| {
            source_timestamp_ns.is_some_and(|timestamp| timestamp.checked_add(duration).is_none())
                || session_timestamp_ns
                    .is_some_and(|timestamp| timestamp.checked_add(duration).is_none())
        }) {
            return Err(SignalTimingError::TimestampOverflow);
        }
        Ok(Self {
            source_timestamp_ns,
            observed_timestamp_ns,
            session_timestamp_ns,
            duration_ns,
        })
    }

    pub const fn observed(observed_timestamp_ns: u64) -> Self {
        Self {
            source_timestamp_ns: None,
            observed_timestamp_ns,
            session_timestamp_ns: None,
            duration_ns: None,
        }
    }

    pub fn with_duration_ns(self, duration_ns: Option<u64>) -> Result<Self, SignalTimingError> {
        Self::try_new(
            self.source_timestamp_ns,
            self.observed_timestamp_ns,
            self.session_timestamp_ns,
            duration_ns,
        )
    }

    pub const fn from_frame(lineage: FrameLineage, observed_timestamp_ns: u64) -> Self {
        Self {
            source_timestamp_ns: Some(lineage.timestamp_start_ns),
            observed_timestamp_ns,
            session_timestamp_ns: Some(lineage.timestamp_start_ns),
            duration_ns: Some(lineage.duration_ns),
        }
    }

    pub fn timestamp_end_ns(self) -> Option<u64> {
        self.source_timestamp_ns
            .zip(self.duration_ns)
            .map(|(start, duration)| start.saturating_add(duration))
    }

    pub const fn source_timestamp_ns(self) -> Option<u64> {
        self.source_timestamp_ns
    }

    pub const fn observed_timestamp_ns(self) -> u64 {
        self.observed_timestamp_ns
    }

    pub const fn session_timestamp_ns(self) -> Option<u64> {
        self.session_timestamp_ns
    }

    pub const fn duration_ns(self) -> Option<u64> {
        self.duration_ns
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum SignalTimingError {
    #[error("signal timing duration must be non-zero when present")]
    ZeroDuration,
    #[error("signal timing range overflows u64 nanoseconds")]
    TimestampOverflow,
}