use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
)]
#[serde(transparent)]
pub struct Frames(pub u64);
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
)]
#[serde(transparent)]
pub struct Samples(pub u64);
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
)]
#[serde(transparent)]
pub struct SampleRate(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(transparent)]
pub struct Hertz(pub f32);
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(transparent)]
pub struct Decibels(pub f32);
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(transparent)]
pub struct Tempo(pub f32);
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
)]
#[serde(transparent)]
pub struct Bars(pub u32);
impl From<u64> for Frames {
fn from(n: u64) -> Self {
Frames(n)
}
}
impl From<u64> for Samples {
fn from(n: u64) -> Self {
Samples(n)
}
}
impl std::ops::Add for Frames {
type Output = Frames;
fn add(self, rhs: Frames) -> Frames {
Frames(self.0 + rhs.0)
}
}
impl std::ops::Sub for Frames {
type Output = Frames;
fn sub(self, rhs: Frames) -> Frames {
Frames(self.0 - rhs.0)
}
}
impl std::ops::Add for Samples {
type Output = Samples;
fn add(self, rhs: Samples) -> Samples {
Samples(self.0 + rhs.0)
}
}
impl std::ops::Sub for Samples {
type Output = Samples;
fn sub(self, rhs: Samples) -> Samples {
Samples(self.0 - rhs.0)
}
}
impl std::ops::Add for Bars {
type Output = Bars;
fn add(self, rhs: Bars) -> Bars {
Bars(self.0 + rhs.0)
}
}
impl std::ops::Sub for Bars {
type Output = Bars;
fn sub(self, rhs: Bars) -> Bars {
Bars(self.0 - rhs.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, JsonSchema)]
pub struct Beat {
pub num: i64,
pub den: u32,
}
impl Beat {
pub const fn new(num: i64, den: u32) -> Beat {
let den = if den == 0 { 1 } else { den };
if num == 0 {
return Beat { num: 0, den: 1 };
}
let mut a = num.unsigned_abs();
let mut b = den as u64;
while b != 0 {
let t = a % b;
a = b;
b = t;
}
let g = a;
Beat {
num: num / g as i64,
den: (den as u64 / g) as u32,
}
}
pub const fn zero() -> Beat {
Beat { num: 0, den: 1 }
}
pub const fn from_int(n: i64) -> Beat {
Beat { num: n, den: 1 }
}
pub fn checked_add(self, other: Beat) -> Result<Beat, BeatError> {
let num = self.num as i128 * other.den as i128 + other.num as i128 * self.den as i128;
let den = self.den as i128 * other.den as i128;
checked(num, den)
}
pub fn checked_sub(self, other: Beat) -> Result<Beat, BeatError> {
let num = self.num as i128 * other.den as i128 - other.num as i128 * self.den as i128;
let den = self.den as i128 * other.den as i128;
checked(num, den)
}
pub fn mul_rational(self, num: i64, den: u32) -> Result<Beat, BeatError> {
checked(
self.num as i128 * num as i128,
self.den as i128 * den as i128,
)
}
pub fn scale(self, factor: i64) -> Result<Beat, BeatError> {
self.mul_rational(factor, 1)
}
pub fn to_f64(self) -> f64 {
self.num as f64 / self.den as f64
}
}
fn checked(num: i128, den: i128) -> Result<Beat, BeatError> {
let num = i64::try_from(num).map_err(|_| BeatError::Overflow)?;
let den = u32::try_from(den).map_err(|_| BeatError::Overflow)?;
Ok(Beat::new(num, den))
}
impl From<(i64, u32)> for Beat {
fn from((num, den): (i64, u32)) -> Beat {
Beat::new(num, den)
}
}
impl Ord for Beat {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
(self.num as i128 * other.den as i128).cmp(&(other.num as i128 * self.den as i128))
}
}
impl PartialOrd for Beat {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::fmt::Display for Beat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.den == 1 {
write!(f, "{}", self.num)
} else {
write!(f, "{}/{}", self.num, self.den)
}
}
}
impl<'de> Deserialize<'de> for Beat {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct Raw {
num: i64,
den: u32,
}
let raw = Raw::deserialize(deserializer)?;
Ok(Beat::new(raw.num, raw.den))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BeatError {
Overflow,
}
impl std::fmt::Display for BeatError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BeatError::Overflow => f.write_str("beat arithmetic overflow"),
}
}
}
impl std::error::Error for BeatError {}
pub fn beat_to_frames(beat: Beat, tempo: Tempo, rate: SampleRate) -> Frames {
let bpm = (tempo.0 as f64).max(1.0);
let seconds = beat.to_f64() * 60.0 / bpm;
let frames = seconds * rate.0 as f64;
Frames(frames.round().max(0.0) as u64)
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct MeterPoint {
pub bar: u32,
pub numerator: u32,
pub denominator: u32,
}
pub fn meter_at(map: &[MeterPoint], default_numerator: u32, bar: u32) -> MeterPoint {
let fallback = MeterPoint {
bar: 0,
numerator: default_numerator.max(1),
denominator: 4,
};
map.iter()
.rev()
.find(|p| p.bar <= bar)
.copied()
.unwrap_or(fallback)
}
pub fn bar_len(
map: &[MeterPoint],
default_numerator: u32,
pickup: Option<Beat>,
index: u32,
) -> Beat {
if index == 0
&& let Some(p) = pickup
{
return p;
}
let meter = meter_at(map, default_numerator, index);
Beat::new(meter.numerator as i64 * 4, meter.denominator)
}
pub fn beat_at_bar(
map: &[MeterPoint],
default_numerator: u32,
pickup: Option<Beat>,
bar: u32,
) -> Beat {
let mut beats = Beat::zero();
if bar == 0 {
return beats;
}
beats = beats
.checked_add(bar_len(map, default_numerator, pickup, 0))
.unwrap_or(Beat::new(i64::MAX, 1));
let mut i = 1u32;
while i < bar {
let seg_end = map
.iter()
.map(|p| p.bar)
.filter(|b| *b > i)
.min()
.unwrap_or(u32::MAX)
.min(bar);
let span = bar_len(map, default_numerator, pickup, i)
.scale(i64::from(seg_end - i))
.unwrap_or(Beat::new(i64::MAX, 1));
beats = beats.checked_add(span).unwrap_or(Beat::new(i64::MAX, 1));
i = seg_end;
}
beats
}
pub fn bar_count_at_beat(
map: &[MeterPoint],
default_numerator: u32,
pickup: Option<Beat>,
beat: Beat,
) -> u32 {
if beat <= Beat::zero() {
return 0;
}
let mut lo = 0u32;
let mut hi = 1u32;
while beat_at_bar(map, default_numerator, pickup, hi) < beat {
hi = hi.saturating_mul(2);
if hi == u32::MAX {
return hi;
}
}
while lo + 1 < hi {
let mid = lo + (hi - lo) / 2;
if beat_at_bar(map, default_numerator, pickup, mid) < beat {
lo = mid;
} else {
hi = mid;
}
}
hi
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_normalizes_by_gcd() {
assert_eq!(Beat::new(2, 4), Beat::new(1, 2));
assert_eq!(Beat::new(-3, 6), Beat { num: -1, den: 2 });
assert_eq!(Beat::new(7, 1), Beat { num: 7, den: 1 });
assert_eq!(Beat::new(0, 7), Beat::zero());
assert_eq!(Beat::new(5, 0), Beat::from_int(5));
}
#[test]
fn orders_across_denominators() {
assert!(Beat::new(1, 3) < Beat::new(1, 2));
assert!(Beat::new(2, 3) > Beat::new(1, 2));
assert_eq!(
Beat::new(2, 4).cmp(&Beat::new(1, 2)),
std::cmp::Ordering::Equal
);
let mut v = vec![Beat::new(3, 4), Beat::new(1, 3), Beat::new(1, 2)];
v.sort();
assert_eq!(v, vec![Beat::new(1, 3), Beat::new(1, 2), Beat::new(3, 4)]);
}
#[test]
fn comparison_never_overflows() {
let a = Beat::new(i64::MAX, u32::MAX);
let b = Beat::new(i64::MAX, u32::MAX - 1);
assert!(a < b, "same numerator, smaller denominator is larger");
assert!(Beat::new(i64::MAX, 1) > Beat::new(1, u32::MAX));
assert!(Beat::new(i64::MIN, 1) < Beat::new(-1, u32::MAX));
}
#[test]
fn add_sub_stay_exact() {
assert_eq!(
Beat::new(1, 3).checked_add(Beat::new(1, 6)).unwrap(),
Beat::new(1, 2)
);
assert_eq!(
Beat::new(1, 2).checked_sub(Beat::new(1, 3)).unwrap(),
Beat::new(1, 6)
);
assert_eq!(
Beat::zero().checked_sub(Beat::new(1, 4)).unwrap(),
Beat::new(-1, 4)
);
}
#[test]
fn add_reports_overflow() {
assert_eq!(
Beat::new(i64::MAX, 1).checked_add(Beat::from_int(1)),
Err(BeatError::Overflow)
);
assert_eq!(
Beat::new(1, u32::MAX).checked_add(Beat::new(1, u32::MAX - 1)),
Err(BeatError::Overflow),
"the unreduced denominator u32::MAX * (u32::MAX - 1) doesn't fit"
);
}
#[test]
fn scales_rationally() {
assert_eq!(Beat::new(2, 3).mul_rational(3, 4).unwrap(), Beat::new(1, 2));
assert_eq!(Beat::new(1, 2).scale(3).unwrap(), Beat::new(3, 2));
assert_eq!(Beat::new(1, 3).mul_rational(0, 1).unwrap(), Beat::zero());
}
#[test]
fn triplet_math_is_exact_at_the_frame_boundary() {
assert_eq!(
beat_to_frames(Beat::new(1, 3), Tempo(120.0), SampleRate(48_000)),
Frames(8000)
);
assert_eq!(
beat_to_frames(Beat::from_int(1), Tempo(120.0), SampleRate(48_000)),
Frames(24_000)
);
}
#[test]
fn rounds_half_away_from_zero() {
assert_eq!(
beat_to_frames(Beat::from_int(1), Tempo(40.0), SampleRate(3)),
Frames(5)
);
}
#[test]
fn clamps_degenerate_inputs() {
assert_eq!(
beat_to_frames(Beat::from_int(1), Tempo(0.5), SampleRate(48_000)),
Frames(2_880_000)
);
assert_eq!(
beat_to_frames(Beat::new(-1, 2), Tempo(120.0), SampleRate(48_000)),
Frames(0)
);
}
#[test]
fn integer_units_do_the_obvious_arithmetic() {
assert_eq!(Frames(10) + Frames(5), Frames(15));
assert_eq!(Frames(10) - Frames(5), Frames(5));
assert_eq!(Bars(1) + Bars(2), Bars(3));
assert_eq!(Frames::from(3u64), Frames(3));
assert!(SampleRate(96_000) > SampleRate(44_100));
}
#[test]
fn beat_displays_compactly() {
assert_eq!(Beat::new(3, 2).to_string(), "3/2");
assert_eq!(Beat::from_int(4).to_string(), "4");
assert_eq!(Beat::new(-1, 2).to_string(), "-1/2");
}
#[test]
fn beat_serde_is_a_flat_normalized_struct() {
assert_eq!(
serde_json::to_string(&Beat::new(1, 2)).unwrap(),
r#"{"num":1,"den":2}"#
);
let b: Beat = serde_json::from_str(r#"{"num":2,"den":4}"#).unwrap();
assert_eq!(b, Beat::new(1, 2));
}
#[test]
fn units_serialize_as_the_bare_inner_value() {
assert_eq!(serde_json::to_string(&Frames(8)).unwrap(), "8");
assert_eq!(serde_json::to_string(&Tempo(127.5)).unwrap(), "127.5");
let f: Frames = serde_json::from_str("8").unwrap();
assert_eq!(f, Frames(8));
}
}