use std::{ops, time::Duration};
pub const SUBSAMPLE_TESIMALS_PER_SECOND: u32 = 282_240_000;
pub const SUBBEAT_TESIMALS_PER_BEAT: u32 = 1_476_034_560;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde-derive", derive(serde::Serialize, serde::Deserialize))]
pub struct Seconds {
seconds: u32,
subsample_tesimals: u32,
}
impl Seconds {
pub const ZERO: Self = Self {
seconds: 0,
subsample_tesimals: 0,
};
#[allow(missing_docs)]
pub fn new(seconds: u32, subsample_tesimals: u32) -> Self {
Self {
seconds,
subsample_tesimals,
}
}
pub fn from_subsample_tesimals_u64(subsample_tesimals: u64) -> Self {
let seconds = (subsample_tesimals / SUBSAMPLE_TESIMALS_PER_SECOND as u64) as u32;
let subsample_tesimals =
(subsample_tesimals - (seconds as u64 * SUBSAMPLE_TESIMALS_PER_SECOND as u64)) as u32;
Self::new(seconds, subsample_tesimals)
}
pub fn to_subsample_tesimals_u64(&self) -> u64 {
self.seconds as u64 * SUBSAMPLE_TESIMALS_PER_SECOND as u64 + self.subsample_tesimals as u64
}
#[allow(missing_docs)]
pub fn from_seconds_f64(seconds_f64: f64) -> Self {
let seconds = seconds_f64.floor() as u32;
let subsample_tesimals =
(seconds_f64.fract() * SUBSAMPLE_TESIMALS_PER_SECOND as f64) as u32;
Self::new(seconds, subsample_tesimals)
}
pub fn from_seconds_f64_return_precision_loss(seconds_f64: f64) -> (Self, f64) {
let ts = Self::from_seconds_f64(seconds_f64);
(ts, seconds_f64 - ts.to_seconds_f64())
}
pub fn to_seconds_f64(&self) -> f64 {
self.seconds as f64
+ (self.subsample_tesimals as f64 / SUBSAMPLE_TESIMALS_PER_SECOND as f64)
}
pub fn from_samples(samples: u64, sample_rate: u64) -> Self {
let seconds = (samples / sample_rate) as u32;
let subsample_tesimals =
((samples % sample_rate) * SUBSAMPLE_TESIMALS_PER_SECOND as u64 / sample_rate) as u32;
Self {
seconds,
subsample_tesimals,
}
}
pub fn to_samples(&self, sample_rate: u64) -> u64 {
self.seconds as u64 * sample_rate
+ ((self.subsample_tesimals as u64 * sample_rate)
/ SUBSAMPLE_TESIMALS_PER_SECOND as u64) as u64
}
#[must_use]
pub fn checked_sub(self, rhs: Self) -> Option<Self> {
if self < rhs {
None
} else {
Some(if self.subsample_tesimals >= rhs.subsample_tesimals {
Self::new(
self.seconds - rhs.seconds,
self.subsample_tesimals - rhs.subsample_tesimals,
)
} else {
Self::new(
self.seconds - rhs.seconds - 1,
SUBSAMPLE_TESIMALS_PER_SECOND
- (rhs.subsample_tesimals - self.subsample_tesimals),
)
})
}
}
}
impl From<Duration> for Seconds {
fn from(value: Duration) -> Self {
let seconds = value.as_secs();
let nanos = value.subsec_nanos();
let conversion_factor = SUBSAMPLE_TESIMALS_PER_SECOND as f64 / 1_000_000_000_f64;
let subsample_tesimals = nanos as f64 * conversion_factor;
Self::new(seconds as u32, subsample_tesimals as u32)
}
}
impl PartialOrd for Seconds {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Seconds {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
if self.seconds == other.seconds {
self.subsample_tesimals.cmp(&other.subsample_tesimals)
} else {
self.seconds.cmp(&other.seconds)
}
}
}
impl ops::Add<Seconds> for Seconds {
type Output = Self;
fn add(self, rhs: Seconds) -> Self::Output {
let mut seconds = self.seconds + rhs.seconds;
let mut subsample_tesimals = self.subsample_tesimals + rhs.subsample_tesimals;
while subsample_tesimals >= SUBSAMPLE_TESIMALS_PER_SECOND {
seconds += 1;
subsample_tesimals -= SUBSAMPLE_TESIMALS_PER_SECOND;
}
Seconds::new(seconds, subsample_tesimals)
}
}
impl ops::AddAssign<Seconds> for Seconds {
fn add_assign(&mut self, rhs: Seconds) {
let result = *self + rhs;
*self = result;
}
}
impl ops::Mul<Seconds> for Seconds {
type Output = Self;
fn mul(self, rhs: Seconds) -> Self::Output {
let mut seconds = self.seconds * rhs.seconds;
let mut subsample_tesimals = self.subsample_tesimals as u64 * rhs.subsample_tesimals as u64;
if subsample_tesimals > SUBSAMPLE_TESIMALS_PER_SECOND as u64 {
seconds += (subsample_tesimals / SUBSAMPLE_TESIMALS_PER_SECOND as u64) as u32;
subsample_tesimals %= SUBSAMPLE_TESIMALS_PER_SECOND as u64;
}
Seconds::new(seconds, subsample_tesimals as u32)
}
}
impl ops::Mul<f64> for Seconds {
type Output = Self;
fn mul(self, rhs: f64) -> Self::Output {
let seconds = self.to_seconds_f64() * rhs;
Seconds::from_seconds_f64(seconds)
}
}
impl ops::Mul<Seconds> for f64 {
type Output = Seconds;
fn mul(self, rhs: Seconds) -> Self::Output {
rhs * self
}
}
impl ops::MulAssign<f64> for Seconds {
fn mul_assign(&mut self, rhs: f64) {
*self = *self * rhs;
}
}
impl ops::MulAssign<Seconds> for Seconds {
fn mul_assign(&mut self, rhs: Seconds) {
*self = *self * rhs;
}
}
impl ops::Mul<f32> for Seconds {
type Output = Self;
fn mul(self, rhs: f32) -> Self::Output {
let seconds = self.to_seconds_f64() * rhs as f64;
Seconds::from_seconds_f64(seconds)
}
}
impl ops::MulAssign<f32> for Seconds {
fn mul_assign(&mut self, rhs: f32) {
*self = *self * rhs;
}
}
impl ops::Mul<Seconds> for f32 {
type Output = Seconds;
fn mul(self, rhs: Seconds) -> Self::Output {
rhs * self
}
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "serde-derive", derive(serde::Serialize, serde::Deserialize))]
pub struct Beats {
beats: u32,
beat_tesimals: u32,
}
impl Beats {
pub const ZERO: Self = Self {
beats: 0,
beat_tesimals: 0,
};
#[allow(missing_docs)]
pub fn new(beats: u32, beat_tesimals: u32) -> Self {
Self {
beat_tesimals,
beats,
}
}
pub fn from_beats(beats: u32) -> Self {
Self::new(beats, 0)
}
pub fn from_fractional_beats<const FRACTION: u32>(beats: u32, fractional_beats: u32) -> Self {
assert!(fractional_beats < FRACTION);
let tesimals = (SUBBEAT_TESIMALS_PER_BEAT / FRACTION) * fractional_beats;
Self::new(beats, tesimals)
}
pub fn from_beats_f32(beats: f32) -> Self {
let fract = beats.fract();
Self {
beats: beats as u32,
beat_tesimals: (fract * SUBBEAT_TESIMALS_PER_BEAT as f32) as u32,
}
}
pub fn from_beats_f64(beats: f64) -> Self {
let fract = beats.fract();
Self {
beats: beats as u32,
beat_tesimals: (fract * SUBBEAT_TESIMALS_PER_BEAT as f64) as u32,
}
}
pub fn as_beats_f32(&self) -> f32 {
self.beats as f32 + (self.beat_tesimals as f32 / SUBBEAT_TESIMALS_PER_BEAT as f32)
}
pub fn as_beats_f64(&self) -> f64 {
self.beats as f64 + (self.beat_tesimals as f64 / SUBBEAT_TESIMALS_PER_BEAT as f64)
}
pub fn checked_sub(&self, v: Self) -> Option<Self> {
if self < &v {
None
} else {
if self.beat_tesimals < v.beat_tesimals {
Some(Self {
beats: self.beats - v.beats - 1,
beat_tesimals: SUBBEAT_TESIMALS_PER_BEAT
- (v.beat_tesimals - self.beat_tesimals),
})
} else {
Some(Self {
beats: self.beats - v.beats,
beat_tesimals: self.beat_tesimals - v.beat_tesimals,
})
}
}
}
}
impl std::iter::Sum for Beats {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Beats::ZERO, |acc, elem| acc + elem)
}
}
impl PartialOrd for Beats {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Beats {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
if self.beats == other.beats {
self.beat_tesimals.cmp(&other.beat_tesimals)
} else {
self.beats.cmp(&other.beats)
}
}
}
impl ops::Add<Beats> for Beats {
type Output = Self;
fn add(self, rhs: Beats) -> Self::Output {
let mut beats = self.beats + rhs.beats;
let mut beat_tesimals = self.beat_tesimals + rhs.beat_tesimals;
while beat_tesimals >= SUBBEAT_TESIMALS_PER_BEAT {
beats += 1;
beat_tesimals -= SUBBEAT_TESIMALS_PER_BEAT;
}
Beats::new(beats, beat_tesimals)
}
}
impl ops::AddAssign<Beats> for Beats {
fn add_assign(&mut self, rhs: Beats) {
let result = *self + rhs;
*self = result;
}
}
impl ops::Mul<Beats> for Beats {
type Output = Self;
fn mul(self, rhs: Beats) -> Self::Output {
let mut beats = self.beats * rhs.beats
+ ((self.beats as u64 * rhs.beat_tesimals as u64) / SUBBEAT_TESIMALS_PER_BEAT as u64)
as u32;
let mut beat_tesimals = (self.beat_tesimals as u64 * rhs.beat_tesimals as u64)
/ SUBBEAT_TESIMALS_PER_BEAT as u64
+ self.beat_tesimals as u64 * rhs.beats as u64;
if beat_tesimals > SUBBEAT_TESIMALS_PER_BEAT as u64 {
beats += (beat_tesimals / SUBBEAT_TESIMALS_PER_BEAT as u64) as u32;
beat_tesimals %= SUBBEAT_TESIMALS_PER_BEAT as u64;
}
Beats::new(beats, beat_tesimals as u32)
}
}
impl ops::MulAssign<Beats> for Beats {
fn mul_assign(&mut self, rhs: Beats) {
*self = *self * rhs;
}
}
#[cfg(test)]
mod tests {
use super::{Seconds, SUBSAMPLE_TESIMALS_PER_SECOND};
use std::time::Duration;
#[test]
fn convert_to_u64_and_back() {
let original_ts = Seconds::new(8347, SUBSAMPLE_TESIMALS_PER_SECOND - 5);
let as_u64 = original_ts.to_subsample_tesimals_u64();
assert_eq!(original_ts, Seconds::from_subsample_tesimals_u64(as_u64));
}
#[test]
fn duration_to_subsample_time() {
for s in [73.73, 10.832, 10000.25, 84923.399] {
let seconds = Seconds::from_seconds_f64(s);
let duration = Duration::from_secs_f64(s);
assert_eq!(seconds, duration.into())
}
}
#[test]
fn sample_conversion() {
assert_eq!(Seconds::from_samples(1, 44100).to_samples(88200), 2);
assert_eq!(Seconds::from_samples(1, 44100).to_samples(44100), 1);
assert_eq!(Seconds::from_samples(2, 44100).to_samples(44100), 2);
assert_eq!(Seconds::from_samples(3, 44100).to_samples(44100), 3);
assert_eq!(Seconds::from_samples(4, 44100).to_samples(44100), 4);
assert_eq!(Seconds::from_samples(44100, 44100).to_samples(88200), 88200);
assert_eq!(
Seconds::from_samples(44100 * 3 + 1, 44100).to_samples(88200),
3 * 88200 + 2
);
assert_eq!(
Seconds::from_samples(96000 * 3 + 8, 96000).to_samples(88200),
3 * 88200 + 7
);
}
#[test]
fn arithmetic() {
assert_eq!(
Seconds::new(0, SUBSAMPLE_TESIMALS_PER_SECOND - 1) + Seconds::new(1, 1),
Seconds::new(2, 0)
);
}
}