use crate::midi_cc_state::MidiCcState;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u32)]
pub enum FrameRate {
#[default]
Fps24 = 0,
Fps25 = 1,
Fps2997 = 2,
Fps30 = 3,
Fps2997Drop = 4,
Fps30Drop = 5,
Fps50 = 10,
Fps5994 = 11,
Fps60 = 12,
Fps5994Drop = 13,
Fps60Drop = 14,
}
impl FrameRate {
#[inline]
pub fn fps(&self) -> f64 {
match self {
Self::Fps24 => 24.0,
Self::Fps25 => 25.0,
Self::Fps2997 | Self::Fps2997Drop => 30000.0 / 1001.0, Self::Fps30 | Self::Fps30Drop => 30.0,
Self::Fps50 => 50.0,
Self::Fps5994 | Self::Fps5994Drop => 60000.0 / 1001.0, Self::Fps60 | Self::Fps60Drop => 60.0,
}
}
#[inline]
pub fn is_drop_frame(&self) -> bool {
matches!(
self,
Self::Fps2997Drop | Self::Fps30Drop | Self::Fps5994Drop | Self::Fps60Drop
)
}
#[inline]
pub fn from_raw(fps: u32, is_drop: bool) -> Option<Self> {
match fps {
24 => Some(Self::Fps24),
25 => Some(Self::Fps25),
29 if is_drop => Some(Self::Fps2997Drop),
29 => Some(Self::Fps2997),
30 if is_drop => Some(Self::Fps30Drop),
30 => Some(Self::Fps30),
50 => Some(Self::Fps50),
59 if is_drop => Some(Self::Fps5994Drop),
59 => Some(Self::Fps5994),
60 if is_drop => Some(Self::Fps60Drop),
60 => Some(Self::Fps60),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Transport {
pub tempo: Option<f64>,
pub time_sig_numerator: Option<i32>,
pub time_sig_denominator: Option<i32>,
pub project_time_samples: Option<i64>,
pub project_time_beats: Option<f64>,
pub bar_position_beats: Option<f64>,
pub cycle_start_beats: Option<f64>,
pub cycle_end_beats: Option<f64>,
pub is_playing: bool,
pub is_recording: bool,
pub is_cycle_active: bool,
pub system_time_ns: Option<i64>,
pub continuous_time_samples: Option<i64>,
pub samples_to_next_clock: Option<i32>,
pub smpte_offset_subframes: Option<i32>,
pub frame_rate: Option<FrameRate>,
}
impl Transport {
#[inline]
pub fn time_signature(&self) -> Option<(i32, i32)> {
match (self.time_sig_numerator, self.time_sig_denominator) {
(Some(num), Some(denom)) => Some((num, denom)),
_ => None,
}
}
#[inline]
pub fn cycle_range(&self) -> Option<(f64, f64)> {
match (self.cycle_start_beats, self.cycle_end_beats) {
(Some(start), Some(end)) => Some((start, end)),
_ => None,
}
}
#[inline]
pub fn is_looping(&self) -> bool {
self.is_cycle_active && self.cycle_range().is_some()
}
#[inline]
pub fn has_timing_info(&self) -> bool {
self.tempo.is_some()
|| self.project_time_samples.is_some()
|| self.project_time_beats.is_some()
}
#[inline]
pub fn has_time_signature(&self) -> bool {
self.time_sig_numerator.is_some() && self.time_sig_denominator.is_some()
}
#[inline]
pub fn smpte_frames(&self) -> Option<(i32, i32)> {
self.smpte_offset_subframes
.map(|sf| (sf.div_euclid(80), sf.rem_euclid(80)))
}
}
#[derive(Debug, Clone)]
pub struct ProcessContext<'a> {
pub sample_rate: f64,
pub num_samples: usize,
pub transport: Transport,
midi_cc_state: Option<&'a MidiCcState>,
}
impl<'a> ProcessContext<'a> {
#[inline]
pub fn new(sample_rate: f64, num_samples: usize, transport: Transport) -> Self {
Self {
sample_rate,
num_samples,
transport,
midi_cc_state: None,
}
}
#[inline]
pub fn with_midi_cc(
sample_rate: f64,
num_samples: usize,
transport: Transport,
midi_cc_state: &'a MidiCcState,
) -> Self {
Self {
sample_rate,
num_samples,
transport,
midi_cc_state: Some(midi_cc_state),
}
}
#[inline]
pub fn with_empty_transport(sample_rate: f64, num_samples: usize) -> Self {
Self {
sample_rate,
num_samples,
transport: Transport::default(),
midi_cc_state: None,
}
}
#[inline]
pub fn midi_cc(&self) -> Option<&MidiCcState> {
self.midi_cc_state
}
#[inline]
pub fn buffer_duration(&self) -> f64 {
self.num_samples as f64 / self.sample_rate
}
#[inline]
pub fn samples_per_beat(&self) -> Option<f64> {
self.transport
.tempo
.map(|tempo| self.sample_rate * 60.0 / tempo)
}
}
impl Default for ProcessContext<'_> {
fn default() -> Self {
Self {
sample_rate: 44100.0,
num_samples: 0,
transport: Transport::default(),
midi_cc_state: None,
}
}
}