use std::fmt;
#[derive(Debug, Clone, Copy)]
pub struct TransportState {
pub is_playing: bool,
pub bpm: f64,
pub frame_pos: u64,
pub time_sig_num: u8,
pub time_sig_den: u8,
pub bar_start_frame: u64,
}
impl Default for TransportState {
fn default() -> Self {
Self {
is_playing: true,
bpm: 120.0,
frame_pos: 0,
time_sig_num: 4,
time_sig_den: 4,
bar_start_frame: 0,
}
}
}
impl fmt::Display for TransportState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} {:.1} BPM {} {}/{} frame={} bar_start={}",
if self.is_playing { "▶" } else { "⏹" },
self.bpm,
if self.is_playing {
"rolling"
} else {
"stopped"
},
self.time_sig_num,
self.time_sig_den,
self.frame_pos,
self.bar_start_frame,
)
}
}
#[derive(Debug, Clone)]
pub struct RenderContext {
pub sample_pos: u64,
pub samples_since_last: u32,
pub sample_rate: f32,
pub transport: TransportState,
pub speed_ratio: f64,
}
impl RenderContext {
pub fn new(sample_pos: u64, samples_since_last: u32, sample_rate: f32) -> Self {
Self {
sample_pos,
samples_since_last,
sample_rate,
transport: TransportState::default(),
speed_ratio: 1.0,
}
}
pub fn with_tempo(
sample_pos: u64,
samples_since_last: u32,
sample_rate: f32,
bpm: f32,
) -> Self {
Self {
sample_pos,
samples_since_last,
sample_rate,
transport: TransportState {
bpm: bpm as f64,
..TransportState::default()
},
speed_ratio: 1.0,
}
}
pub fn delta_seconds(&self) -> f64 {
self.samples_since_last as f64 / self.sample_rate as f64
}
pub fn absolute_seconds(&self) -> f64 {
self.sample_pos as f64 / self.sample_rate as f64
}
pub fn bpm(&self) -> f32 {
self.transport.bpm as f32
}
pub fn beat_position(&self) -> Option<f64> {
if self.transport.bpm <= 0.0 {
return None;
}
let seconds_per_beat = 60.0 / self.transport.bpm;
Some(self.absolute_seconds() / seconds_per_beat)
}
pub fn musical_position(&self) -> Option<(u32, u8, u8)> {
self.beat_position().map(|total_beats| {
let beats_per_bar = self.transport.time_sig_num as f64;
let bar = (total_beats / beats_per_bar).floor() as u32;
let beat_in_bar = (total_beats % beats_per_bar) as u8;
let sixteenth = ((total_beats.fract() * 4.0) as u8) % 4;
(bar, beat_in_bar, sixteenth)
})
}
pub fn is_new_bar(&self) -> bool {
self.musical_position()
.map(|(_bar, beat, sixteenth)| beat == 0 && sixteenth == 0)
.unwrap_or(false)
}
pub fn is_new_beat(&self) -> bool {
self.musical_position()
.map(|(_bar, _beat, sixteenth)| sixteenth == 0)
.unwrap_or(false)
}
}
impl fmt::Display for RenderContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"RenderContext(sample={}, block={}, sr={:.0}, transport=[{}], ratio={:.6})",
self.sample_pos,
self.samples_since_last,
self.sample_rate,
self.transport,
self.speed_ratio,
)
}
}