use crate::core::*; use atomic_float::AtomicF64; #[derive(Debug)] pub struct Timebase { /// Frames per second pub rate: AtomicF64, /// Beats per minute pub bpm: AtomicF64, /// Ticks per beat pub ppq: AtomicF64, } impl Default for Timebase { fn default () -> Self { Self { rate: 48000f64.into(), bpm: 125f64.into(), ppq: 96f64.into(), } } } impl Timebase { pub fn new (rate: f64, bpm: f64, ppq: f64) -> Self { Self { rate: rate.into(), bpm: bpm.into(), ppq: ppq.into() } } /// Frames per second #[inline] fn rate (&self) -> f64 { self.rate.load(Ordering::Relaxed) } /// Usec per frame #[inline] fn frame_usec (&self) -> f64 { 1_000_000 as f64 / self.rate() as f64 } /// Frames to usecs #[inline] pub fn frames_usecs (&self, frame: f64) -> f64 { frame * self.frame_usec() } /// Beats per minute #[inline] pub fn bpm (&self) -> f64 { self.bpm.load(Ordering::Relaxed) } /// Usec per beat #[inline] fn beat_usec (&self) -> f64 { 60_000_000f64 / self.bpm() as f64 } /// Pulses per beat #[inline] pub fn ppq (&self) -> f64 { self.ppq.load(Ordering::Relaxed) } /// Usec per pulse #[inline] fn pulse_usec (&self) -> f64 { self.beat_usec() / self.ppq() as f64 } /// Pulses per frame #[inline] pub fn pulse_frame (&self) -> f64 { self.pulse_usec() / self.frame_usec() as f64 } /// Frames per pulse #[inline] pub fn frame_pulse (&self) -> f64 { self.frame_usec() as f64 / self.pulse_usec() } /// Frames to pulses #[inline] pub fn pulses_frames (&self, pulses: f64) -> f64 { self.pulse_frame() * pulses } /// Pulses to frames #[inline] pub fn frames_pulses (&self, frames: f64) -> f64 { frames / self.pulse_frame() } #[inline] pub fn usec_per_step (&self, divisor: f64) -> f64 { self.beat_usec() / divisor } #[inline] pub fn note_to_usec (&self, (num, den): (f64, f64)) -> f64 { 4.0 * self.beat_usec() * num / den } #[inline] fn usec_to_frame (&self, usec: f64) -> f64 { usec * self.rate() / 1000.0 } #[inline] pub fn note_to_frame (&self, note: (f64, f64)) -> f64 { self.usec_to_frame(self.note_to_usec(note)) } #[inline] pub fn quantize ( &self, step: (f64, f64), time: f64 ) -> (f64, f64) { let step = self.note_to_usec(step); (time / step, time % step) } #[inline] pub fn quantize_into ( &self, step: (f64, f64), events: E ) -> Vec<(f64, T)> where E: std::iter::Iterator + Sized { let step = (step.0.into(), step.1.into()); events .map(|(time, event)|(self.quantize(step, time).0, event)) .collect() } #[inline] fn beats_per_second (&self) -> f64 { self.bpm() as f64 / 60000000.0 } #[inline] fn ticks_per_second (&self) -> f64 { self.beats_per_second() * self.ppq() as f64 } #[inline] pub fn frames_per_tick (&self) -> f64 { self.rate() as f64 / self.ticks_per_second() } pub fn frames_to_ticks ( &self, start: f64, end: f64, repeat: f64, ) -> Vec<(usize, usize)> { let start_frame = start % repeat; let end_frame = end % repeat; let fpt = self.pulse_frame(); //panic!("{start_frame} {end_frame} {fpt}"); let mut ticks = vec![]; let mut add_frame = |frame: f64|{ let jitter = frame.rem_euclid(fpt); let last_jitter = (frame - 1.0).max(0.0) % fpt; let next_jitter = frame + 1.0 % fpt; if jitter <= last_jitter && jitter <= next_jitter { ticks.push((frame as usize % (end as usize-start as usize), (frame / fpt) as usize)); }; }; if start_frame < end_frame { for frame in start_frame as usize..end_frame as usize { add_frame(frame as f64); } } else { let mut frame = start_frame as usize; loop { add_frame(frame as f64); frame = frame + 1; if frame >= repeat as usize { frame = 0; loop { add_frame(frame as f64); frame = frame + 1; if frame >= (end_frame as usize).saturating_sub(1) { break } } break } } } ticks } } #[cfg(test)] mod test { use super::*; #[test] fn test_timebase () -> Usually<()> { let timebase = Timebase::new(48000.0, 240.0, 24.0); println!("F/S = {:.03}", s.rate()); println!("B/S = {:.03}", s.beats_per_secon()); println!("F/B = {:.03}", s.frames_per_beat()); println!("T/B = {:.03}", s.ticks_per_beat()); println!("F/T = {:.03}", s.frames_per_tick()); println!("F/L = {:.03}", s.frames_per_loop()); println!("T/L = {:.03}", s.ticks_per_loop()); let fpt = s.fpt(); let frames_per_chunk = 240; let chunk = |chunk: usize| s.frames_to_ticks( chunk * frames_per_chunk, (chunk + 1) * frames_per_chunk ); //for i in 0..2000 { //println!("{i} {:?}", chunk(i)); //} assert_eq!(chunk(0), vec![(0, 0), (125, 1)]); assert_eq!(chunk(1), vec![(10, 2), (135, 3)]); assert_eq!(chunk(12), vec![(120, 24)]); assert_eq!(chunk(412), vec![(120, 24)]); assert_eq!(chunk(413), vec![(5, 25), (130, 26)]); Ok(()) } }