extract TransportClock

This commit is contained in:
🪞👃🪞 2024-10-26 16:50:54 +03:00
parent ccc74fd743
commit 85e243f782
6 changed files with 130 additions and 145 deletions

View file

@ -103,6 +103,16 @@ pub trait UsecPosition<T> {
fn set_usec (&self, usec: T);
}
pub trait LaunchSync<T> {
fn sync (&self) -> T;
fn set_sync (&self, sync: T);
}
pub trait Quantize<T> {
fn quant (&self) -> T;
fn set_quant (&self, quant: T);
}
#[derive(Debug)]
/// Keeps track of global time units.
pub struct Timebase {
@ -133,47 +143,6 @@ impl PulsesPerQuaver<f64> for Timebase {
#[inline] fn set_ppq (&self, ppq: f64) { self.ppq.store(ppq, Ordering::Relaxed); }
}
#[derive(Debug)]
pub struct TransportTime {
/// Current sample sr, tempo, and PPQ.
pub timebase: Arc<Timebase>,
/// Note quantization factor
pub quant: usize,
/// Launch quantization factor
pub sync: usize,
/// Current time in frames
pub frame: usize,
/// Current time in pulses
pub pulse: usize,
/// Current time in microseconds
pub usecs: usize,
/// Pulses per quarter note
pub ppq: usize,
}
impl SampleRate<f64> for TransportTime {
#[inline] fn sr (&self) -> f64 { self.timebase.sr() }
#[inline] fn set_sr (&self, sr: f64) { self.timebase.set_sr(sr); }
}
impl BeatsPerMinute<f64> for TransportTime {
#[inline] fn bpm (&self) -> f64 { self.timebase.bpm() }
#[inline] fn set_bpm (&self, bpm: f64) { self.timebase.set_bpm(bpm); }
}
impl PulsesPerQuaver<f64> for TransportTime {
const DEFAULT_PPQ: f64 = Timebase::DEFAULT_PPQ;
#[inline] fn ppq (&self) -> f64 { self.timebase.ppq() }
#[inline] fn set_ppq (&self, ppq: f64) { self.timebase.set_ppq(ppq); }
}
pub trait LaunchSync<T> {
fn sync (&self) -> T;
fn set_sync (&self, sync: T) -> T;
}
pub trait Quantize<T> {
fn quant (&self) -> T;
fn set_quant (&self, quant: T);
}
/// (pulses, name)
pub const NOTE_DURATIONS: [(usize, &str);26] = [
(1, "1/384"),
@ -205,32 +174,22 @@ pub const NOTE_DURATIONS: [(usize, &str);26] = [
];
/// Returns the next shorter length
pub fn prev_note_length (ppq: usize) -> usize {
pub fn prev_note_length (pulses: usize) -> usize {
for i in 1..=16 {
let length = NOTE_DURATIONS[16-i].0;
if length < ppq {
return length
}
if length < pulses { return length }
}
ppq
pulses
}
/// Returns the next longer length
pub fn next_note_length (ppq: usize) -> usize {
for (length, _) in &NOTE_DURATIONS {
if *length > ppq {
return *length
}
}
ppq
pub fn next_note_length (pulses: usize) -> usize {
for (length, _) in &NOTE_DURATIONS { if *length > pulses { return *length } }
pulses
}
pub fn ppq_to_name (ppq: usize) -> &'static str {
for (length, name) in &NOTE_DURATIONS {
if *length == ppq {
return name
}
}
pub fn pulses_to_name (pulses: usize) -> &'static str {
for (length, name) in &NOTE_DURATIONS { if *length == pulses { return name } }
""
}

View file

@ -142,7 +142,7 @@ impl Content for PhraseEditor<Tui> {
upper_left = format!("{upper_left}: {}", phrase.read().unwrap().name);
}
let mut upper_right = format!("Zoom: {} (+{}:{}*{}|{})",
ppq_to_name(time_scale),
pulses_to_name(time_scale),
time_start,
time_point.unwrap_or(0),
time_scale,
@ -150,7 +150,7 @@ impl Content for PhraseEditor<Tui> {
);
if *focused && *entered {
upper_right = format!("Note: {} (+{}:{}|{}) {upper_right}",
ppq_to_name(*note_len),
pulses_to_name(*note_len),
note_start,
note_point.unwrap_or(0),
note_clamp.unwrap_or(0),

View file

@ -1,12 +1,28 @@
use crate::*;
#[derive(Debug)]
pub struct TransportTime {
/// Current sample sr, tempo, and PPQ.
pub timebase: Timebase,
/// Playback state
pub playing: RwLock<Option<TransportState>>,
/// Current time in frames
pub frame: AtomicUsize,
/// Current time in pulses
pub pulse: AtomicUsize,
/// Current time in microseconds
pub usecs: AtomicUsize,
/// Note quantization factor
pub quant: AtomicUsize,
/// Launch quantization factor
pub sync: AtomicUsize,
}
/// Stores and displays time-related state.
pub struct TransportToolbar<E: Engine> {
_engine: PhantomData<E>,
/// Current sample rate, tempo, and PPQ.
pub clock: Arc<TransportTime>,
/// Enable metronome?
pub metronome: bool,
/// Current sample rate, tempo, and PPQ.
pub timebase: Arc<Timebase>,
/// JACK client handle (needs to not be dropped for standalone mode to work).
pub jack: Option<JackClient>,
/// JACK transport handle.
@ -17,57 +33,76 @@ pub struct TransportToolbar<E: Engine> {
pub focused: bool,
/// Which part of the toolbar is focused
pub focus: TransportToolbarFocus,
/// Playback state
pub playing: Option<TransportState>,
/// Current tempo
pub bpm: f64,
/// Quantization factor
pub quant: usize,
/// Launch sync
pub sync: usize,
/// Current time in frames
pub frame: usize,
/// Current time in pulses
pub pulse: usize,
/// Current time in microseconds
pub usecs: usize,
/// Pulses per quarter note
pub ppq: usize,
}
/// Which item of the transport toolbar is focused
#[derive(Clone, Copy, PartialEq)]
pub enum TransportToolbarFocus {
Bpm,
Sync,
PlayPause,
Clock,
Quant,
}
pub enum TransportToolbarFocus { Bpm, Sync, PlayPause, Clock, Quant, }
impl SampleRate<f64> for TransportTime {
#[inline] fn sr (&self) -> f64 { self.timebase.sr() }
#[inline] fn set_sr (&self, sr: f64) { self.timebase.set_sr(sr); }
}
impl BeatsPerMinute<f64> for TransportTime {
#[inline] fn bpm (&self) -> f64 { self.timebase.bpm() }
#[inline] fn set_bpm (&self, bpm: f64) { self.timebase.set_bpm(bpm); }
}
impl PulsesPerQuaver<f64> for TransportTime {
const DEFAULT_PPQ: f64 = Timebase::DEFAULT_PPQ;
#[inline] fn ppq (&self) -> f64 { self.timebase.ppq() }
#[inline] fn set_ppq (&self, ppq: f64) { self.timebase.set_ppq(ppq); }
}
impl FramePosition<usize> for TransportTime {
#[inline] fn frame (&self) -> usize { self.frame.load(Ordering::Relaxed) }
#[inline] fn set_frame (&self, frame: usize) { self.frame.store(frame, Ordering::Relaxed); }
}
impl UsecPosition<usize> for TransportTime {
#[inline] fn usec (&self) -> usize { self.usecs.load(Ordering::Relaxed) }
#[inline] fn set_usec (&self, usec: usize) { self.usecs.store(usec, Ordering::Relaxed); }
}
impl PulsePosition<usize> for TransportTime {
#[inline] fn pulse (&self) -> usize { self.pulse.load(Ordering::Relaxed) }
#[inline] fn set_pulse (&self, usec: usize) { self.pulse.store(usec, Ordering::Relaxed); }
}
impl Quantize<usize> for TransportTime {
#[inline] fn quant (&self) -> usize { self.quant.load(Ordering::Relaxed) }
#[inline] fn set_quant (&self, quant: usize) { self.quant.store(quant, Ordering::Relaxed); }
}
impl LaunchSync<usize> for TransportTime {
#[inline] fn sync (&self) -> usize { self.sync.load(Ordering::Relaxed) }
#[inline] fn set_sync (&self, sync: usize) { self.sync.store(sync, Ordering::Relaxed); }
}
impl<E: Engine> TransportToolbar<E> {
pub fn new (transport: Option<Transport>) -> Self {
let timebase = Arc::new(Timebase::default());
let timebase = Timebase::default();
Self {
_engine: Default::default(),
focused: false,
focus: TransportToolbarFocus::PlayPause,
playing: Some(TransportState::Stopped),
bpm: timebase.bpm(),
quant: 24,
sync: timebase.ppq() as usize * 4,
frame: 0,
pulse: 0,
ppq: 0,
usecs: 0,
metronome: false,
started: None,
jack: None,
transport,
timebase,
clock: Arc::new(TransportTime {
playing: Some(TransportState::Stopped).into(),
quant: 24.into(),
sync: (timebase.ppq() as usize * 4).into(),
frame: 0.into(),
pulse: 0.into(),
usecs: 0.into(),
timebase,
})
}
}
pub fn bpm (&self) -> usize { self.clock.bpm() as usize }
pub fn ppq (&self) -> usize { self.clock.ppq() as usize }
pub fn pulse (&self) -> usize { self.clock.samples_to_pulse(self.clock.frame()as f64) as usize }
pub fn usecs (&self) -> usize { self.clock.samples_to_usec(self.clock.frame() as f64) as usize }
pub fn quant (&self) -> usize { self.clock.quant() }
pub fn sync (&self) -> usize { self.clock.sync() }
pub fn toggle_play (&mut self) -> Usually<()> {
let transport = self.transport.as_ref().unwrap();
self.playing = match self.playing.expect("1st frame has not been processed yet") {
let playing = self.clock.playing.read().unwrap().expect("1st frame has not been processed yet");
let playing = match playing {
TransportState::Stopped => {
transport.start()?;
Some(TransportState::Starting)
@ -78,28 +113,10 @@ impl<E: Engine> TransportToolbar<E> {
Some(TransportState::Stopped)
},
};
*self.clock.playing.write().unwrap() = playing;
Ok(())
}
pub fn bpm (&self) -> usize {
self.timebase.bpm() as usize
}
pub fn ppq (&self) -> usize {
self.timebase.ppq() as usize
}
pub fn pulse (&self) -> usize {
self.timebase.samples_to_pulse(self.frame as f64) as usize
}
pub fn usecs (&self) -> usize {
self.timebase.samples_to_usec(self.frame as f64) as usize
}
pub fn quant (&self) -> usize {
self.quant
}
pub fn sync (&self) -> usize {
self.sync
}
}
impl TransportToolbarFocus {
pub fn next (&mut self) {
*self = match self {

View file

@ -25,26 +25,34 @@ impl TransportToolbar<Tui> {
}
fn handle_bpm (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() {
key!(KeyCode::Char(',')) => { self.bpm -= 1.0; },
key!(KeyCode::Char('.')) => { self.bpm += 1.0; },
key!(KeyCode::Char('<')) => { self.bpm -= 0.001; },
key!(KeyCode::Char('>')) => { self.bpm += 0.001; },
key!(KeyCode::Char(',')) => { self.clock.set_bpm(self.clock.bpm() - 1.0); },
key!(KeyCode::Char('.')) => { self.clock.set_bpm(self.clock.bpm() + 1.0); },
key!(KeyCode::Char('<')) => { self.clock.set_bpm(self.clock.bpm() - 0.001); },
key!(KeyCode::Char('>')) => { self.clock.set_bpm(self.clock.bpm() + 0.001); },
_ => return Ok(None)
}
Ok(Some(true))
}
fn handle_quant (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() {
key!(KeyCode::Char(',')) => { self.quant = prev_note_length(self.quant); },
key!(KeyCode::Char('.')) => { self.quant = next_note_length(self.quant); },
key!(KeyCode::Char(',')) => {
self.clock.set_quant(prev_note_length(self.clock.quant()));
},
key!(KeyCode::Char('.')) => {
self.clock.set_quant(next_note_length(self.clock.quant()));
},
_ => return Ok(None)
}
return Ok(Some(true))
}
fn handle_sync (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() {
key!(KeyCode::Char(',')) => { self.sync = prev_note_length(self.sync); },
key!(KeyCode::Char('.')) => { self.sync = next_note_length(self.sync); },
key!(KeyCode::Char(',')) => {
self.clock.set_quant(prev_note_length(self.clock.sync()));
},
key!(KeyCode::Char('.')) => {
self.clock.set_quant(next_note_length(self.clock.sync()));
},
_ => return Ok(None)
}
return Ok(Some(true))

View file

@ -11,9 +11,9 @@ impl<E: Engine> TransportToolbar<E> {
let CycleTimes { current_frames, current_usecs, next_usecs, period_usecs } = times;
let chunk_size = scope.n_frames() as usize;
let transport = self.transport.as_ref().unwrap().query().unwrap();
self.frame = transport.pos.frame() as usize;
self.clock.set_frame(transport.pos.frame() as usize);
let mut reset = false;
if self.playing != Some(transport.state) {
if *self.clock.playing.read().unwrap() != Some(transport.state) {
match transport.state {
TransportState::Rolling => {
self.started = Some((current_frames as usize, current_usecs as usize));
@ -24,19 +24,15 @@ impl<E: Engine> TransportToolbar<E> {
},
_ => {}
}
}
self.playing = Some(transport.state);
if self.playing == Some(TransportState::Stopped) {
};
*self.clock.playing.write().unwrap() = Some(transport.state);
if *self.clock.playing.read().unwrap() == Some(TransportState::Stopped) {
self.started = None;
}
match self.started {
Some((_, usecs)) => {
self.usecs = current_usecs as usize - usecs;
},
None => {
self.usecs = 0;
}
}
Some((_, usecs)) => { self.clock.set_usec(current_usecs as usize - usecs); },
None => { self.clock.set_usec(0); }
};
(
reset,
current_frames as usize,

View file

@ -4,13 +4,13 @@ impl Content for TransportToolbar<Tui> {
fn content (&self) -> impl Widget<Engine = Tui> {
lay!(
self.focus.wrap(self.focused, TransportToolbarFocus::PlayPause, &Styled(
match self.playing {
match *self.clock.playing.read().unwrap() {
Some(TransportState::Stopped) => Some(GRAY_DIM.bold()),
Some(TransportState::Starting) => Some(GRAY_NOT_DIM_BOLD),
Some(TransportState::Rolling) => Some(WHITE_NOT_DIM_BOLD),
_ => unreachable!(),
},
match self.playing {
match *self.clock.playing.read().unwrap() {
Some(TransportState::Rolling) => "▶ PLAYING",
Some(TransportState::Starting) => "READY ...",
Some(TransportState::Stopped) => "⏹ STOPPED",
@ -20,19 +20,24 @@ impl Content for TransportToolbar<Tui> {
row!(
self.focus.wrap(self.focused, TransportToolbarFocus::Bpm, &Outset::X(1u16, row! {
"BPM ", format!("{}.{:03}", self.bpm as usize, (self.bpm * 1000.0) % 1000.0)
"BPM ", format!("{}.{:03}",
self.clock.bpm() as usize,
(self.clock.bpm() * 1000.0) % 1000.0
)
})),
//self.focus.wrap(self.focused, TransportToolbarFocus::Quant, &Outset::X(1u16, row! {
//let quant = self.focus.wrap(self.focused, TransportToolbarFocus::Quant, &Outset::X(1u16, row! {
//"QUANT ", ppq_to_name(self.quant as usize)
//})),
self.focus.wrap(self.focused, TransportToolbarFocus::Sync, &Outset::X(1u16, row! {
"SYNC ", ppq_to_name(self.sync as usize)
})),
"SYNC ", pulses_to_name(self.sync() as usize)
}))
).align_w().fill_x(),
self.focus.wrap(self.focused, TransportToolbarFocus::Clock, &{
let Self { frame: _frame, pulse, ppq, usecs, .. } = self;
let (beats, pulses) = if *ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
let pulse = self.clock.pulse();
let ppq = self.clock.ppq() as usize;
let usecs = self.clock.usec();
let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1);
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
let (minutes, seconds) = (seconds / 60, seconds % 60);