wip: simplifying timebase

This commit is contained in:
🪞👃🪞 2024-07-06 09:17:39 +03:00
parent 238d307817
commit b3e6206b08
5 changed files with 50 additions and 89 deletions

View file

@ -3,11 +3,11 @@ use atomic_float::AtomicF64;
#[derive(Debug)] #[derive(Debug)]
pub struct Timebase { pub struct Timebase {
/// Frames per second /// Frames per second
pub rate: AtomicF64, rate: AtomicF64,
/// Beats per minute /// Beats per minute
pub bpm: AtomicF64, bpm: AtomicF64,
/// Ticks per beat /// Ticks per beat
pub ppq: AtomicF64, ppq: AtomicF64,
} }
impl Default for Timebase { impl Default for Timebase {
fn default () -> Self { fn default () -> Self {
@ -27,61 +27,48 @@ impl Timebase {
#[inline] fn rate (&self) -> f64 { #[inline] fn rate (&self) -> f64 {
self.rate.load(Ordering::Relaxed) self.rate.load(Ordering::Relaxed)
} }
/// Usec per frame #[inline] fn usec_per_frame (&self) -> f64 {
#[inline] fn frame_usec (&self) -> f64 {
1_000_000 as f64 / self.rate() as f64 1_000_000 as f64 / self.rate() as f64
} }
/// Frames to usecs #[inline] pub fn frame_to_usec (&self, frame: f64) -> f64 {
#[inline] pub fn frames_usecs (&self, frame: f64) -> f64 { frame * self.usec_per_frame()
frame * self.frame_usec()
}
/// Frames to usecs
#[inline] pub fn usecs_frames (&self, usec: f64) -> f64 {
usec / self.frame_usec()
} }
/// Beats per minute /// Beats per minute
#[inline] pub fn bpm (&self) -> f64 { #[inline] pub fn bpm (&self) -> f64 {
self.bpm.load(Ordering::Relaxed) self.bpm.load(Ordering::Relaxed)
} }
/// Usec per beat #[inline] fn usec_per_beat (&self) -> f64 {
#[inline] fn beat_usec (&self) -> f64 {
60_000_000f64 / self.bpm() as f64 60_000_000f64 / self.bpm() as f64
} }
#[inline] fn beat_per_second (&self) -> f64 {
self.bpm() as f64 / 60000000.0
}
/// Pulses per beat /// Pulses per beat
#[inline] pub fn ppq (&self) -> f64 { #[inline] pub fn ppq (&self) -> f64 {
self.ppq.load(Ordering::Relaxed) self.ppq.load(Ordering::Relaxed)
} }
/// Usec per pulse #[inline] fn usec_per_pulse (&self) -> f64 {
#[inline] fn pulse_usec (&self) -> f64 { self.usec_per_beat() / self.ppq() as f64
self.beat_usec() / self.ppq() as f64
} }
/// Usecs to pulses #[inline] pub fn pulse_per_frame (&self) -> f64 {
#[inline] pub fn pulses_usecs (&self, pulses: f64) -> f64 { self.usec_per_pulse() / self.usec_per_frame() as f64
self.pulse_usec() * pulses
} }
/// Pulses per frame #[inline] pub fn frame_to_pulse (&self, pulses: f64) -> f64 {
#[inline] pub fn pulse_frame (&self) -> f64 { self.pulse_per_frame() * pulses
self.pulse_usec() / self.frame_usec() as f64
} }
/// Frames per pulse #[inline] pub fn pulse_to_frame (&self, frames: f64) -> f64 {
#[inline] pub fn frame_pulse (&self) -> f64 { frames / self.pulse_per_frame()
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 { #[inline] pub fn note_to_usec (&self, (num, den): (f64, f64)) -> f64 {
4.0 * self.beat_usec() * num / den 4.0 * self.usec_per_beat() * num / den
}
#[inline] fn pulses_per_second (&self) -> f64 {
self.beat_per_second() * self.ppq() as f64
}
#[inline] pub fn frames_per_pulse (&self) -> f64 {
self.rate() as f64 / self.pulses_per_second()
} }
#[inline] fn usec_to_frame (&self, usec: f64) -> f64 { #[inline] fn usec_to_frame (&self, usec: f64) -> f64 {
@ -109,43 +96,4 @@ impl Timebase {
.collect() .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()
}
}
#[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(())
}
} }

View file

@ -47,8 +47,8 @@ impl Phrase {
(frame0, frames, _): (usize, usize, f64), (frame0, frames, _): (usize, usize, f64),
) { ) {
for (time, tick) in frames_to_ticks( for (time, tick) in frames_to_ticks(
timebase.pulse_frame(), timebase.pulse_per_frame(),
timebase.pulses_frames(self.length as f64), timebase.frame_to_pulse(self.length as f64),
frame0 as f64, frame0 as f64,
(frame0 + frames) as f64, (frame0 + frames) as f64,
) { ) {
@ -76,10 +76,9 @@ fn frames_to_ticks (fpt: f64, repeat: f64, start: f64, end: f64) -> Box<dyn Iter
let jitter = frame.rem_euclid(fpt); // ramps let jitter = frame.rem_euclid(fpt); // ramps
let next_jitter = (frame + 1.0).rem_euclid(fpt); let next_jitter = (frame + 1.0).rem_euclid(fpt);
if jitter > next_jitter { // at head of ramp crossing if jitter > next_jitter { // at head of ramp crossing
ticks.push(( let time = frame as usize % (end as usize-start as usize);
frame as usize % (end as usize-start as usize), let tick = (frame / fpt) as usize;
(frame / fpt) as usize) ticks.push((time, tick));
);
}; };
}; };
let start_frame = start % repeat; let start_frame = start % repeat;
@ -125,3 +124,18 @@ fn frames_to_ticks_2 (add_frame: &mut impl FnMut(f64), start_frame: f64, end_fra
phrase phrase
}} }}
} }
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_frames_to_ticks () {
let fpt = Timebase::default().pulse_per_frame();
let repeat = 0.0;
let start = 0.0;
let end = 0.0;
println!("{:?}", frames_to_ticks(fpt, repeat, start, end).collect::<Vec<_>>())
}
}

View file

@ -138,8 +138,7 @@ impl Track {
if self.reset { if self.reset {
all_notes_off(&mut self.midi_out_buf); all_notes_off(&mut self.midi_out_buf);
self.reset = false; self.reset = false;
} } else if reset {
else if reset {
all_notes_off(&mut self.midi_out_buf); all_notes_off(&mut self.midi_out_buf);
} }
// Play from phrase into output buffer // Play from phrase into output buffer
@ -168,7 +167,7 @@ impl Track {
} }
if recording { if recording {
if let Some(phrase) = phrase { if let Some(phrase) = phrase {
let pulse = timebase.frames_pulses((frame0 + time) as f64) as usize; let pulse = timebase.pulse_to_frame((frame0 + time) as f64) as usize;
let pulse = pulse % phrase.length; let pulse = pulse % phrase.length;
let pulse = (pulse / quant) * quant; let pulse = (pulse / quant) * quant;
let contains = phrase.notes.contains_key(&pulse); let contains = phrase.notes.contains_key(&pulse);

View file

@ -59,7 +59,7 @@ render!(App |self, buf, area| {
phrase, phrase,
focused: self.section == 2, focused: self.section == 2,
ppq: self.timebase.ppq() as usize, ppq: self.timebase.ppq() as usize,
now: self.timebase.frames_pulses(self.playhead as f64) as usize, now: self.timebase.pulse_to_frame(self.playhead as f64) as usize,
time_cursor: self.time_cursor, time_cursor: self.time_cursor,
time_start: self.time_start, time_start: self.time_start,
time_zoom: self.time_zoom, time_zoom: self.time_zoom,

View file

@ -26,11 +26,11 @@ impl<'a> Render for TransportView<'a> {
pub fn draw_timer (buf: &mut Buffer, x: u16, y: u16, timebase: &Arc<Timebase>, frame: usize) { pub fn draw_timer (buf: &mut Buffer, x: u16, y: u16, timebase: &Arc<Timebase>, frame: usize) {
let ppq = timebase.ppq() as usize; let ppq = timebase.ppq() as usize;
let pulse = timebase.frames_pulses(frame as f64) as usize; let pulse = timebase.pulse_to_frame(frame as f64) as usize;
let (beats, pulses) = (pulse / ppq, pulse % ppq); let (beats, pulses) = (pulse / ppq, pulse % ppq);
let (bars, beats) = (beats / 4, beats % 4); let (bars, beats) = (beats / 4, beats % 4);
let usecs = timebase.frames_usecs(frame as f64) as usize; let usecs = timebase.frame_to_usec(frame as f64) as usize;
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
let (minutes, seconds) = (seconds / 60, seconds % 60); let (minutes, seconds) = (seconds / 60, seconds % 60);