mod parse;
use crate::{parse::IResult, time::Timestamp};
use ordered_float::OrderedFloat;
use std::{
fmt,
hash::{Hash, Hasher},
iter::Sum,
ops::{Add, AddAssign},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IllegalBeat {
NaN,
Negative,
}
impl fmt::Display for IllegalBeat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IllegalBeat::NaN => write!(f, "Beat may not be NaN"),
IllegalBeat::Negative => {
write!(f, "Beat may not be Negative")
}
}
}
}
impl std::error::Error for IllegalBeat {}
#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)]
pub struct Beat(f64);
impl Eq for Beat {}
impl Hash for Beat {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
state.write_u64(self.0.to_bits())
}
}
impl Ord for Beat {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other).expect("Beat may not be NaN")
}
}
impl Beat {
pub const ZERO: Beat = Beat(0.0);
pub const ONE: Beat = Beat(1.0);
pub fn new(beat: f64) -> Result<Self, IllegalBeat> {
if beat >= 0.0 {
Ok(Self(beat))
} else if beat.is_nan() {
Err(IllegalBeat::NaN)
} else {
Err(IllegalBeat::Negative)
}
}
pub fn get(&self) -> f64 {
self.0
}
pub fn max(self, other: Self) -> Self {
Self(self.0.max(other.0))
}
pub fn checked_add(self, other: Self) -> Result<Self, IllegalBeat> {
Self::new(self.0 + other.0)
}
pub fn parse(input: &str) -> IResult<&str, Self> {
parse::beat(input)
}
}
impl Add for Beat {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
self.checked_add(rhs).expect("added to illegal beat")
}
}
impl AddAssign for Beat {
fn add_assign(&mut self, rhs: Self) {
self.0 = self.checked_add(rhs).expect("added to illegal beat").0;
}
}
impl Sum for Beat {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
Self::new(iter.map(|each| each.0).sum()).expect("summed to illegal beat")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IllegalBeatsPerMinute {
NaN,
NonPositive,
}
impl fmt::Display for IllegalBeatsPerMinute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IllegalBeatsPerMinute::NaN => write!(f, "BeatsPerMinute may not be NaN"),
IllegalBeatsPerMinute::NonPositive => {
write!(f, "BeatsPerMinute must be positive")
}
}
}
}
impl std::error::Error for IllegalBeatsPerMinute {}
#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)]
pub struct BeatsPerMinute(f64);
impl Eq for BeatsPerMinute {}
impl Ord for BeatsPerMinute {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other)
.expect("BeatsPerMinute may not be NaN")
}
}
impl Hash for BeatsPerMinute {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
state.write_u64(self.0.to_bits())
}
}
impl BeatsPerMinute {
pub fn new(beats_per_minute: f64) -> Result<Self, IllegalBeatsPerMinute> {
if beats_per_minute > 0.0 {
Ok(Self(beats_per_minute))
} else if beats_per_minute.is_nan() {
Err(IllegalBeatsPerMinute::NaN)
} else {
Err(IllegalBeatsPerMinute::NonPositive)
}
}
pub fn get(&self) -> f64 {
self.0
}
pub fn max(self, other: Self) -> Self {
Self(self.0.max(other.0))
}
pub fn checked_add(self, other: Self) -> Result<Self, IllegalBeatsPerMinute> {
Self::new(self.0 + other.0)
}
}
impl Add for BeatsPerMinute {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
self.checked_add(rhs).expect("added to illegal beat")
}
}
impl AddAssign for BeatsPerMinute {
fn add_assign(&mut self, rhs: Self) {
self.0 = self.checked_add(rhs).expect("added to illegal beat").0;
}
}
impl Sum for BeatsPerMinute {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
Self::new(iter.map(|each| each.0).sum()).expect("summed to illegal beat")
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub struct TempoInstruction {
pub beat: Beat,
pub tempo: BeatsPerMinute,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)]
struct CalculatedTempoInstruction {
beat: f64,
time: f64,
seconds_per_beat: f64,
}
#[derive(Debug, Default)]
pub struct Metronome {
instructions: Vec<CalculatedTempoInstruction>,
}
impl Metronome {
pub fn new() -> Self {
Default::default()
}
pub fn add_tempo_instruction(&mut self, instruction: TempoInstruction) {
self.instructions.push(CalculatedTempoInstruction {
beat: instruction.beat.get(),
time: 0.0f64,
seconds_per_beat: 60.0 / instruction.tempo.get(),
});
self.instructions
.sort_by_key(|instruction| OrderedFloat(instruction.beat));
let mut last = CalculatedTempoInstruction {
beat: 0.0,
time: 0.0,
seconds_per_beat: self.instructions[0].seconds_per_beat,
};
for instruction in &mut self.instructions {
let time = if instruction.beat == last.beat {
last.time
} else {
Self::integrate_beat(last, *instruction, instruction.beat)
};
instruction.time = time;
last = *instruction;
}
}
pub fn beat_to_time(&self, beat: Beat) -> Timestamp {
let instructions_len = self.instructions.len();
let beat = beat.get();
Timestamp::from_seconds(match instructions_len {
0 => (60.0 / 128.0) * beat,
1 => self.instructions[0].seconds_per_beat * beat,
_ => {
match self
.instructions
.binary_search_by_key(&OrderedFloat(beat), |instruction| {
OrderedFloat(instruction.beat)
}) {
Ok(index) => self.instructions[index].time,
Err(0) => self.instructions[0].seconds_per_beat * beat,
Err(index) if index == instructions_len => {
let last = self.instructions[instructions_len - 1];
let beats_left = beat - last.beat;
let time_since_last = beats_left * last.seconds_per_beat;
last.time + time_since_last
}
Err(index) => {
let a = self.instructions[index - 1];
let b = self.instructions[index];
Self::integrate_beat(a, b, beat)
}
}
}
})
.expect("Time ended up negative or NaN")
}
fn integrate_beat(
a: CalculatedTempoInstruction,
b: CalculatedTempoInstruction,
beat: f64,
) -> f64 {
let b1 = a.beat;
let spb1 = a.seconds_per_beat;
let b2 = b.beat;
let spb2 = b.seconds_per_beat;
let m = (spb2 - spb1) / (b2 - b1);
let time = m * (beat.powi(2) - b1.powi(2)) / 2.0 + (beat - b1) * (-m * b1 + spb1);
a.time + time
}
}