From 85e243f78260e2a94009fbfa13152f99e867e6b0 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 26 Oct 2024 16:50:54 +0300 Subject: [PATCH] extract TransportClock --- crates/tek_core/src/time.rs | 77 +++---------- crates/tek_sequencer/src/sequencer_tui.rs | 4 +- crates/tek_sequencer/src/transport.rs | 129 ++++++++++++---------- crates/tek_sequencer/src/transport_cmd.rs | 24 ++-- crates/tek_sequencer/src/transport_snd.rs | 20 ++-- crates/tek_sequencer/src/transport_tui.rs | 21 ++-- 6 files changed, 130 insertions(+), 145 deletions(-) diff --git a/crates/tek_core/src/time.rs b/crates/tek_core/src/time.rs index 97dd2694..200f498b 100644 --- a/crates/tek_core/src/time.rs +++ b/crates/tek_core/src/time.rs @@ -103,6 +103,16 @@ pub trait UsecPosition { fn set_usec (&self, usec: T); } +pub trait LaunchSync { + fn sync (&self) -> T; + fn set_sync (&self, sync: T); +} + +pub trait Quantize { + 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 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, - /// 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 for TransportTime { - #[inline] fn sr (&self) -> f64 { self.timebase.sr() } - #[inline] fn set_sr (&self, sr: f64) { self.timebase.set_sr(sr); } -} -impl BeatsPerMinute for TransportTime { - #[inline] fn bpm (&self) -> f64 { self.timebase.bpm() } - #[inline] fn set_bpm (&self, bpm: f64) { self.timebase.set_bpm(bpm); } -} -impl PulsesPerQuaver 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 { - fn sync (&self) -> T; - fn set_sync (&self, sync: T) -> T; -} - -pub trait Quantize { - 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 } } "" } diff --git a/crates/tek_sequencer/src/sequencer_tui.rs b/crates/tek_sequencer/src/sequencer_tui.rs index a18f120a..84008d8d 100644 --- a/crates/tek_sequencer/src/sequencer_tui.rs +++ b/crates/tek_sequencer/src/sequencer_tui.rs @@ -142,7 +142,7 @@ impl Content for PhraseEditor { 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 { ); 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), diff --git a/crates/tek_sequencer/src/transport.rs b/crates/tek_sequencer/src/transport.rs index 8bea0dc2..7885bf6d 100644 --- a/crates/tek_sequencer/src/transport.rs +++ b/crates/tek_sequencer/src/transport.rs @@ -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>, + /// 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 { _engine: PhantomData, + /// Current sample rate, tempo, and PPQ. + pub clock: Arc, /// Enable metronome? pub metronome: bool, - /// Current sample rate, tempo, and PPQ. - pub timebase: Arc, /// JACK client handle (needs to not be dropped for standalone mode to work). pub jack: Option, /// JACK transport handle. @@ -17,57 +33,76 @@ pub struct TransportToolbar { pub focused: bool, /// Which part of the toolbar is focused pub focus: TransportToolbarFocus, - /// Playback state - pub playing: Option, - /// 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 for TransportTime { + #[inline] fn sr (&self) -> f64 { self.timebase.sr() } + #[inline] fn set_sr (&self, sr: f64) { self.timebase.set_sr(sr); } +} +impl BeatsPerMinute for TransportTime { + #[inline] fn bpm (&self) -> f64 { self.timebase.bpm() } + #[inline] fn set_bpm (&self, bpm: f64) { self.timebase.set_bpm(bpm); } +} +impl PulsesPerQuaver 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 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 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 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 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 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 TransportToolbar { pub fn new (transport: Option) -> 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 TransportToolbar { 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 { diff --git a/crates/tek_sequencer/src/transport_cmd.rs b/crates/tek_sequencer/src/transport_cmd.rs index 69a99282..0ef3058f 100644 --- a/crates/tek_sequencer/src/transport_cmd.rs +++ b/crates/tek_sequencer/src/transport_cmd.rs @@ -25,26 +25,34 @@ impl TransportToolbar { } fn handle_bpm (&mut self, from: &TuiInput) -> Perhaps { 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 { 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 { 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)) diff --git a/crates/tek_sequencer/src/transport_snd.rs b/crates/tek_sequencer/src/transport_snd.rs index 12b1ed30..8c8cbb1a 100644 --- a/crates/tek_sequencer/src/transport_snd.rs +++ b/crates/tek_sequencer/src/transport_snd.rs @@ -11,9 +11,9 @@ impl TransportToolbar { 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 TransportToolbar { }, _ => {} } - } - 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, diff --git a/crates/tek_sequencer/src/transport_tui.rs b/crates/tek_sequencer/src/transport_tui.rs index e011b7b1..eca580f0 100644 --- a/crates/tek_sequencer/src/transport_tui.rs +++ b/crates/tek_sequencer/src/transport_tui.rs @@ -4,13 +4,13 @@ impl Content for TransportToolbar { fn content (&self) -> impl Widget { 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 { 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);