mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 12:46:42 +01:00
186 lines
5.7 KiB
Rust
186 lines
5.7 KiB
Rust
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 <E, T> (
|
|
&self, step: (f64, f64), events: E
|
|
) -> Vec<(f64, T)>
|
|
where E: std::iter::Iterator<Item=(f64, T)> + 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(())
|
|
}
|
|
}
|