diff --git a/crates/tek_api/src/api.rs b/crates/tek_api/src/api.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/crates/tek_api/src/arrange.rs b/crates/tek_api/src/arrange.rs index 50ac4684..1bed1b40 100644 --- a/crates/tek_api/src/arrange.rs +++ b/crates/tek_api/src/arrange.rs @@ -1,4 +1,4 @@ -use scene::*; +use crate::*; pub struct Arrangement { /// JACK client handle (needs to not be dropped for standalone mode to work). @@ -56,8 +56,8 @@ pub enum ArrangementCommand { Go(Direction), Edit(Option>>), Scene(SceneCommand), - Track(TrackCommand), - Clip(ClipCommand), + Track(ArrangementTrackCommand), + Clip(ArrangementClipCommand), } #[derive(Clone)] diff --git a/crates/tek_api/src/lib.rs b/crates/tek_api/src/lib.rs index 9a635c52..464637bc 100644 --- a/crates/tek_api/src/lib.rs +++ b/crates/tek_api/src/lib.rs @@ -1,4 +1,4 @@ -pub(crate) use tek_core::*; +pub use tek_core::*; pub(crate) use tek_core::midly::{*, live::LiveEvent, num::u7}; pub(crate) use std::thread::JoinHandle; pub(crate) use std::fmt::{Debug, Formatter, Error}; @@ -9,6 +9,8 @@ pub(crate) use tek_core::jack::{ }; submod! { + api_jack + arrange clock mixer phrase @@ -16,14 +18,13 @@ submod! { plugin_kind plugin_lv2 pool - sampler sample - scene scene_cmd + sampler + scene + scene_cmd sequencer track - transport transport_cmd + transport + transport_cmd voice - - api_cmd - api_jack } diff --git a/crates/tek_api/src/transport.rs b/crates/tek_api/src/transport.rs index 73372d7a..080c3ab4 100644 --- a/crates/tek_api/src/transport.rs +++ b/crates/tek_api/src/transport.rs @@ -10,6 +10,17 @@ pub struct Transport { pub metronome: bool, } +impl Debug for Transport { + fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { + f.debug_struct("transport") + .field("jack", &self.jack) + .field("transport", &"(JACK transport)") + .field("clock", &self.clock) + .field("metronome", &self.metronome) + .finish() + } +} + impl Audio for Transport { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { let times = scope.cycle_times().unwrap(); @@ -41,3 +52,22 @@ impl Audio for Transport { Control::Continue } } + +impl Transport { + pub fn toggle_play (&mut self) -> Usually<()> { + let playing = self.clock.playing.read().unwrap().expect("1st sample has not been processed yet"); + let playing = match playing { + TransportState::Stopped => { + self.transport.start()?; + Some(TransportState::Starting) + }, + _ => { + self.transport.stop()?; + self.transport.locate(0)?; + Some(TransportState::Stopped) + }, + }; + *self.clock.playing.write().unwrap() = playing; + Ok(()) + } +} diff --git a/crates/tek_cli/src/cli_arranger.rs b/crates/tek_cli/src/cli_arranger.rs index 340741ca..c69c6b08 100644 --- a/crates/tek_cli/src/cli_arranger.rs +++ b/crates/tek_cli/src/cli_arranger.rs @@ -20,7 +20,7 @@ impl ArrangerCli { /// Run the arranger TUI from CLI arguments. fn run (&self) -> Usually<()> { Tui::run(JackClient::new("tek_arranger")?.activate_with(|jack|{ - let transport = TransportToolbar::new(jack, None); + let transport = TransportView::new(jack, None); let phrases = Arc::new(RwLock::new(PhrasePool::new())); let mut arrangement = Arrangement::new(&jack, &transport.clock, "", &phrases); let transport = Arc::new(RwLock::new(transport)); @@ -43,7 +43,7 @@ impl ArrangerCli { Some(scene_color_1.mix(scene_color_2, i as f32 / self.scenes as f32)) )?; } - Ok(ArrangerApp::new( + Ok(ArrangerView::new( jack, self.transport.then_some(transport), arrangement, diff --git a/crates/tek_cli/src/cli_sequencer.rs b/crates/tek_cli/src/cli_sequencer.rs index cae72eba..2fef53e3 100644 --- a/crates/tek_cli/src/cli_sequencer.rs +++ b/crates/tek_cli/src/cli_sequencer.rs @@ -19,7 +19,7 @@ pub struct SequencerCli { impl SequencerCli { fn run (&self) -> Usually<()> { Tui::run(JackClient::new("tek_sequencer")?.activate_with(|jack|{ - let transport = TransportToolbar::new(jack, None); + let transport = TransportView::new(jack, None); if let Some(_) = self.name.as_ref() { // TODO: sequencer.name = Arc::new(RwLock::new(name.clone())); } @@ -31,7 +31,7 @@ impl SequencerCli { //phrase.write().unwrap().length = length; //} } - Ok(SequencerApp { + Ok(SequencerView { jack: jack.clone(), focus_cursor: (1, 1), entered: false, diff --git a/crates/tek_cli/src/cli_transport.rs b/crates/tek_cli/src/cli_transport.rs index 4ebde7b2..6296156b 100644 --- a/crates/tek_cli/src/cli_transport.rs +++ b/crates/tek_cli/src/cli_transport.rs @@ -2,7 +2,7 @@ include!("lib.rs"); /// Application entrypoint. pub fn main () -> Usually<()> { Tui::run(JackClient::new("tek_transport")?.activate_with(|jack|{ - let mut transport = TransportToolbarView::new(jack, None); + let mut transport = TransportView::new(jack, None); transport.focused = true; Ok(transport) })?)?; diff --git a/crates/tek_tui/src/lib.rs b/crates/tek_tui/src/lib.rs index 6f7e3b63..c281d39f 100644 --- a/crates/tek_tui/src/lib.rs +++ b/crates/tek_tui/src/lib.rs @@ -1,4 +1,4 @@ -pub(crate) use tek_core::*; +pub(crate) use tek_api::*; pub(crate) use tek_core::crossterm::event::{KeyCode, KeyModifiers}; pub(crate) use tek_core::midly::{num::u7, live::LiveEvent, MidiMessage}; pub(crate) use tek_core::jack::*; @@ -10,6 +10,7 @@ pub(crate) use std::ffi::OsString; pub(crate) use std::fs::read_dir; submod! { + tui_app tui_arrangement tui_arranger tui_arranger_bar @@ -37,5 +38,6 @@ submod! { tui_sequencer_cmd tui_sequencer_foc tui_transport + tui_transport_bar tui_transport_cmd } diff --git a/crates/tek_tui/src/tui_app.rs b/crates/tek_tui/src/tui_app.rs new file mode 100644 index 00000000..24e54882 --- /dev/null +++ b/crates/tek_tui/src/tui_app.rs @@ -0,0 +1,39 @@ +use crate::*; + +pub struct App +where + E: Engine, + T: Widget + Handle + Audio, + C: Command, + S: Widget +{ + state: T, + cursor: (usize, usize), + entered: bool, + menu_bar: Option>, + status_bar: Option + history: Vec, + size: Measure, +} + +impl App, TransportViewCommand, TransportStatusBar> { + fn new () -> Self { + Self { + state: TransportView { + _engine: Default::default(), + state: Transport { + jack: Default::default(), + transport: Default::default(), + clock: Default::default(), + metronome: false + } + }, + cursor: (0, 0), + entered: false, + menu_bar: None, + status_bar: None, + history: vec![], + size: Default::default(), + } + } +} diff --git a/crates/tek_tui/src/tui_arrangement.rs b/crates/tek_tui/src/tui_arrangement.rs index b80dde10..80f792e2 100644 --- a/crates/tek_tui/src/tui_arrangement.rs +++ b/crates/tek_tui/src/tui_arrangement.rs @@ -4,7 +4,7 @@ pub struct ArrangementEditor { /// Global JACK client pub jack: Arc>, /// Global timebase - pub clock: Arc, + pub clock: Arc, /// Name of arranger pub name: Arc>, /// Collection of phrases. diff --git a/crates/tek_tui/src/tui_arranger.rs b/crates/tek_tui/src/tui_arranger.rs index c01bfff3..0f1ce93d 100644 --- a/crates/tek_tui/src/tui_arranger.rs +++ b/crates/tek_tui/src/tui_arranger.rs @@ -1,38 +1,28 @@ use crate::*; /// Root level object for standalone `tek_arranger` -pub struct ArrangerApp { - /// JACK client handle (needs to not be dropped for standalone mode to work). - pub jack: Arc>, - /// Which view is focused - pub focus_cursor: (usize, usize), - /// Whether the focused view is entered - pub entered: bool, - /// Controls the JACK transport. - pub transport: Option>>>, - /// Global timebase - pub clock: Arc, +pub struct ArrangerView { + /// Sequencer component + pub sequencer: SequencerView, /// Contains all the sequencers. - pub arrangement: ArrangementEditor, - /// Pool of all phrases in the arrangement - pub phrases: Arc>>, - /// Phrase editor view - pub editor: PhraseEditor, + pub arrangement: ArrangementEditor, /// Status bar - pub status: ArrangerStatusBar, + pub status: ArrangerStatusBar, /// Height of arrangement - pub arrangement_split: u16, - /// Width of phrase pool - pub phrases_split: u16, + pub split: u16, /// Width and height of app at last render - pub size: Measure, + pub size: Measure, /// Menu bar - pub menu: MenuBar, + pub menu: MenuBar, /// Command history - pub history: Vec, + pub history: Vec, + /// Which view is focused + pub cursor: (usize, usize), + /// Whether the focused view is entered + pub entered: bool, } -impl Audio for ArrangerApp { +impl Audio for ArrangerView { fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { if let Some(ref transport) = self.transport { transport.write().unwrap().process(client, scope); @@ -63,7 +53,7 @@ impl Audio for ArrangerApp { } /// Layout for standalone arranger app. -impl Content for ArrangerApp { +impl Content for ArrangerView { type Engine = Tui; fn content (&self) -> impl Widget { let focused = self.arrangement.focused; @@ -103,7 +93,7 @@ impl Content for ArrangerApp { } /// General methods for arranger -impl ArrangerApp { +impl ArrangerView { pub fn new ( jack: &Arc>, transport: Option>>>, @@ -126,10 +116,10 @@ impl ArrangerApp { clock: if let Some(ref transport) = transport { transport.read().unwrap().clock.clone() } else { - Arc::new(TransportTime::default()) + Arc::new(Clock::default()) }, menu: { - use ArrangerAppCommand::*; + use ArrangerViewCommand::*; MenuBar::new() .add({ use ArrangementCommand::*; @@ -139,7 +129,7 @@ impl ArrangerApp { .cmd("s", "Save project", Arrangement(Save)) }) .add({ - use TransportAppCommand::*; + use TransportViewCommand::*; Menu::new("Transport") .cmd("p", "Play", Transport(Play)) .cmd("s", "Play from start", Transport(PlayFromStart)) @@ -188,7 +178,7 @@ impl ArrangerApp { //pub fn new ( //jack: &Arc>, - //clock: &Arc, + //clock: &Arc, //name: &str, //phrases: &Arc>> //) -> Self { @@ -234,7 +224,7 @@ impl ArrangerApp { self.arrangement.phrase_put(); } self.show_phrase(); - self.focus(ArrangerAppFocus::PhraseEditor); + self.focus(ArrangerViewFocus::PhraseEditor); self.editor.entered = true; } /// Rename the selected track, scene, or clip @@ -247,7 +237,7 @@ impl ArrangerApp { ArrangementEditorFocus::Clip(t, s) => if let Some(ref phrase) = scenes[s].clips[t] { let index = self.phrases.read().unwrap().index_of(&*phrase.read().unwrap()); if let Some(index) = index { - self.focus(ArrangerAppFocus::PhrasePool); + self.focus(ArrangerViewFocus::PhrasePool); self.phrases.write().unwrap().phrase = index; self.phrases.write().unwrap().begin_rename(); } @@ -257,15 +247,15 @@ impl ArrangerApp { /// Update status bar pub fn update_status (&mut self) { self.status = match self.focused() { - ArrangerAppFocus::Transport => ArrangerStatusBar::Transport, - ArrangerAppFocus::Arrangement => match self.arrangement.selected { + ArrangerViewFocus::Transport => ArrangerStatusBar::Transport, + ArrangerViewFocus::Arrangement => match self.arrangement.selected { ArrangementEditorFocus::Mix => ArrangerStatusBar::ArrangementMix, ArrangementEditorFocus::Track(_) => ArrangerStatusBar::ArrangementTrack, ArrangementEditorFocus::Scene(_) => ArrangerStatusBar::ArrangementScene, ArrangementEditorFocus::Clip(_, _) => ArrangerStatusBar::ArrangementClip, }, - ArrangerAppFocus::PhrasePool => ArrangerStatusBar::PhrasePool, - ArrangerAppFocus::PhraseEditor => match self.editor.entered { + ArrangerViewFocus::PhrasePool => ArrangerStatusBar::PhrasePool, + ArrangerViewFocus::PhraseEditor => match self.editor.entered { true => ArrangerStatusBar::PhraseEdit, false => ArrangerStatusBar::PhraseView, }, @@ -563,7 +553,7 @@ impl Arrangement { impl ArrangementTrack { pub fn new ( jack: &Arc>, - clock: &Arc, + clock: &Arc, name: &str, color: Option ) -> Usually { diff --git a/crates/tek_tui/src/tui_arranger_cmd.rs b/crates/tek_tui/src/tui_arranger_cmd.rs index bc9b1910..ab90ee65 100644 --- a/crates/tek_tui/src/tui_arranger_cmd.rs +++ b/crates/tek_tui/src/tui_arranger_cmd.rs @@ -1,7 +1,7 @@ use crate::*; #[derive(Clone)] -pub enum ArrangerAppCommand { +pub enum ArrangerViewCommand { Focus(FocusCommand), Transport(TransportCommand), Phrases(PhrasePoolCommand), @@ -11,10 +11,10 @@ pub enum ArrangerAppCommand { } /// Handle top-level events in standalone arranger. -impl Handle for ArrangerApp { +impl Handle for ArrangerView { fn handle (&mut self, i: &TuiInput) -> Perhaps { if let Some(entered) = self.entered() { - use ArrangerAppFocus::*; + use ArrangerViewFocus::*; if let Some(true) = match entered { Transport => self.transport.as_mut().map(|t|t.handle(i)).transpose()?.flatten(), Arrangement => self.arrangement.handle(i)?, @@ -24,14 +24,14 @@ impl Handle for ArrangerApp { return Ok(Some(true)) } } - ArrangerAppCommand::execute_with_state(self, i) + ArrangerViewCommand::execute_with_state(self, i) } } -impl InputToCommand> for ArrangerAppCommand { - fn input_to_command (state: &ArrangerApp, input: &TuiInput) -> Option { +impl InputToCommand> for ArrangerViewCommand { + fn input_to_command (state: &ArrangerView, input: &TuiInput) -> Option { use FocusCommand::*; - use ArrangerAppCommand::*; + use ArrangerViewCommand::*; match input.event() { key!(KeyCode::Tab) => Some(Focus(Next)), key!(Shift-KeyCode::Tab) => Some(Focus(Prev)), @@ -45,11 +45,11 @@ impl InputToCommand> for ArrangerAppCommand { key!(KeyCode::Esc) => Some(Focus(Exit)), key!(KeyCode::Char(' ')) => Some(Transport(TransportCommand::PlayToggle)), _ => match state.focused() { - ArrangerAppFocus::Transport => state.transport.as_ref() + ArrangerViewFocus::Transport => state.transport.as_ref() .map(|t|TransportCommand::input_to_command(&*t.read().unwrap(), input) .map(Transport)) .flatten(), - ArrangerAppFocus::PhrasePool => { + ArrangerViewFocus::PhrasePool => { let phrases = state.phrases.read().unwrap(); match input.event() { key!(KeyCode::Char('e')) => Some(EditPhrase(Some(phrases.phrase().clone()))), @@ -57,10 +57,10 @@ impl InputToCommand> for ArrangerAppCommand { .map(Phrases) } }, - ArrangerAppFocus::PhraseEditor => + ArrangerViewFocus::PhraseEditor => PhraseEditorCommand::input_to_command(&state.editor, input) .map(Editor), - ArrangerAppFocus::Arrangement => match input.event() { + ArrangerViewFocus::Arrangement => match input.event() { key!(KeyCode::Char('e')) => Some(EditPhrase(state.arrangement.phrase())), _ => ArrangementCommand::input_to_command(&state.arrangement, &input) .map(Arrangement) @@ -108,14 +108,14 @@ impl InputToCommand> for ArrangementCommand { } } -//impl ArrangerApp { +//impl ArrangerView { ///// Helper for event passthru to focused component //fn handle_focused (&mut self, from: &TuiInput) -> Perhaps { //match self.focused() { - //ArrangerAppFocus::Transport => self.transport.handle(from), - //ArrangerAppFocus::PhrasePool => self.handle_pool(from), - //ArrangerAppFocus::PhraseEditor => self.editor.handle(from), - //ArrangerAppFocus::Arrangement => self.handle_arrangement(from) + //ArrangerViewFocus::Transport => self.transport.handle(from), + //ArrangerViewFocus::PhrasePool => self.handle_pool(from), + //ArrangerViewFocus::PhraseEditor => self.editor.handle(from), + //ArrangerViewFocus::Arrangement => self.handle_arrangement(from) //.and_then(|result|{self.show_phrase();Ok(result)}), //} //} @@ -187,8 +187,8 @@ pub enum ArrangementCommand { Edit(Option>>), } -impl Command> for ArrangerAppCommand { - fn execute (self, state: &mut ArrangerApp) -> Perhaps { +impl Command> for ArrangerViewCommand { + fn execute (self, state: &mut ArrangerView) -> Perhaps { let undo = match self { Self::Focus(cmd) => { delegate(cmd, Self::Focus, state) @@ -209,7 +209,7 @@ impl Command> for ArrangerAppCommand { }, Self::EditPhrase(phrase) => { state.editor.phrase = phrase.clone(); - state.focus(ArrangerAppFocus::PhraseEditor); + state.focus(ArrangerViewFocus::PhraseEditor); state.focus_enter(); Ok(None) } diff --git a/crates/tek_tui/src/tui_arranger_foc.rs b/crates/tek_tui/src/tui_arranger_foc.rs index f8d61baf..a89a7488 100644 --- a/crates/tek_tui/src/tui_arranger_foc.rs +++ b/crates/tek_tui/src/tui_arranger_foc.rs @@ -2,7 +2,7 @@ use crate::*; /// Sections in the arranger app that may be focused #[derive(Copy, Clone, PartialEq, Eq)] -pub enum ArrangerAppFocus { +pub enum ArrangerViewFocus { /// The transport (toolbar) is focused Transport, /// The arrangement (grid) is focused @@ -14,15 +14,15 @@ pub enum ArrangerAppFocus { } /// Focus layout of arranger app -impl FocusGrid for ArrangerApp { - type Item = ArrangerAppFocus; +impl FocusGrid for ArrangerView { + type Item = ArrangerViewFocus; fn cursor (&self) -> (usize, usize) { self.focus_cursor } fn cursor_mut (&mut self) -> &mut (usize, usize) { &mut self.focus_cursor } fn focus_enter (&mut self) { let focused = self.focused(); if !self.entered { self.entered = true; - use ArrangerAppFocus::*; + use ArrangerViewFocus::*; if let Some(transport) = self.transport.as_ref() { //transport.write().unwrap().entered = focused == Transport } @@ -39,15 +39,15 @@ impl FocusGrid for ArrangerApp { self.phrases.write().unwrap().entered = false; } } - fn entered (&self) -> Option { + fn entered (&self) -> Option { if self.entered { Some(self.focused()) } else { None } } - fn layout (&self) -> &[&[ArrangerAppFocus]] { - use ArrangerAppFocus::*; + fn layout (&self) -> &[&[ArrangerViewFocus]] { + use ArrangerViewFocus::*; &[ &[Transport, Transport], &[Arrangement, Arrangement], @@ -55,7 +55,7 @@ impl FocusGrid for ArrangerApp { ] } fn update_focus (&mut self) { - use ArrangerAppFocus::*; + use ArrangerViewFocus::*; let focused = self.focused(); if let Some(transport) = self.transport.as_ref() { transport.write().unwrap().focused = focused == Transport diff --git a/crates/tek_tui/src/tui_sequencer.rs b/crates/tek_tui/src/tui_sequencer.rs index f61dd966..164db388 100644 --- a/crates/tek_tui/src/tui_sequencer.rs +++ b/crates/tek_tui/src/tui_sequencer.rs @@ -2,44 +2,40 @@ use crate::*; use std::cmp::PartialEq; /// Root level object for standalone `tek_sequencer` -pub struct SequencerApp { - /// JACK client handle (needs to not be dropped for standalone mode to work). - pub jack: Arc>, +pub struct SequencerView { /// Controls the JACK transport. - pub transport: Option>>>, - /// Global timebase - pub clock: Arc, + pub transport: TransportView, + /// Width of phrase pool + pub split: u16, /// Pool of all phrases available to the sequencer - pub phrases: Arc>>, + pub phrases: PhrasePoolView, /// Phrase editor view - pub editor: PhraseEditor, + pub editor: PhraseEditor, /// Phrase player - pub player: PhrasePlayer, + pub player: MIDIPlayer, /// Which view is focused - pub focus_cursor: (usize, usize), + pub cursor: (usize, usize), /// Whether the currently focused item is entered - pub entered: bool, + pub entered: bool, } -impl Content for SequencerApp { +/// JACK process callback for sequencer app +impl Audio for SequencerView { + fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { + self.transport.process(client, scope); + self.player.process(client, scope); + Control::Continue + } +} + +impl Content for SequencerView { type Engine = Tui; fn content (&self) -> impl Widget { Stack::down(move|add|{ add(&self.transport)?; - add(&self.phrases.clone() + add(&self.phrases .split(Direction::Right, 20, &self.editor as &dyn Widget) .min_y(20)) }) } } - -/// JACK process callback for sequencer app -impl Audio for SequencerApp { - fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - if let Some(ref transport) = self.transport { - transport.write().unwrap().process(client, scope); - } - self.player.process(client, scope); - Control::Continue - } -} diff --git a/crates/tek_tui/src/tui_sequencer_cmd.rs b/crates/tek_tui/src/tui_sequencer_cmd.rs index f775e096..2221723b 100644 --- a/crates/tek_tui/src/tui_sequencer_cmd.rs +++ b/crates/tek_tui/src/tui_sequencer_cmd.rs @@ -1,14 +1,14 @@ use crate::*; #[derive(Clone, PartialEq)] -pub enum SequencerAppCommand { +pub enum SequencerViewCommand { Focus(FocusCommand), Transport(TransportCommand), Phrases(PhrasePoolCommand), Editor(PhraseEditorCommand), } -impl Handle for SequencerApp { +impl Handle for SequencerView { fn handle (&mut self, i: &TuiInput) -> Perhaps { if let Some(entered) = self.entered() { use SequencerFocus::*; @@ -28,8 +28,8 @@ impl Handle for SequencerApp { } } -impl Command> for SequencerAppCommand { - fn execute (self, state: &mut SequencerApp) -> Perhaps { +impl Command> for SequencerViewCommand { + fn execute (self, state: &mut SequencerView) -> Perhaps { match self { Self::Focus(cmd) => { return delegate(cmd, Self::Focus, state) @@ -48,9 +48,9 @@ impl Command> for SequencerAppCommand { } } -impl InputToCommand> for SequencerAppCommand { - fn input_to_command (state: &SequencerApp, input: &TuiInput) -> Option { - use SequencerAppCommand::*; +impl InputToCommand> for SequencerViewCommand { + fn input_to_command (state: &SequencerView, input: &TuiInput) -> Option { + use SequencerViewCommand::*; use FocusCommand::*; match input.event() { key!(KeyCode::Tab) => Some(Focus(Next)), @@ -61,10 +61,10 @@ impl InputToCommand> for SequencerAppCommand { key!(KeyCode::Down) => Some(Focus(Down)), key!(KeyCode::Left) => Some(Focus(Left)), key!(KeyCode::Right) => Some(Focus(Right)), - key!(KeyCode::Char(' ')) => Some(Transport(TransportAppCommand::PlayToggle)), + key!(KeyCode::Char(' ')) => Some(Transport(TransportViewCommand::PlayToggle)), _ => match state.focused() { SequencerFocus::Transport => if let Some(t) = state.transport.as_ref() { - TransportAppCommand::input_to_command(&*t.read().unwrap(), input).map(Transport) + TransportViewCommand::input_to_command(&*t.read().unwrap(), input).map(Transport) } else { None }, diff --git a/crates/tek_tui/src/tui_transport.rs b/crates/tek_tui/src/tui_transport.rs index 70eaf8de..7e40de48 100644 --- a/crates/tek_tui/src/tui_transport.rs +++ b/crates/tek_tui/src/tui_transport.rs @@ -1,28 +1,30 @@ use crate::*; +use tek_api::Transport; + /// Stores and displays time-related state. #[derive(Debug)] pub struct TransportView { _engine: PhantomData, - state: TransportToolbar, + state: Transport, + focus: TransportViewFocus, focused: bool, - focus: TransportFocus, } -/// Which item of the transport toolbar is focused -#[derive(Clone, Copy, PartialEq)] -pub enum TransportFocus { - Bpm, - Sync, - PlayPause, - Clock, - Quant, + +/// JACK process callback for transport app +impl Audio for TransportView { + fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { + self.state.process(client, scope); + Control::Continue + } } + impl TransportView { - pub fn new (jack: &Arc>, clock: Option<&Arc>) -> Self { + pub fn new (jack: &Arc>, clock: Option<&Arc>) -> Self { Self { _engine: Default::default(), focused: false, - focus: TransportFocus::PlayPause, - state: TransportToolbar { + focus: TransportViewFocus::PlayPause, + state: Transport { metronome: false, transport: jack.read().unwrap().transport(), jack: jack.clone(), @@ -30,7 +32,7 @@ impl TransportView { Some(clock) => clock.clone(), None => { let timebase = Arc::new(Timebase::default()); - Arc::new(TransportTime { + Arc::new(Clock { playing: Some(TransportState::Stopped).into(), quant: 24.into(), sync: (timebase.ppq.get() * 4.).into(), @@ -42,48 +44,13 @@ impl TransportView { } } } - pub fn toggle_play (&mut self) -> Usually<()> { - let playing = self.clock.playing.read().unwrap().expect("1st sample has not been processed yet"); - let playing = match playing { - TransportState::Stopped => { - self.transport.start()?; - Some(TransportState::Starting) - }, - _ => { - self.transport.stop()?; - self.transport.locate(0)?; - Some(TransportState::Stopped) - }, - }; - *self.clock.playing.write().unwrap() = playing; - Ok(()) - } } -impl TransportFocus { - pub fn next (&mut self) { - *self = match self { - Self::PlayPause => Self::Bpm, - Self::Bpm => Self::Quant, - Self::Quant => Self::Sync, - Self::Sync => Self::Clock, - Self::Clock => Self::PlayPause, - } - } - pub fn prev (&mut self) { - *self = match self { - Self::PlayPause => Self::Clock, - Self::Bpm => Self::PlayPause, - Self::Quant => Self::Bpm, - Self::Sync => Self::Quant, - Self::Clock => Self::Sync, - } - } -} -impl Content for TransportToolbar { + +impl Content for TransportView { type Engine = Tui; fn content (&self) -> impl Widget { lay!( - self.focus.wrap(self.focused, TransportToolbarFocus::PlayPause, &Styled( + self.focus.wrap(self.focused, TransportViewFocus::PlayPause, &Styled( None, match *self.clock.playing.read().unwrap() { Some(TransportState::Rolling) => "▶ PLAYING", @@ -94,19 +61,19 @@ impl Content for TransportToolbar { ).min_xy(11, 2).push_x(1)).align_x().fill_x(), row!( - self.focus.wrap(self.focused, TransportToolbarFocus::Bpm, &Outset::X(1u16, { + self.focus.wrap(self.focused, TransportViewFocus::Bpm, &Outset::X(1u16, { let bpm = self.clock.timebase().bpm.get(); row! { "BPM ", format!("{}.{:03}", bpm as usize, (bpm * 1000.0) % 1000.0) } })), - //let quant = self.focus.wrap(self.focused, TransportToolbarFocus::Quant, &Outset::X(1u16, row! { + //let quant = self.focus.wrap(self.focused, TransportViewFocus::Quant, &Outset::X(1u16, row! { //"QUANT ", ppq_to_name(self.quant as usize) //})), - self.focus.wrap(self.focused, TransportToolbarFocus::Sync, &Outset::X(1u16, row! { + self.focus.wrap(self.focused, TransportViewFocus::Sync, &Outset::X(1u16, row! { "SYNC ", pulses_to_name(self.clock.sync.get() as usize) })) ).align_w().fill_x(), - self.focus.wrap(self.focused, TransportToolbarFocus::Clock, &{ + self.focus.wrap(self.focused, TransportViewFocus::Clock, &{ let time1 = self.clock.current.format_beat(); let time2 = self.clock.current.usec.format_msu(); row!("B" ,time1.as_str(), " T", time2.as_str()).outset_x(1) @@ -115,13 +82,3 @@ impl Content for TransportToolbar { ).fill_x().bg(Color::Rgb(40, 50, 30)) } } -impl TransportToolbarFocus { - pub fn wrap <'a, W: Widget> ( - self, parent_focus: bool, focus: Self, widget: &'a W - ) -> impl Widget + 'a { - let focused = parent_focus && focus == self; - let corners = focused.then_some(CORNERS); - let highlight = focused.then_some(Background(Color::Rgb(60, 70, 50))); - lay!(corners, highlight, *widget) - } -} diff --git a/crates/tek_api/src/api_cmd.rs b/crates/tek_tui/src/tui_transport_bar.rs similarity index 100% rename from crates/tek_api/src/api_cmd.rs rename to crates/tek_tui/src/tui_transport_bar.rs diff --git a/crates/tek_tui/src/tui_transport_cmd.rs b/crates/tek_tui/src/tui_transport_cmd.rs index 997a5a81..49b448c4 100644 --- a/crates/tek_tui/src/tui_transport_cmd.rs +++ b/crates/tek_tui/src/tui_transport_cmd.rs @@ -1,15 +1,15 @@ use crate::*; #[derive(Copy, Clone, PartialEq)] -pub enum TransportAppCommand { +pub enum TransportViewCommand { Focus(FocusCommand), Transport(TransportCommand), } impl Handle for TransportView { fn handle (&mut self, from: &TuiInput) -> Perhaps { - TransportAppCommand::execute_with_state(self, from) + TransportViewCommand::execute_with_state(self, from) } } -impl InputToCommand> for TransportAppCommand { +impl InputToCommand> for TransportViewCommand { fn input_to_command (_: &TransportView, input: &TuiInput) -> Option { match input.event() { key!(KeyCode::Char(' ')) => Some(Self::FocusPrev), diff --git a/crates/tek_tui/src/tui_transport_foc.rs b/crates/tek_tui/src/tui_transport_foc.rs new file mode 100644 index 00000000..31e6d6ff --- /dev/null +++ b/crates/tek_tui/src/tui_transport_foc.rs @@ -0,0 +1,40 @@ +use crate::*; + +impl TransportViewFocus { + pub fn next (&mut self) { + *self = match self { + Self::PlayPause => Self::Bpm, + Self::Bpm => Self::Quant, + Self::Quant => Self::Sync, + Self::Sync => Self::Clock, + Self::Clock => Self::PlayPause, + } + } + pub fn prev (&mut self) { + *self = match self { + Self::PlayPause => Self::Clock, + Self::Bpm => Self::PlayPause, + Self::Quant => Self::Bpm, + Self::Sync => Self::Quant, + Self::Clock => Self::Sync, + } + } + pub fn wrap <'a, W: Widget> ( + self, parent_focus: bool, focus: Self, widget: &'a W + ) -> impl Widget + 'a { + let focused = parent_focus && focus == self; + let corners = focused.then_some(CORNERS); + let highlight = focused.then_some(Background(Color::Rgb(60, 70, 50))); + lay!(corners, highlight, *widget) + } +} + +/// Which item of the transport toolbar is focused +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum TransportViewFocus { + Bpm, + Sync, + PlayPause, + Clock, + Quant, +}