From b6da43e93ebd8ff62f2c171aa4f55595ccadaef4 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 10 Aug 2024 12:06:20 +0300 Subject: [PATCH] transport: some standalone functioning --- crates/tek_timer/Cargo.toml | 2 +- crates/tek_timer/src/lib.rs | 5 +- crates/tek_timer/src/main.rs | 17 --- crates/tek_timer/src/transport.rs | 167 +++++------------------ crates/tek_timer/src/transport_handle.rs | 51 +++++++ crates/tek_timer/src/transport_main.rs | 6 + crates/tek_timer/src/transport_render.rs | 81 +++++++++++ 7 files changed, 179 insertions(+), 150 deletions(-) delete mode 100644 crates/tek_timer/src/main.rs create mode 100644 crates/tek_timer/src/transport_handle.rs create mode 100644 crates/tek_timer/src/transport_main.rs create mode 100644 crates/tek_timer/src/transport_render.rs diff --git a/crates/tek_timer/Cargo.toml b/crates/tek_timer/Cargo.toml index c00587d4..25752403 100644 --- a/crates/tek_timer/Cargo.toml +++ b/crates/tek_timer/Cargo.toml @@ -13,4 +13,4 @@ path = "src/lib.rs" [[bin]] name = "tek_timer" -path = "src/main.rs" +path = "src/transport_main.rs" diff --git a/crates/tek_timer/src/lib.rs b/crates/tek_timer/src/lib.rs index 02302250..ace16f86 100644 --- a/crates/tek_timer/src/lib.rs +++ b/crates/tek_timer/src/lib.rs @@ -1,7 +1,6 @@ pub(crate) use tek_core::*; -pub(crate) use tek_core::ratatui::prelude::*; pub(crate) use tek_core::crossterm::event::{KeyCode, KeyModifiers}; -pub(crate) use tek_jack::jack::*; +pub(crate) use tek_jack::{*, jack::*}; pub(crate) use std::sync::{Arc, atomic::Ordering}; pub(crate) use atomic_float::AtomicF64; submod! { @@ -9,6 +8,8 @@ submod! { ticks transport transport_focus + transport_handle + transport_render } /// (pulses, name) diff --git a/crates/tek_timer/src/main.rs b/crates/tek_timer/src/main.rs deleted file mode 100644 index 712f90b2..00000000 --- a/crates/tek_timer/src/main.rs +++ /dev/null @@ -1,17 +0,0 @@ -pub(crate) use tek_core::*; -pub(crate) use tek_core::ratatui::prelude::*; -pub(crate) use tek_core::crossterm::event::{KeyCode, KeyModifiers}; -pub(crate) use tek_jack::jack::*; -pub(crate) use std::sync::{Arc, atomic::Ordering}; -pub(crate) use atomic_float::AtomicF64; -submod! { - timebase - ticks - transport - transport_focus -} -/// Application entrypoint. -pub fn main () -> Usually<()> { - run(Arc::new(RwLock::new(TransportToolbar::standalone())))?; - Ok(()) -} diff --git a/crates/tek_timer/src/transport.rs b/crates/tek_timer/src/transport.rs index 67db33a4..f615d994 100644 --- a/crates/tek_timer/src/transport.rs +++ b/crates/tek_timer/src/transport.rs @@ -9,8 +9,10 @@ pub struct TransportToolbar { pub selected: TransportFocus, /// 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. - transport: Option, + pub transport: Option, /// Quantization factor pub quant: usize, /// Global sync quant @@ -18,15 +20,38 @@ pub struct TransportToolbar { /// Current transport state pub playing: Option, /// Current position according to transport - playhead: usize, + pub playhead: usize, /// Global frame and usec at which playback started pub started: Option<(usize, usize)>, } +process!(TransportToolbar |self, _client, scope| { + self.update(&scope); + Control::Continue +}); + impl TransportToolbar { - pub fn standalone () -> Self { - Self::new(None) + + pub fn standalone () -> Usually>> { + let mut transport = Self::new(None); + transport.focused = true; + transport.entered = true; + let jack = JackClient::Inactive( + Client::new("tek_transport", ClientOptions::NO_START_SERVER)?.0 + ); + transport.transport = Some(jack.transport()); + let transport = Arc::new(RwLock::new(transport)); + transport.write().unwrap().jack = Some( + jack.activate( + &transport.clone(), + |state, client, scope| { + state.write().unwrap().process(client, scope) + } + )? + ); + Ok(transport) } + pub fn new (transport: Option) -> Self { let timebase = Arc::new(Timebase::default()); Self { @@ -39,10 +64,12 @@ impl TransportToolbar { started: None, quant: 24, sync: timebase.ppq() as usize * 4, + jack: None, transport, timebase, } } + pub fn toggle_play (&mut self) -> Usually<()> { self.playing = match self.playing.expect("1st frame has not been processed yet") { TransportState::Stopped => { @@ -57,6 +84,7 @@ impl TransportToolbar { }; Ok(()) } + pub fn update (&mut self, scope: &ProcessScope) -> (bool, usize, usize, usize, usize, f64) { let CycleTimes { current_frames, @@ -93,142 +121,21 @@ impl TransportToolbar { period_usecs as f64 ) } + 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.frame_to_pulse(self.playhead as f64) as usize } + pub fn usecs (&self) -> usize { self.timebase.frame_to_usec(self.playhead as f64) as usize } -} - -render!(TransportToolbar |self, buf, area| { - let mut area = area; - area.height = 2; - let gray = Style::default().gray(); - let not_dim = Style::default().not_dim(); - let not_dim_bold = not_dim.bold(); - let corners = Corners(Style::default().green().not_dim()); - let ppq = self.ppq(); - let bpm = self.bpm(); - let pulse = self.pulse(); - let usecs = self.usecs(); - let Self { quant, sync, focused, entered, .. } = self; - fill_bg(buf, area, Nord::bg_lo(*focused, *entered)); - Split::right([ - - // Play/Pause button - &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ - let style = Some(match self.playing { - Some(TransportState::Stopped) => gray.dim().bold(), - Some(TransportState::Starting) => gray.not_dim().bold(), - Some(TransportState::Rolling) => gray.not_dim().white().bold(), - _ => unreachable!(), - }); - let label = match self.playing { - Some(TransportState::Rolling) => "▶ PLAYING", - Some(TransportState::Starting) => "READY ...", - Some(TransportState::Stopped) => "⏹ STOPPED", - _ => unreachable!(), - }; - let mut result = label.blit(buf, x + 1, y, style)?; - result.width = result.width + 1; - Ok(result) - }, - - // Beats per minute - &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ - "BPM".blit(buf, x, y, Some(not_dim))?; - let width = format!("{}.{:03}", bpm, bpm % 1).blit(buf, x, y + 1, Some(not_dim_bold))?.width; - let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; - if self.focused && self.entered && self.selected == TransportFocus::BPM { - corners.draw(buf, Rect { x: area.x - 1, ..area })?; - } - Ok(area) - }, - - // Quantization - &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ - "QUANT".blit(buf, x, y, Some(not_dim))?; - let width = ppq_to_name(*quant as usize).blit(buf, x, y + 1, Some(not_dim_bold))?.width; - let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; - if self.focused && self.entered && self.selected == TransportFocus::Quant { - corners.draw(buf, Rect { x: area.x - 1, ..area })?; - } - Ok(area) - }, - - // Clip launch sync - &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ - "SYNC".blit(buf, x, y, Some(not_dim))?; - let width = ppq_to_name(*sync as usize).blit(buf, x, y + 1, Some(not_dim_bold))?.width; - let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; - if self.focused && self.entered && self.selected == TransportFocus::Sync { - corners.draw(buf, Rect { x: area.x - 1, ..area })?; - } - Ok(area) - }, - - // Clock - &|buf: &mut Buffer, Rect { x, y, width, .. }: Rect|{ - let (beats, pulses) = (pulse / ppq, pulse % ppq); - let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1); - let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); - let (minutes, seconds) = (seconds / 60, seconds % 60); - let timer = format!("{minutes}:{seconds:02}:{msecs:03} {bars}.{beats}.{pulses:02}"); - timer.blit(buf, x + width - timer.len() as u16 - 1, y, Some(not_dim)) - } - - ]).render(buf, area) -}); - -/// Key bindings for transport toolbar. -pub const KEYMAP_TRANSPORT: &'static [KeyBinding] = keymap!(TransportToolbar { - [Left, NONE, "transport_prev", "select previous control", |transport: &mut TransportToolbar| Ok({ - transport.selected.prev(); - true - })], - [Right, NONE, "transport_next", "select next control", |transport: &mut TransportToolbar| Ok({ - transport.selected.next(); - true - })], - [Char('.'), NONE, "transport_increment", "increment value at cursor", |transport: &mut TransportToolbar| { - match transport.selected { - TransportFocus::BPM => { - transport.timebase.bpm.fetch_add(1.0, Ordering::Relaxed); - }, - TransportFocus::Quant => { - transport.quant = next_note_length(transport.quant) - }, - TransportFocus::Sync => { - transport.sync = next_note_length(transport.sync) - }, - }; - Ok(true) - }], - [Char(','), NONE, "transport_decrement", "decrement value at cursor", |transport: &mut TransportToolbar| { - match transport.selected { - TransportFocus::BPM => { - transport.timebase.bpm.fetch_sub(1.0, Ordering::Relaxed); - }, - TransportFocus::Quant => { - transport.quant = prev_note_length(transport.quant); - }, - TransportFocus::Sync => { - transport.sync = prev_note_length(transport.sync); - }, - }; - Ok(true) - }], -}); - -handle!{ - TransportToolbar |self, e| { - handle_keymap(self, e, KEYMAP_TRANSPORT) - } + } diff --git a/crates/tek_timer/src/transport_handle.rs b/crates/tek_timer/src/transport_handle.rs new file mode 100644 index 00000000..eab5f021 --- /dev/null +++ b/crates/tek_timer/src/transport_handle.rs @@ -0,0 +1,51 @@ +use crate::*; + +handle!{ + TransportToolbar |self, e| { + handle_keymap(self, e, KEYMAP_TRANSPORT) + } +} + +/// Key bindings for transport toolbar. +pub const KEYMAP_TRANSPORT: &'static [KeyBinding] = keymap!(TransportToolbar { + [Left, NONE, "transport_prev", "select previous control", |transport: &mut TransportToolbar| Ok({ + transport.selected.prev(); + true + })], + [Right, NONE, "transport_next", "select next control", |transport: &mut TransportToolbar| Ok({ + transport.selected.next(); + true + })], + [Char('.'), NONE, "transport_increment", "increment value at cursor", |transport: &mut TransportToolbar| { + match transport.selected { + TransportFocus::BPM => { + transport.timebase.bpm.fetch_add(1.0, Ordering::Relaxed); + }, + TransportFocus::Quant => { + transport.quant = next_note_length(transport.quant) + }, + TransportFocus::Sync => { + transport.sync = next_note_length(transport.sync) + }, + }; + Ok(true) + }], + [Char(','), NONE, "transport_decrement", "decrement value at cursor", |transport: &mut TransportToolbar| { + match transport.selected { + TransportFocus::BPM => { + transport.timebase.bpm.fetch_sub(1.0, Ordering::Relaxed); + }, + TransportFocus::Quant => { + transport.quant = prev_note_length(transport.quant); + }, + TransportFocus::Sync => { + transport.sync = prev_note_length(transport.sync); + }, + }; + Ok(true) + }], + [Char(' '), NONE, "transport_play_toggle", "play or pause", |transport: &mut TransportToolbar| { + transport.toggle_play()?; + Ok(true) + }], +}); diff --git a/crates/tek_timer/src/transport_main.rs b/crates/tek_timer/src/transport_main.rs new file mode 100644 index 00000000..d5118d75 --- /dev/null +++ b/crates/tek_timer/src/transport_main.rs @@ -0,0 +1,6 @@ +include!("lib.rs"); +/// Application entrypoint. +pub fn main () -> Usually<()> { + run(TransportToolbar::standalone()?)?; + Ok(()) +} diff --git a/crates/tek_timer/src/transport_render.rs b/crates/tek_timer/src/transport_render.rs new file mode 100644 index 00000000..5e7c2433 --- /dev/null +++ b/crates/tek_timer/src/transport_render.rs @@ -0,0 +1,81 @@ +use crate::*; + +render!(TransportToolbar |self, buf, area| { + let mut area = area; + area.height = 2; + let gray = Style::default().gray(); + let not_dim = Style::default().not_dim(); + let not_dim_bold = not_dim.bold(); + let corners = Corners(Style::default().green().not_dim()); + let ppq = self.ppq(); + let bpm = self.bpm(); + let pulse = self.pulse(); + let usecs = self.usecs(); + let Self { quant, sync, focused, entered, .. } = self; + fill_bg(buf, area, Nord::bg_lo(*focused, *entered)); + Split::right([ + + // Play/Pause button + &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ + let style = Some(match self.playing { + Some(TransportState::Stopped) => gray.dim().bold(), + Some(TransportState::Starting) => gray.not_dim().bold(), + Some(TransportState::Rolling) => gray.not_dim().white().bold(), + _ => unreachable!(), + }); + let label = match self.playing { + Some(TransportState::Rolling) => "▶ PLAYING", + Some(TransportState::Starting) => "READY ...", + Some(TransportState::Stopped) => "⏹ STOPPED", + _ => unreachable!(), + }; + let mut result = label.blit(buf, x + 1, y, style)?; + result.width = result.width + 1; + Ok(result) + }, + + // Beats per minute + &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ + "BPM".blit(buf, x, y, Some(not_dim))?; + let width = format!("{}.{:03}", bpm, bpm % 1).blit(buf, x, y + 1, Some(not_dim_bold))?.width; + let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; + if self.focused && self.entered && self.selected == TransportFocus::BPM { + corners.draw(buf, Rect { x: area.x - 1, ..area })?; + } + Ok(area) + }, + + // Quantization + &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ + "QUANT".blit(buf, x, y, Some(not_dim))?; + let width = ppq_to_name(*quant as usize).blit(buf, x, y + 1, Some(not_dim_bold))?.width; + let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; + if self.focused && self.entered && self.selected == TransportFocus::Quant { + corners.draw(buf, Rect { x: area.x - 1, ..area })?; + } + Ok(area) + }, + + // Clip launch sync + &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ + "SYNC".blit(buf, x, y, Some(not_dim))?; + let width = ppq_to_name(*sync as usize).blit(buf, x, y + 1, Some(not_dim_bold))?.width; + let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; + if self.focused && self.entered && self.selected == TransportFocus::Sync { + corners.draw(buf, Rect { x: area.x - 1, ..area })?; + } + Ok(area) + }, + + // Clock + &|buf: &mut Buffer, Rect { x, y, width, .. }: Rect|{ + let (beats, pulses) = (pulse / ppq, pulse % ppq); + let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1); + let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); + let (minutes, seconds) = (seconds / 60, seconds % 60); + let timer = format!("{minutes}:{seconds:02}:{msecs:03} {bars}.{beats}.{pulses:02}"); + timer.blit(buf, x + width - timer.len() as u16 - 1, y, Some(not_dim)) + } + + ]).render(buf, area) +});