use std::cmp::Ordering;
use std::fmt::Display;
use std::num::NonZeroU32;
use std::ops::{Add, AddAssign, Sub, SubAssign};
use std::str::FromStr;
use std::time::Duration;
use std::time::Instant;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize)]
pub struct Frequency(NonZeroU32);
impl Frequency {
pub const MICROS: Self = Self::make(1_000_000);
pub const FIXED_POINT_6_18: Frequency = Self::make(2u32.pow(18));
pub const NINETY_KHZ: Frequency = Self::make(90_000);
pub const FORTY_EIGHT_KHZ: Frequency = Self::make(48_000);
pub const EIGHT_KHZ: Frequency = Self::make(8_000);
pub const MILLIS: Frequency = Self::make(1_000);
pub const HUNDREDTHS: Frequency = Self::make(100);
pub const SECONDS: Frequency = Self::make(1);
const fn make(v: u32) -> Frequency {
match NonZeroU32::new(v) {
Some(v) => Self(v),
None => panic!("assured non-zero value is zero"),
}
}
pub const fn from_nonzero(v: NonZeroU32) -> Self {
Self(v)
}
pub fn new(v: u32) -> Option<Self> {
NonZeroU32::new(v).map(Self)
}
pub const fn get(&self) -> u32 {
self.0.get()
}
pub const fn nonzero(&self) -> NonZeroU32 {
self.0
}
}
impl PartialEq for Frequency {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for Frequency {}
impl From<Frequency> for NonZeroU32 {
fn from(value: Frequency) -> Self {
value.0
}
}
impl From<NonZeroU32> for Frequency {
fn from(value: NonZeroU32) -> Self {
Self(value)
}
}
impl FromStr for Frequency {
type Err = <NonZeroU32 as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
NonZeroU32::from_str(s).map(Self)
}
}
impl Display for Frequency {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl<'de> Deserialize<'de> for Frequency {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
NonZeroU32::deserialize(deserializer).map(Self)
}
}
#[derive(Debug, Clone, Copy)]
pub struct MediaTime(u64, Frequency);
impl MediaTime {
pub const ZERO: MediaTime = MediaTime::from_secs(0);
pub const fn new(numer: u64, denom: Frequency) -> Self {
Self(numer, denom)
}
#[inline(always)]
pub const fn numer(&self) -> u64 {
self.0
}
#[inline(always)]
pub const fn denom(&self) -> u32 {
self.1.get()
}
#[inline(always)]
pub const fn frequency(&self) -> Frequency {
self.1
}
#[inline(always)]
pub const fn from_micros(v: u64) -> MediaTime {
MediaTime(v, Frequency::MICROS)
}
#[inline(always)]
pub const fn from_fixed_point_6_18(v: u64) -> MediaTime {
MediaTime(v, Frequency::FIXED_POINT_6_18)
}
#[inline(always)]
pub const fn from_90khz(v: u64) -> MediaTime {
MediaTime(v, Frequency::NINETY_KHZ)
}
#[inline(always)]
pub const fn from_millis(v: u64) -> MediaTime {
MediaTime(v, Frequency::MILLIS)
}
#[inline(always)]
pub const fn from_hundredths(v: u64) -> MediaTime {
MediaTime(v, Frequency::HUNDREDTHS)
}
#[inline(always)]
pub const fn from_secs(v: u64) -> MediaTime {
MediaTime(v, Frequency::SECONDS)
}
#[inline(always)]
pub fn from_seconds(v: impl Into<f64>) -> MediaTime {
Self::from_micros((v.into() * 1_000_000.0_f64) as u64)
}
#[inline(always)]
pub fn as_seconds(&self) -> f64 {
let denom: f64 = self.1.get().into();
self.0 as f64 / denom
}
pub const fn as_micros(&self) -> u64 {
self.rebase(Frequency::MICROS).numer()
}
#[inline(always)]
pub const fn is_zero(&self) -> bool {
self.0 == 0
}
#[inline(always)]
pub const fn rebase(self, denom: Frequency) -> MediaTime {
if denom.get() == self.1.get() {
self
} else {
let numer = self.0 as i128 * denom.get() as i128 / self.1.get() as i128;
MediaTime::new(numer as u64, denom)
}
}
#[inline(always)]
fn same_base(t0: MediaTime, t1: MediaTime) -> (MediaTime, MediaTime) {
let max = Frequency(t0.1.0.max(t1.1.0));
(t0.rebase(max), t1.rebase(max))
}
#[inline]
pub fn checked_sub(self, rhs: MediaTime) -> Option<MediaTime> {
let (lhs, rhs) = MediaTime::same_base(self, rhs);
if lhs.0 < rhs.0 {
None
} else {
Some(MediaTime::new(lhs.0 - rhs.0, lhs.1))
}
}
#[inline]
pub fn saturating_sub(self, rhs: MediaTime) -> MediaTime {
match self.checked_sub(rhs) {
Some(v) => v,
None => MediaTime::ZERO,
}
}
}
impl PartialEq for MediaTime {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
let (t0, t1) = MediaTime::same_base(*self, *other);
t0.0 == t1.0
}
}
impl Eq for MediaTime {}
impl PartialOrd for MediaTime {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MediaTime {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
let (t0, t1) = MediaTime::same_base(*self, *other);
if t0 == t1 {
Ordering::Equal
} else if t0.0 < t1.0 {
Ordering::Less
} else {
Ordering::Greater
}
}
}
impl Add for MediaTime {
type Output = MediaTime;
#[inline(always)]
fn add(self, rhs: Self) -> Self::Output {
let (t0, t1) = MediaTime::same_base(self, rhs);
MediaTime::new(t0.0 + t1.0, t0.1)
}
}
impl Sub for MediaTime {
type Output = MediaTime;
fn sub(self, rhs: Self) -> Self::Output {
self.checked_sub(rhs)
.expect("overflow when subtracting MediaTime")
}
}
impl SubAssign for MediaTime {
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl Sub<MediaTime> for Instant {
type Output = Instant;
fn sub(self, rhs: MediaTime) -> Self::Output {
self - Duration::from(rhs)
}
}
impl SubAssign<MediaTime> for Instant {
fn sub_assign(&mut self, rhs: MediaTime) {
*self = *self - rhs;
}
}
impl AddAssign for MediaTime {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl Add<MediaTime> for Instant {
type Output = Instant;
fn add(self, rhs: MediaTime) -> Self::Output {
self + Duration::from(rhs)
}
}
impl AddAssign<MediaTime> for Instant {
fn add_assign(&mut self, rhs: MediaTime) {
*self = *self + rhs;
}
}
impl From<MediaTime> for Duration {
fn from(value: MediaTime) -> Self {
Duration::from_micros(value.rebase(Frequency::MICROS).numer())
}
}
impl From<Duration> for MediaTime {
fn from(v: Duration) -> Self {
MediaTime::new(v.as_micros() as u64, Frequency::MICROS)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ts_rebase() {
let t1 = MediaTime::from_seconds(10.0);
let t2 = t1.rebase(Frequency::NINETY_KHZ);
assert_eq!(t2.numer(), 90_000 * 10);
assert_eq!(t2.denom(), 90_000);
println!("{}", (10.0234_f64).fract());
}
}