use std::sync::OnceLock;
use truce_rack_core::transport::TransportInfo;
#[derive(Debug, Clone, Copy)]
pub struct TransportConfig {
pub enabled: bool,
pub tempo_bpm: f64,
pub time_sig: (u32, u32),
pub playing: bool,
}
impl Default for TransportConfig {
fn default() -> Self {
Self {
enabled: true,
tempo_bpm: 120.0,
time_sig: (4, 4),
playing: true,
}
}
}
static CONFIG: OnceLock<TransportConfig> = OnceLock::new();
pub fn set_config(config: TransportConfig) {
let _ = CONFIG.set(config);
}
#[must_use]
pub fn config() -> TransportConfig {
CONFIG.get().copied().unwrap_or_default()
}
pub struct TransportClock {
config: TransportConfig,
sample_pos: i64,
}
impl TransportClock {
#[must_use]
pub fn new() -> Self {
Self {
config: config(),
sample_pos: 0,
}
}
#[allow(clippy::cast_precision_loss)]
pub fn next_block(&mut self, frames: usize, sample_rate: f64) -> Option<TransportInfo> {
if !self.config.enabled {
return None;
}
let (num, den) = self.config.time_sig;
let beats_per_sec = self.config.tempo_bpm / 60.0;
let pos_seconds = self.sample_pos as f64 / sample_rate.max(1.0);
let song_position_beats = pos_seconds * beats_per_sec;
let beats_per_bar = f64::from(num) * 4.0 / f64::from(den.max(1));
let bar_index = (song_position_beats / beats_per_bar.max(f64::EPSILON)).floor();
let bar_start_beats = bar_index * beats_per_bar;
let info = TransportInfo {
tempo_bpm: Some(self.config.tempo_bpm),
time_signature: Some((num, den)),
song_position_beats: Some(song_position_beats),
song_position_samples: Some(self.sample_pos),
bar_start_beats: Some(bar_start_beats),
playing: self.config.playing,
recording: false,
loop_active: false,
};
if self.config.playing {
self.sample_pos = self
.sample_pos
.saturating_add(i64::try_from(frames).unwrap_or(0));
}
Some(info)
}
}
impl Default for TransportClock {
fn default() -> Self {
Self::new()
}
}