use std::num::NonZeroU32;
use serde::{Deserialize, Serialize};
use crate::error::Error;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(into = "u32", try_from = "u32")]
pub struct SampleRate(NonZeroU32);
impl SampleRate {
pub const fn new(rate: NonZeroU32) -> Self {
Self(rate)
}
pub const fn get(self) -> u32 {
self.0.get()
}
}
impl TryFrom<u32> for SampleRate {
type Error = Error;
fn try_from(rate: u32) -> Result<Self, Error> {
NonZeroU32::new(rate)
.map(Self)
.ok_or_else(|| Error::validation("sample rate must be nonzero"))
}
}
impl From<SampleRate> for u32 {
fn from(rate: SampleRate) -> u32 {
rate.get()
}
}
impl std::fmt::Display for SampleRate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} Hz", self.get())
}
}
#[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct Samples(pub u64);
impl Samples {
pub const fn get(self) -> u64 {
self.0
}
}
impl std::fmt::Display for Samples {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} samples", self.0)
}
}
#[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct Frames(pub u64);
impl Frames {
pub const fn get(self) -> u64 {
self.0
}
}
impl std::fmt::Display for Frames {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} frames", self.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TimeBase {
rate: SampleRate,
stride: NonZeroU32,
}
impl TimeBase {
pub const fn new(rate: SampleRate, stride: NonZeroU32) -> Self {
Self { rate, stride }
}
pub const fn of_samples(rate: SampleRate) -> Self {
Self {
rate,
stride: NonZeroU32::MIN,
}
}
pub const fn sample_rate(self) -> SampleRate {
self.rate
}
pub const fn stride(self) -> NonZeroU32 {
self.stride
}
pub fn frames_per_second(self) -> f64 {
f64::from(self.rate.get()) / f64::from(self.stride.get())
}
pub fn samples(self, frames: Frames) -> Option<Samples> {
frames
.get()
.checked_mul(u64::from(self.stride.get()))
.map(Samples)
}
pub fn frames_exact(self, samples: Samples) -> Option<Frames> {
let stride = u64::from(self.stride.get());
samples
.get()
.is_multiple_of(stride)
.then(|| Frames(samples.get() / stride))
}
pub fn seconds(self, frames: Frames) -> f64 {
frames.get() as f64 * f64::from(self.stride.get()) / f64::from(self.rate.get())
}
}
impl std::fmt::Display for TimeBase {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} Hz / {}", self.rate.get(), self.stride.get())
}
}
pub fn require_same_clock(left: TimeBase, right: TimeBase) -> Result<TimeBase, Error> {
if left == right {
Ok(left)
} else {
Err(Error::ClockMismatch { left, right })
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rate(hz: u32) -> SampleRate {
SampleRate::try_from(hz).unwrap()
}
fn stride(n: u32) -> NonZeroU32 {
NonZeroU32::new(n).unwrap()
}
#[test]
fn zero_sample_rate_is_unrepresentable() {
assert!(SampleRate::try_from(0).is_err());
assert!(serde_json::from_str::<SampleRate>("0").is_err());
assert_eq!(
serde_json::from_str::<SampleRate>("44100").unwrap().get(),
44_100
);
}
#[test]
fn frames_to_samples_is_exact() {
let tb = TimeBase::new(rate(44_100), stride(512));
assert_eq!(tb.samples(Frames(3)), Some(Samples(1536)));
assert_eq!(tb.frames_exact(Samples(1536)), Some(Frames(3)));
assert_eq!(tb.frames_exact(Samples(1537)), None);
}
#[test]
fn samples_overflow_is_none_not_wrap() {
let tb = TimeBase::new(rate(48_000), stride(u32::MAX));
assert_eq!(tb.samples(Frames(u64::MAX)), None);
}
#[test]
fn equality_is_representational() {
let a = TimeBase::new(rate(44_100), stride(512));
let b = TimeBase::new(rate(88_200), stride(1024));
assert!((a.frames_per_second() - b.frames_per_second()).abs() < 1e-9);
assert_ne!(a, b);
assert!(require_same_clock(a, b).is_err());
assert_eq!(require_same_clock(a, a).unwrap(), a);
}
#[test]
fn seconds_and_display() {
let tb = TimeBase::new(rate(48_000), stride(480));
assert!((tb.seconds(Frames(100)) - 1.0).abs() < 1e-12);
assert_eq!(tb.to_string(), "48000 Hz / 480");
assert_eq!(TimeBase::of_samples(rate(48_000)).stride().get(), 1);
}
#[test]
fn serde_round_trips() {
let tb = TimeBase::new(rate(44_100), stride(512));
let json = serde_json::to_string(&tb).unwrap();
assert_eq!(serde_json::from_str::<TimeBase>(&json).unwrap(), tb);
}
}