diff --git a/crates/tek_core/src/space.rs b/crates/tek_core/src/space.rs index 4e6fdeab..5f5fd627 100644 --- a/crates/tek_core/src/space.rs +++ b/crates/tek_core/src/space.rs @@ -21,17 +21,16 @@ pub trait Coordinate: Send + Sync + Copy } } -impl Coordinate for T where - T: Send + Sync + Copy - + Add - + Sub - + Mul - + Div - + Ord + PartialEq + Eq - + Debug + Display + Default - + From + Into - + Into - + Into +impl Coordinate for T where T: Send + Sync + Copy + + Add + + Sub + + Mul + + Div + + Ord + PartialEq + Eq + + Debug + Display + Default + + From + Into + + Into + + Into {} pub struct FixedAxis { diff --git a/crates/tek_tui/src/lib.rs b/crates/tek_tui/src/lib.rs index 1fa1fd28..03a3fef5 100644 --- a/crates/tek_tui/src/lib.rs +++ b/crates/tek_tui/src/lib.rs @@ -12,13 +12,18 @@ pub(crate) use std::fs::read_dir; use std::fmt::Debug; submod! { + + tui_model + tui_jack + tui_handle + tui_focus + tui_status + tui_menu + tui_arranger tui_arranger_cmd - tui_arranger_focus - tui_arranger_jack tui_arranger_scene tui_arranger_select - tui_arranger_status tui_arranger_track tui_arranger_view @@ -38,17 +43,11 @@ submod! { //tui_sampler_cmd tui_sequencer tui_sequencer_cmd - tui_sequencer_jack tui_sequencer_view - tui_sequencer_focus - tui_sequencer_status - tui_status tui_theme tui_transport tui_transport_cmd - tui_transport_focus - tui_transport_jack tui_transport_view } diff --git a/crates/tek_tui/src/tui_arranger.rs b/crates/tek_tui/src/tui_arranger.rs index c43cc311..a467e679 100644 --- a/crates/tek_tui/src/tui_arranger.rs +++ b/crates/tek_tui/src/tui_arranger.rs @@ -1,69 +1,5 @@ use crate::*; -/// Root view for standalone `tek_arranger` -pub struct ArrangerTui { - pub jack: Arc>, - pub transport: jack::Transport, - pub playing: RwLock>, - pub started: RwLock>, - pub current: Instant, - pub quant: Quantize, - pub sync: LaunchSync, - pub metronome: bool, - pub phrases: Vec>>, - pub phrase: usize, - pub tracks: Vec, - pub scenes: Vec, - pub name: Arc>, - pub splits: [u16;2], - pub selected: ArrangerSelection, - pub mode: ArrangerMode, - pub color: ItemColor, - pub entered: bool, - pub size: Measure, - pub note_buf: Vec, - pub midi_buf: Vec>>, - pub cursor: (usize, usize), - pub menu_bar: Option>, - pub status_bar: Option, - pub history: Vec, -} - -impl TryFrom<&Arc>> for ArrangerTui { - type Error = Box; - fn try_from (jack: &Arc>) -> Usually { - Ok(Self { - name: Arc::new(RwLock::new(String::new())), - phrases: vec![], - phrase: 0, - scenes: vec![], - tracks: vec![], - metronome: false, - playing: None.into(), - started: None.into(), - transport: jack.read().unwrap().transport(), - current: Instant::default(), - jack: jack.clone(), - selected: ArrangerSelection::Clip(0, 0), - mode: ArrangerMode::Vertical(2), - color: Color::Rgb(28, 35, 25).into(), - size: Measure::new(), - entered: false, - quant: Default::default(), - sync: Default::default(), - splits: [20, 20], - note_buf: vec![], - midi_buf: vec![], - cursor: (0, 0), - entered: false, - history: vec![], - size: Measure::new(), - menu_bar: None, - status_bar: None, - }) - } -} - impl HasPhrases for ArrangerTui { fn phrases (&self) -> &Vec>> { &self.phrases diff --git a/crates/tek_tui/src/tui_arranger_cmd.rs b/crates/tek_tui/src/tui_arranger_cmd.rs index 74a3a9c3..26e2212d 100644 --- a/crates/tek_tui/src/tui_arranger_cmd.rs +++ b/crates/tek_tui/src/tui_arranger_cmd.rs @@ -1,12 +1,5 @@ use crate::*; -/// Handle top-level events in standalone arranger. -impl Handle for ArrangerTui { - fn handle (&mut self, i: &TuiInput) -> Perhaps { - ArrangerCommand::execute_with_state(self, i) - } -} - #[derive(Clone, Debug)] pub enum ArrangerCommand { Focus(FocusCommand), diff --git a/crates/tek_tui/src/tui_arranger_focus.rs b/crates/tek_tui/src/tui_arranger_focus.rs deleted file mode 100644 index 70ce996a..00000000 --- a/crates/tek_tui/src/tui_arranger_focus.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::*; - -/// Sections in the arranger app that may be focused -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum ArrangerFocus { - /// The menu bar is focused - Menu, - /// The transport (toolbar) is focused - Transport, - /// The arrangement (grid) is focused - Arranger, - /// The phrase list (pool) is focused - PhrasePool, - /// The phrase editor (sequencer) is focused - PhraseEditor, -} - -impl FocusEnter for ArrangerTui { - type Item = ArrangerFocus; - fn focus_enter (&mut self) { - use ArrangerFocus::*; - let focused = self.focused(); - if !self.entered { - self.entered = focused == Arranger; - self.app.editor.entered = focused == PhraseEditor; - self.app.phrases.entered = focused == PhrasePool; - } - } - fn focus_exit (&mut self) { - if self.entered { - self.entered = false; - self.app.editor.entered = false; - self.app.phrases.entered = false; - } - } - fn focus_entered (&self) -> Option { - if self.entered { - Some(self.focused()) - } else { - None - } - } -} - -/// Focus layout of arranger app -impl FocusGrid for ArrangerTui { - type Item = ArrangerFocus; - fn focus_cursor (&self) -> (usize, usize) { - self.cursor - } - fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { - &mut self.cursor - } - fn focus_layout (&self) -> &[&[Self::Item]] { - use ArrangerFocus::*; - &[ - &[Menu, Menu ], - &[Transport, Transport ], - &[Arranger, Arranger ], - &[PhrasePool, PhraseEditor], - ] - } - fn focus_update (&mut self) { - use ArrangerFocus::*; - let focused = self.focused(); - if let Some(mut status_bar) = self.status_bar { - status_bar.update(&( - self.focused(), - self.app.selected, - focused == PhraseEditor && self.entered - )) - } - } -} diff --git a/crates/tek_tui/src/tui_arranger_jack.rs b/crates/tek_tui/src/tui_arranger_jack.rs deleted file mode 100644 index 1bcee333..00000000 --- a/crates/tek_tui/src/tui_arranger_jack.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::*; - -impl JackApi for ArrangerTui { - fn jack (&self) -> &Arc> { - &self.jack - } -} - -impl Audio for ArrangerTui { - #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - if TracksAudio( - &mut self.app.tracks, - &mut self.app.note_buf, - &mut self.app.midi_buf, - Default::default(), - ).process(client, scope) == Control::Quit { - return Control::Quit - } - // FIXME: one of these per playing track - if let ArrangerSelection::Clip(t, s) = self.selected { - let phrase = self.scenes().get(s).map(|scene|scene.clips.get(t)); - if let Some(Some(Some(phrase))) = phrase { - if let Some(track) = self.tracks().get(t) { - if let Some((ref started_at, Some(ref playing))) = track.player.phrase { - let phrase = phrase.read().unwrap(); - if *playing.read().unwrap() == *phrase { - let pulse = self.current().pulse.get(); - let start = started_at.pulse.get(); - let now = (pulse - start) % phrase.length as f64; - self.editor.now.set(now); - return Control::Continue - } - } - } - } - } - self.editor.now.set(0.); - return Control::Continue - } -} - -impl ClockApi for ArrangerTui { - fn timebase (&self) -> &Arc { - &self.current.timebase - } - fn quant (&self) -> &Quantize { - &self.quant - } - fn sync (&self) -> &LaunchSync { - &self.sync - } -} - -impl PlayheadApi for ArrangerTui { - fn current (&self) -> &Instant { - &self.current - } - fn transport (&self) -> &jack::Transport { - &self.transport - } - fn playing (&self) -> &RwLock> { - &self.playing - } - fn started (&self) -> &RwLock> { - &self.started - } -} diff --git a/crates/tek_tui/src/tui_arranger_status.rs b/crates/tek_tui/src/tui_arranger_status.rs deleted file mode 100644 index f3069075..00000000 --- a/crates/tek_tui/src/tui_arranger_status.rs +++ /dev/null @@ -1,120 +0,0 @@ -use crate::*; - -/// Status bar for arranger app -#[derive(Copy, Clone, Debug)] -pub enum ArrangerStatus { - Transport, - ArrangerMix, - ArrangerTrack, - ArrangerScene, - ArrangerClip, - PhrasePool, - PhraseView, - PhraseEdit, -} - -impl StatusBar for ArrangerStatus { - type State = (ArrangerFocus, ArrangerSelection, bool); - fn hotkey_fg () -> Color where Self: Sized { - TuiTheme::hotkey_fg() - } - fn update (&mut self, (focused, selected, entered): &Self::State) { - *self = match focused { - ArrangerFocus::Transport => ArrangerStatus::Transport, - ArrangerFocus::Arranger => match selected { - ArrangerSelection::Mix => ArrangerStatus::ArrangerMix, - ArrangerSelection::Track(_) => ArrangerStatus::ArrangerTrack, - ArrangerSelection::Scene(_) => ArrangerStatus::ArrangerScene, - ArrangerSelection::Clip(_, _) => ArrangerStatus::ArrangerClip, - }, - ArrangerFocus::PhrasePool => ArrangerStatus::PhrasePool, - ArrangerFocus::PhraseEditor => match entered { - true => ArrangerStatus::PhraseEdit, - false => ArrangerStatus::PhraseView, - }, - } - } -} - -impl Content for ArrangerStatus { - type Engine = Tui; - fn content (&self) -> impl Widget { - let label = match self { - Self::Transport => "TRANSPORT", - Self::ArrangerMix => "PROJECT", - Self::ArrangerTrack => "TRACK", - Self::ArrangerScene => "SCENE", - Self::ArrangerClip => "CLIP", - Self::PhrasePool => "SEQ LIST", - Self::PhraseView => "VIEW SEQ", - Self::PhraseEdit => "EDIT SEQ", - }; - let status_bar_bg = TuiTheme::status_bar_bg(); - let mode_bg = TuiTheme::mode_bg(); - let mode_fg = TuiTheme::mode_fg(); - let mode = TuiStyle::bold(format!(" {label} "), true).bg(mode_bg).fg(mode_fg); - let commands = match self { - Self::ArrangerMix => Self::command(&[ - ["", "c", "olor"], - ["", "<>", "resize"], - ["", "+-", "zoom"], - ["", "n", "ame/number"], - ["", "Enter", " stop all"], - ]), - Self::ArrangerClip => Self::command(&[ - ["", "g", "et"], - ["", "s", "et"], - ["", "a", "dd"], - ["", "i", "ns"], - ["", "d", "up"], - ["", "e", "dit"], - ["", "c", "olor"], - ["re", "n", "ame"], - ["", ",.", "select"], - ["", "Enter", " launch"], - ]), - Self::ArrangerTrack => Self::command(&[ - ["re", "n", "ame"], - ["", ",.", "resize"], - ["", "<>", "move"], - ["", "i", "nput"], - ["", "o", "utput"], - ["", "m", "ute"], - ["", "s", "olo"], - ["", "Del", "ete"], - ["", "Enter", " stop"], - ]), - Self::ArrangerScene => Self::command(&[ - ["re", "n", "ame"], - ["", "Del", "ete"], - ["", "Enter", " launch"], - ]), - Self::PhrasePool => Self::command(&[ - ["", "a", "ppend"], - ["", "i", "nsert"], - ["", "d", "uplicate"], - ["", "Del", "ete"], - ["", "c", "olor"], - ["re", "n", "ame"], - ["leng", "t", "h"], - ["", ",.", "move"], - ["", "+-", "resize view"], - ]), - Self::PhraseView => Self::command(&[ - ["", "enter", " edit"], - ["", "arrows/pgup/pgdn", " scroll"], - ["", "+=", "zoom"], - ]), - Self::PhraseEdit => Self::command(&[ - ["", "esc", " exit"], - ["", "a", "ppend"], - ["", "s", "et"], - ["", "][", "length"], - ["", "+-", "zoom"], - ]), - _ => Self::command(&[]) - }; - //let commands = commands.iter().reduce(String::new(), |s, (a, b, c)| format!("{s} {a}{b}{c}")); - row!(mode, commands).fill_x().bg(status_bar_bg) - } -} diff --git a/crates/tek_tui/src/tui_focus.rs b/crates/tek_tui/src/tui_focus.rs new file mode 100644 index 00000000..17ad98e8 --- /dev/null +++ b/crates/tek_tui/src/tui_focus.rs @@ -0,0 +1,215 @@ +use crate::*; + +/// Which item of the transport toolbar is focused +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum TransportFocus { + Menu, + Bpm, + Sync, + PlayPause, + Clock, + Quant, +} +/// Sections in the sequencer app that may be focused +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum SequencerFocus { + /// The menu bar is focused + Menu, + /// The transport (toolbar) is focused + Transport, + /// The phrase list (pool) is focused + PhrasePool, + /// The phrase editor (sequencer) is focused + PhraseEditor, +} +/// Sections in the arranger app that may be focused +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum ArrangerFocus { + /// The menu bar is focused + Menu, + /// The transport (toolbar) is focused + Transport, + /// The arrangement (grid) is focused + Arranger, + /// The phrase list (pool) is focused + PhrasePool, + /// The phrase editor (sequencer) is focused + PhraseEditor, +} + +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, + } + } + 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) + } +} + +impl HasFocus for TransportTui { + type Item = TransportFocus; +} + +impl FocusEnter for TransportTui { + type Item = TransportFocus; + fn focus_enter (&mut self) { + self.entered = true; + } + fn focus_exit (&mut self) { + self.entered = false; + } + fn focus_entered (&self) -> Option { + if self.entered { + Some(self.focused()) + } else { + None + } + } +} + +impl FocusGrid for TransportTui { + type Item = TransportFocus; + fn focus_cursor (&self) -> (usize, usize) { + self.cursor + } + fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { + &mut self.cursor + } + fn focus_layout (&self) -> &[&[Self::Item]] { + use TransportFocus::*; + &[ + &[Menu], + &[Bpm, Sync, PlayPause, Clock, Quant], + ] + } + fn focus_update (&mut self) { + // TODO + } +} + +impl HasFocus for SequencerTui { + type Item = SequencerFocus; +} + +impl FocusEnter for SequencerTui { + type Item = SequencerFocus; + fn focus_enter (&mut self) { + let focused = self.focused(); + if !self.entered { + self.entered = true; + // TODO + } + } + fn focus_exit (&mut self) { + if self.entered { + self.entered = false; + // TODO + } + } + fn focus_entered (&self) -> Option { + if self.entered { + Some(self.focused()) + } else { + None + } + } +} + +impl FocusGrid for SequencerTui { + type Item = SequencerFocus; + fn focus_cursor (&self) -> (usize, usize) { + self.cursor + } + fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { + &mut self.cursor + } + fn focus_layout (&self) -> &[&[Self::Item]] { + use SequencerFocus::*; + &[ + &[Menu, Menu ], + &[Transport, Transport ], + &[PhrasePool, PhraseEditor], + ] + } + fn focus_update (&mut self) { + // TODO + } +} + +impl FocusEnter for ArrangerTui { + type Item = ArrangerFocus; + fn focus_enter (&mut self) { + use ArrangerFocus::*; + let focused = self.focused(); + if !self.entered { + self.entered = focused == Arranger; + self.app.editor.entered = focused == PhraseEditor; + self.app.phrases.entered = focused == PhrasePool; + } + } + fn focus_exit (&mut self) { + if self.entered { + self.entered = false; + self.app.editor.entered = false; + self.app.phrases.entered = false; + } + } + fn focus_entered (&self) -> Option { + if self.entered { + Some(self.focused()) + } else { + None + } + } +} + +/// Focus layout of arranger app +impl FocusGrid for ArrangerTui { + type Item = ArrangerFocus; + fn focus_cursor (&self) -> (usize, usize) { + self.cursor + } + fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { + &mut self.cursor + } + fn focus_layout (&self) -> &[&[Self::Item]] { + use ArrangerFocus::*; + &[ + &[Menu, Menu ], + &[Transport, Transport ], + &[Arranger, Arranger ], + &[PhrasePool, PhraseEditor], + ] + } + fn focus_update (&mut self) { + use ArrangerFocus::*; + let focused = self.focused(); + if let Some(mut status_bar) = self.status_bar { + status_bar.update(&( + self.focused(), + self.app.selected, + focused == PhraseEditor && self.entered + )) + } + } +} diff --git a/crates/tek_tui/src/tui_handle.rs b/crates/tek_tui/src/tui_handle.rs new file mode 100644 index 00000000..013d7171 --- /dev/null +++ b/crates/tek_tui/src/tui_handle.rs @@ -0,0 +1,27 @@ +use crate::*; + +impl Handle for TransportTui { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + TransportCommand::execute_with_state(self, from) + } +} +impl Handle for SequencerTui { + fn handle (&mut self, i: &TuiInput) -> Perhaps { + SequencerCommand::execute_with_state(self, i) + } +} +impl Handle for ArrangerTui { + fn handle (&mut self, i: &TuiInput) -> Perhaps { + ArrangerCommand::execute_with_state(self, i) + } +} +impl Handle for PhrasesTui { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + PhrasesCommand::execute_with_state(self, from) + } +} +impl Handle for PhraseTui { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + PhraseCommand::execute_with_state(self, from) + } +} diff --git a/crates/tek_tui/src/tui_init.rs b/crates/tek_tui/src/tui_init.rs new file mode 100644 index 00000000..5292208e --- /dev/null +++ b/crates/tek_tui/src/tui_init.rs @@ -0,0 +1,68 @@ +use crate::*; + +/// Create app state from JACK handle. +impl TryFrom<&Arc>> for TransportTui { + type Error = Box; + fn try_from (jack: &Arc>) -> Usually { + Ok(Self { + metronome: false, + transport: jack.read().unwrap().transport(), + jack: jack.clone(), + focused: false, + focus: TransportFocus::PlayPause, + size: Measure::new(), + }) + } +} + +impl TryFrom<&Arc>> for SequencerTui { + type Error = Box; + fn try_from (jack: &Arc>) -> Usually { + Ok(Self::new(SequencerTui { + phrases: vec![], + metronome: false, + transport: jack.read().unwrap().transport(), + jack: jack.clone(), + focused: false, + focus: TransportFocus::PlayPause, + size: Measure::new(), + phrases: vec![], + }.into(), None, None)) + } +} + +impl TryFrom<&Arc>> for ArrangerTui { + type Error = Box; + fn try_from (jack: &Arc>) -> Usually { + Ok(Self { + name: Arc::new(RwLock::new(String::new())), + phrases: vec![], + phrase: 0, + scenes: vec![], + tracks: vec![], + metronome: false, + playing: None.into(), + started: None.into(), + transport: jack.read().unwrap().transport(), + current: Instant::default(), + jack: jack.clone(), + selected: ArrangerSelection::Clip(0, 0), + mode: ArrangerMode::Vertical(2), + color: Color::Rgb(28, 35, 25).into(), + size: Measure::new(), + entered: false, + quant: Default::default(), + sync: Default::default(), + splits: [20, 20], + note_buf: vec![], + midi_buf: vec![], + cursor: (0, 0), + entered: false, + history: vec![], + size: Measure::new(), + menu_bar: None, + status_bar: None, + }) + } +} + diff --git a/crates/tek_tui/src/tui_jack.rs b/crates/tek_tui/src/tui_jack.rs new file mode 100644 index 00000000..a2ddd24e --- /dev/null +++ b/crates/tek_tui/src/tui_jack.rs @@ -0,0 +1,191 @@ +use crate::*; + +impl JackApi for TransportTui { + fn jack (&self) -> &Arc> { + &self.jack + } +} +impl JackApi for SequencerTui { + fn jack (&self) -> &Arc> { + &self.jack + } +} +impl JackApi for ArrangerTui { + fn jack (&self) -> &Arc> { + &self.jack + } +} + +impl Audio for SequencerTui { + fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { + self.model.process(client, scope) + } +} +impl Audio for ArrangerTui { + #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { + if TracksAudio( + &mut self.app.tracks, + &mut self.app.note_buf, + &mut self.app.midi_buf, + Default::default(), + ).process(client, scope) == Control::Quit { + return Control::Quit + } + // FIXME: one of these per playing track + if let ArrangerSelection::Clip(t, s) = self.selected { + let phrase = self.scenes().get(s).map(|scene|scene.clips.get(t)); + if let Some(Some(Some(phrase))) = phrase { + if let Some(track) = self.tracks().get(t) { + if let Some((ref started_at, Some(ref playing))) = track.player.phrase { + let phrase = phrase.read().unwrap(); + if *playing.read().unwrap() == *phrase { + let pulse = self.current().pulse.get(); + let start = started_at.pulse.get(); + let now = (pulse - start) % phrase.length as f64; + self.editor.now.set(now); + return Control::Continue + } + } + } + } + } + self.editor.now.set(0.); + return Control::Continue + } +} + +impl ClockApi for ArrangerTui { + fn timebase (&self) -> &Arc { + &self.current.timebase + } + fn quant (&self) -> &Quantize { + &self.quant + } + fn sync (&self) -> &LaunchSync { + &self.sync + } +} + +impl PlayheadApi for ArrangerTui { + fn current (&self) -> &Instant { + &self.current + } + fn transport (&self) -> &jack::Transport { + &self.transport + } + fn playing (&self) -> &RwLock> { + &self.playing + } + fn started (&self) -> &RwLock> { + &self.started + } +} + +impl Audio for TransportTui { + fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { + PlayheadAudio(self).process(client, scope) + } +} + + +impl ClockApi for TransportTui { + fn timebase (&self) -> &Arc { + &self.current.timebase + } + fn quant (&self) -> &Quantize { + &self.quant + } + fn sync (&self) -> &LaunchSync { + &self.sync + } +} + +impl PlayheadApi for TransportTui { + fn current (&self) -> &Instant { + &self.current + } + fn transport (&self) -> &jack::Transport { + &self.transport + } + fn playing (&self) -> &RwLock> { + &self.playing + } + fn started (&self) -> &RwLock> { + &self.started + } +} + + +impl MidiInputApi for SequencerTui { + fn midi_ins(&self) -> &Vec> { + todo!() + } + fn midi_ins_mut(&self) -> &mut Vec> { + todo!() + } + fn recording(&self) -> bool { + todo!() + } + fn recording_mut(&mut self) -> &mut bool { + todo!() + } + fn monitoring(&self) -> bool { + todo!() + } + fn monitoring_mut(&mut self) -> &mut bool { + todo!() + } + fn overdub(&self) -> bool { + todo!() + } + fn overdub_mut(&mut self) -> &mut bool { + todo!() + } + fn notes_in(&self) -> &Arc> { + todo!() + } +} + +impl MidiOutputApi for SequencerTui { + fn midi_outs (&self) -> &Vec> { + todo!() + } + fn midi_outs_mut (&mut self) -> &mut Vec> { + todo!() + } + fn midi_note (&mut self) -> &mut Vec { + todo!() + } + fn notes_out (&self) -> &Arc> { + todo!() + } +} + +impl ClockApi for SequencerTui { + fn timebase (&self) -> &Arc { + todo!() + } + fn quant (&self) -> &Quantize { + todo!() + } + fn sync (&self) -> &LaunchSync { + todo!() + } +} + +impl PlayheadApi for SequencerTui { + fn current(&self) -> &Instant { + todo!() + } + fn transport(&self) -> &Transport { + todo!() + } + fn playing(&self) -> &RwLock> { + todo!() + } + fn started(&self) -> &RwLock> { + todo!() + } +} + +impl PlayerApi for SequencerTui {} diff --git a/crates/tek_tui/src/tui_menu.rs b/crates/tek_tui/src/tui_menu.rs new file mode 100644 index 00000000..c7b7e813 --- /dev/null +++ b/crates/tek_tui/src/tui_menu.rs @@ -0,0 +1 @@ +use crate::*; diff --git a/crates/tek_tui/src/tui_model.rs b/crates/tek_tui/src/tui_model.rs new file mode 100644 index 00000000..2f6d6c72 --- /dev/null +++ b/crates/tek_tui/src/tui_model.rs @@ -0,0 +1,100 @@ +use crate::*; + +/// Stores and displays time-related info. +pub struct TransportTui { + jack: Arc>, + /// Playback state + playing: RwLock>, + /// Global sample and usec at which playback started + started: RwLock>, + /// Current moment in time + current: Instant, + /// Note quantization factor + quant: Quantize, + /// Launch quantization factor + sync: LaunchSync, + /// JACK transport handle. + transport: jack::Transport, + /// Enable metronome? + metronome: bool, + focus: TransportFocus, + focused: bool, + size: Measure, +} + +impl std::fmt::Debug for TransportTui { + fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("transport") + .field("jack", &self.jack) + .field("metronome", &self.metronome) + .finish() + } +} + +/// Root view for standalone `tek_sequencer`. +pub struct SequencerTui { + jack: Arc>, + playing: RwLock>, + started: RwLock>, + current: Instant, + quant: Quantize, + sync: LaunchSync, + transport: jack::Transport, + metronome: bool, + phrases: Vec>>, + view_phrase: usize, + split: u16, + /// Start time and phrase being played + play_phrase: Option<(Instant, Option>>)>, + /// Start time and next phrase + next_phrase: Option<(Instant, Option>>)>, + /// Play input through output. + monitoring: bool, + /// Write input to sequence. + recording: bool, + /// Overdub input to sequence. + overdub: bool, + /// Send all notes off + reset: bool, // TODO?: after Some(nframes) + /// Record from MIDI ports to current sequence. + midi_inputs: Vec>, + /// Play from current sequence to MIDI ports + midi_outputs: Vec>, + /// MIDI output buffer + midi_note: Vec, + /// MIDI output buffer + midi_chunk: Vec>>, + /// Notes currently held at input + notes_in: Arc>, + /// Notes currently held at output + notes_out: Arc>, +} + +/// Root view for standalone `tek_arranger` +pub struct ArrangerTui { + pub jack: Arc>, + pub transport: jack::Transport, + pub playing: RwLock>, + pub started: RwLock>, + pub current: Instant, + pub quant: Quantize, + pub sync: LaunchSync, + pub metronome: bool, + pub phrases: Vec>>, + pub phrase: usize, + pub tracks: Vec, + pub scenes: Vec, + pub name: Arc>, + pub splits: [u16;2], + pub selected: ArrangerSelection, + pub mode: ArrangerMode, + pub color: ItemColor, + pub entered: bool, + pub size: Measure, + pub note_buf: Vec, + pub midi_buf: Vec>>, + pub cursor: (usize, usize), + pub menu_bar: Option>, + pub status_bar: Option, + pub history: Vec, +} diff --git a/crates/tek_tui/src/tui_phrase_cmd.rs b/crates/tek_tui/src/tui_phrase_cmd.rs index 30a657c1..d22bcd46 100644 --- a/crates/tek_tui/src/tui_phrase_cmd.rs +++ b/crates/tek_tui/src/tui_phrase_cmd.rs @@ -1,11 +1,5 @@ use crate::*; -impl Handle for PhraseTui { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - PhraseCommand::execute_with_state(self, from) - } -} - #[derive(Clone, PartialEq, Debug)] pub enum PhraseCommand { // TODO: 1-9 seek markers that by default start every 8th of the phrase diff --git a/crates/tek_tui/src/tui_pool_cmd.rs b/crates/tek_tui/src/tui_pool_cmd.rs index e01ed388..007af379 100644 --- a/crates/tek_tui/src/tui_pool_cmd.rs +++ b/crates/tek_tui/src/tui_pool_cmd.rs @@ -1,11 +1,5 @@ use crate::*; -impl Handle for PhrasesTui { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - PhrasesCommand::execute_with_state(self, from) - } -} - #[derive(Clone, PartialEq, Debug)] pub enum PhrasesCommand { Select(usize), diff --git a/crates/tek_tui/src/tui_sequencer.rs b/crates/tek_tui/src/tui_sequencer.rs index 6083dc12..028a7929 100644 --- a/crates/tek_tui/src/tui_sequencer.rs +++ b/crates/tek_tui/src/tui_sequencer.rs @@ -1,79 +1,5 @@ use crate::*; -impl TryFrom<&Arc>> for SequencerTui { - type Error = Box; - fn try_from (jack: &Arc>) -> Usually { - Ok(Self::new(SequencerTui { - phrases: vec![], - metronome: false, - transport: jack.read().unwrap().transport(), - jack: jack.clone(), - focused: false, - focus: TransportFocus::PlayPause, - size: Measure::new(), - phrases: vec![], - }.into(), None, None)) - } -} - -/// Root view for standalone `tek_sequencer`. -pub struct SequencerTui { - jack: Arc>, - playing: RwLock>, - started: RwLock>, - current: Instant, - quant: Quantize, - sync: LaunchSync, - transport: jack::Transport, - metronome: bool, - phrases: Vec>>, - view_phrase: usize, - split: u16, - /// Start time and phrase being played - play_phrase: Option<(Instant, Option>>)>, - /// Start time and next phrase - next_phrase: Option<(Instant, Option>>)>, - /// Play input through output. - monitoring: bool, - /// Write input to sequence. - recording: bool, - /// Overdub input to sequence. - overdub: bool, - /// Send all notes off - reset: bool, // TODO?: after Some(nframes) - /// Record from MIDI ports to current sequence. - midi_inputs: Vec>, - /// Play from current sequence to MIDI ports - midi_outputs: Vec>, - /// MIDI output buffer - midi_note: Vec, - /// MIDI output buffer - midi_chunk: Vec>>, - /// Notes currently held at input - notes_in: Arc>, - /// Notes currently held at output - notes_out: Arc>, -} - -/// Sections in the sequencer app that may be focused -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum SequencerFocus { - /// The menu bar is focused - Menu, - /// The transport (toolbar) is focused - Transport, - /// The phrase list (pool) is focused - PhrasePool, - /// The phrase editor (sequencer) is focused - PhraseEditor, -} - -impl Audio for SequencerTui { - fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - self.model.process(client, scope) - } -} - impl HasPhrases for SequencerTui { fn phrases (&self) -> &Vec>> { &self.phrases diff --git a/crates/tek_tui/src/tui_sequencer_cmd.rs b/crates/tek_tui/src/tui_sequencer_cmd.rs index f3dcbe77..40601a22 100644 --- a/crates/tek_tui/src/tui_sequencer_cmd.rs +++ b/crates/tek_tui/src/tui_sequencer_cmd.rs @@ -1,11 +1,5 @@ use crate::*; -impl Handle for SequencerTui { - fn handle (&mut self, i: &TuiInput) -> Perhaps { - SequencerCommand::execute_with_state(self, i) - } -} - #[derive(Clone, Debug, PartialEq)] pub enum SequencerCommand { Focus(FocusCommand), diff --git a/crates/tek_tui/src/tui_sequencer_focus.rs b/crates/tek_tui/src/tui_sequencer_focus.rs deleted file mode 100644 index 8d23b539..00000000 --- a/crates/tek_tui/src/tui_sequencer_focus.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::*; - -impl HasFocus for SequencerTui { - type Item = SequencerFocus; -} - -impl FocusEnter for SequencerTui { - type Item = SequencerFocus; - fn focus_enter (&mut self) { - let focused = self.focused(); - if !self.entered { - self.entered = true; - // TODO - } - } - fn focus_exit (&mut self) { - if self.entered { - self.entered = false; - // TODO - } - } - fn focus_entered (&self) -> Option { - if self.entered { - Some(self.focused()) - } else { - None - } - } -} - -impl FocusGrid for SequencerTui { - type Item = SequencerFocus; - fn focus_cursor (&self) -> (usize, usize) { - self.cursor - } - fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { - &mut self.cursor - } - fn focus_layout (&self) -> &[&[Self::Item]] { - use SequencerFocus::*; - &[ - &[Menu, Menu ], - &[Transport, Transport ], - &[PhrasePool, PhraseEditor], - ] - } - fn focus_update (&mut self) { - // TODO - } -} diff --git a/crates/tek_tui/src/tui_sequencer_jack.rs b/crates/tek_tui/src/tui_sequencer_jack.rs deleted file mode 100644 index a591a9f9..00000000 --- a/crates/tek_tui/src/tui_sequencer_jack.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::*; - -impl JackApi for SequencerTui { - fn jack (&self) -> &Arc> { - &self.jack - } -} - -impl MidiInputApi for SequencerTui { - fn midi_ins(&self) -> &Vec> { - todo!() - } - fn midi_ins_mut(&self) -> &mut Vec> { - todo!() - } - fn recording(&self) -> bool { - todo!() - } - fn recording_mut(&mut self) -> &mut bool { - todo!() - } - fn monitoring(&self) -> bool { - todo!() - } - fn monitoring_mut(&mut self) -> &mut bool { - todo!() - } - fn overdub(&self) -> bool { - todo!() - } - fn overdub_mut(&mut self) -> &mut bool { - todo!() - } - fn notes_in(&self) -> &Arc> { - todo!() - } -} - -impl MidiOutputApi for SequencerTui { - fn midi_outs (&self) -> &Vec> { - todo!() - } - fn midi_outs_mut (&mut self) -> &mut Vec> { - todo!() - } - fn midi_note (&mut self) -> &mut Vec { - todo!() - } - fn notes_out (&self) -> &Arc> { - todo!() - } -} - -impl ClockApi for SequencerTui { - fn timebase (&self) -> &Arc { - todo!() - } - fn quant (&self) -> &Quantize { - todo!() - } - fn sync (&self) -> &LaunchSync { - todo!() - } -} - -impl PlayheadApi for SequencerTui { - fn current(&self) -> &Instant { - todo!() - } - fn transport(&self) -> &Transport { - todo!() - } - fn playing(&self) -> &RwLock> { - todo!() - } - fn started(&self) -> &RwLock> { - todo!() - } -} - -impl PlayerApi for SequencerTui {} diff --git a/crates/tek_tui/src/tui_sequencer_status.rs b/crates/tek_tui/src/tui_sequencer_status.rs deleted file mode 100644 index c1d57f18..00000000 --- a/crates/tek_tui/src/tui_sequencer_status.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::*; - -/// Status bar for sequencer app -#[derive(Copy, Clone)] -pub enum SequencerStatusBar { - Transport, - PhrasePool, - PhraseEditor, -} - -impl StatusBar for SequencerStatusBar { - type State = (); - fn hotkey_fg () -> Color { - TuiTheme::hotkey_fg() - } - fn update (&mut self, state: &()) { - todo!() - } -} - -impl Content for SequencerStatusBar { - type Engine = Tui; - fn content (&self) -> impl Widget { - todo!(); - "" - } -} diff --git a/crates/tek_tui/src/tui_status.rs b/crates/tek_tui/src/tui_status.rs index faa2910c..ed47881f 100644 --- a/crates/tek_tui/src/tui_status.rs +++ b/crates/tek_tui/src/tui_status.rs @@ -26,3 +26,169 @@ pub trait StatusBar: Copy + Widget { }) } } + +#[derive(Copy, Clone)] +pub struct TransportStatusBar; + +impl StatusBar for TransportStatusBar { + type State = (); + fn hotkey_fg () -> Color { + TuiTheme::hotkey_fg() + } + fn update (&mut self, state: &()) { + todo!() + } +} + +impl Content for TransportStatusBar { + type Engine = Tui; + fn content (&self) -> impl Widget { + todo!(); + "" + } +} + +/// Status bar for sequencer app +#[derive(Copy, Clone)] +pub enum SequencerStatusBar { + Transport, + PhrasePool, + PhraseEditor, +} + +impl StatusBar for SequencerStatusBar { + type State = (); + fn hotkey_fg () -> Color { + TuiTheme::hotkey_fg() + } + fn update (&mut self, state: &()) { + todo!() + } +} + +impl Content for SequencerStatusBar { + type Engine = Tui; + fn content (&self) -> impl Widget { + todo!(); + "" + } +} + +/// Status bar for arranger app +#[derive(Copy, Clone, Debug)] +pub enum ArrangerStatus { + Transport, + ArrangerMix, + ArrangerTrack, + ArrangerScene, + ArrangerClip, + PhrasePool, + PhraseView, + PhraseEdit, +} + +impl StatusBar for ArrangerStatus { + type State = (ArrangerFocus, ArrangerSelection, bool); + fn hotkey_fg () -> Color where Self: Sized { + TuiTheme::hotkey_fg() + } + fn update (&mut self, (focused, selected, entered): &Self::State) { + *self = match focused { + ArrangerFocus::Transport => ArrangerStatus::Transport, + ArrangerFocus::Arranger => match selected { + ArrangerSelection::Mix => ArrangerStatus::ArrangerMix, + ArrangerSelection::Track(_) => ArrangerStatus::ArrangerTrack, + ArrangerSelection::Scene(_) => ArrangerStatus::ArrangerScene, + ArrangerSelection::Clip(_, _) => ArrangerStatus::ArrangerClip, + }, + ArrangerFocus::PhrasePool => ArrangerStatus::PhrasePool, + ArrangerFocus::PhraseEditor => match entered { + true => ArrangerStatus::PhraseEdit, + false => ArrangerStatus::PhraseView, + }, + } + } +} + +impl Content for ArrangerStatus { + type Engine = Tui; + fn content (&self) -> impl Widget { + let label = match self { + Self::Transport => "TRANSPORT", + Self::ArrangerMix => "PROJECT", + Self::ArrangerTrack => "TRACK", + Self::ArrangerScene => "SCENE", + Self::ArrangerClip => "CLIP", + Self::PhrasePool => "SEQ LIST", + Self::PhraseView => "VIEW SEQ", + Self::PhraseEdit => "EDIT SEQ", + }; + let status_bar_bg = TuiTheme::status_bar_bg(); + let mode_bg = TuiTheme::mode_bg(); + let mode_fg = TuiTheme::mode_fg(); + let mode = TuiStyle::bold(format!(" {label} "), true).bg(mode_bg).fg(mode_fg); + let commands = match self { + Self::ArrangerMix => Self::command(&[ + ["", "c", "olor"], + ["", "<>", "resize"], + ["", "+-", "zoom"], + ["", "n", "ame/number"], + ["", "Enter", " stop all"], + ]), + Self::ArrangerClip => Self::command(&[ + ["", "g", "et"], + ["", "s", "et"], + ["", "a", "dd"], + ["", "i", "ns"], + ["", "d", "up"], + ["", "e", "dit"], + ["", "c", "olor"], + ["re", "n", "ame"], + ["", ",.", "select"], + ["", "Enter", " launch"], + ]), + Self::ArrangerTrack => Self::command(&[ + ["re", "n", "ame"], + ["", ",.", "resize"], + ["", "<>", "move"], + ["", "i", "nput"], + ["", "o", "utput"], + ["", "m", "ute"], + ["", "s", "olo"], + ["", "Del", "ete"], + ["", "Enter", " stop"], + ]), + Self::ArrangerScene => Self::command(&[ + ["re", "n", "ame"], + ["", "Del", "ete"], + ["", "Enter", " launch"], + ]), + Self::PhrasePool => Self::command(&[ + ["", "a", "ppend"], + ["", "i", "nsert"], + ["", "d", "uplicate"], + ["", "Del", "ete"], + ["", "c", "olor"], + ["re", "n", "ame"], + ["leng", "t", "h"], + ["", ",.", "move"], + ["", "+-", "resize view"], + ]), + Self::PhraseView => Self::command(&[ + ["", "enter", " edit"], + ["", "arrows/pgup/pgdn", " scroll"], + ["", "+=", "zoom"], + ]), + Self::PhraseEdit => Self::command(&[ + ["", "esc", " exit"], + ["", "a", "ppend"], + ["", "s", "et"], + ["", "][", "length"], + ["", "+-", "zoom"], + ]), + _ => Self::command(&[]) + }; + //let commands = commands.iter().reduce(String::new(), |s, (a, b, c)| format!("{s} {a}{b}{c}")); + row!(mode, commands).fill_x().bg(status_bar_bg) + } +} diff --git a/crates/tek_tui/src/tui_transport.rs b/crates/tek_tui/src/tui_transport.rs index 64cef671..c7b7e813 100644 --- a/crates/tek_tui/src/tui_transport.rs +++ b/crates/tek_tui/src/tui_transport.rs @@ -1,47 +1 @@ use crate::*; - -/// Stores and displays time-related info. -pub struct TransportTui { - jack: Arc>, - /// Playback state - playing: RwLock>, - /// Global sample and usec at which playback started - started: RwLock>, - /// Current moment in time - current: Instant, - /// Note quantization factor - quant: Quantize, - /// Launch quantization factor - sync: LaunchSync, - /// JACK transport handle. - transport: jack::Transport, - /// Enable metronome? - metronome: bool, - focus: TransportFocus, - focused: bool, - size: Measure, -} - -/// Create app state from JACK handle. -impl TryFrom<&Arc>> for TransportTui { - type Error = Box; - fn try_from (jack: &Arc>) -> Usually { - Ok(Self { - metronome: false, - transport: jack.read().unwrap().transport(), - jack: jack.clone(), - focused: false, - focus: TransportFocus::PlayPause, - size: Measure::new(), - }) - } -} - -impl std::fmt::Debug for TransportTui { - fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - f.debug_struct("transport") - .field("jack", &self.jack) - .field("metronome", &self.metronome) - .finish() - } -} diff --git a/crates/tek_tui/src/tui_transport_cmd.rs b/crates/tek_tui/src/tui_transport_cmd.rs index 6fc1bb80..8d0c106d 100644 --- a/crates/tek_tui/src/tui_transport_cmd.rs +++ b/crates/tek_tui/src/tui_transport_cmd.rs @@ -1,12 +1,5 @@ use crate::*; -/// Handle input. -impl Handle for TransportTui { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - TransportCommand::execute_with_state(self, from) - } -} - #[derive(Clone, Debug, PartialEq)] pub enum TransportCommand { Focus(FocusCommand), diff --git a/crates/tek_tui/src/tui_transport_focus.rs b/crates/tek_tui/src/tui_transport_focus.rs deleted file mode 100644 index 4a6d75cb..00000000 --- a/crates/tek_tui/src/tui_transport_focus.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::*; - -/// Which item of the transport toolbar is focused -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum TransportFocus { - Menu, - Bpm, - Sync, - PlayPause, - Clock, - Quant, -} - -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, - } - } - 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) - } -} - -impl HasFocus for TransportTui { - type Item = TransportFocus; -} - -impl FocusEnter for TransportTui { - type Item = TransportFocus; - fn focus_enter (&mut self) { - self.entered = true; - } - fn focus_exit (&mut self) { - self.entered = false; - } - fn focus_entered (&self) -> Option { - if self.entered { - Some(self.focused()) - } else { - None - } - } -} - -impl FocusGrid for TransportTui { - type Item = TransportFocus; - fn focus_cursor (&self) -> (usize, usize) { - self.cursor - } - fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { - &mut self.cursor - } - fn focus_layout (&self) -> &[&[Self::Item]] { - use TransportFocus::*; - &[ - &[Menu], - &[Bpm, Sync, PlayPause, Clock, Quant], - ] - } - fn focus_update (&mut self) { - // TODO - } -} diff --git a/crates/tek_tui/src/tui_transport_jack.rs b/crates/tek_tui/src/tui_transport_jack.rs deleted file mode 100644 index 71d0d277..00000000 --- a/crates/tek_tui/src/tui_transport_jack.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::*; - -impl Audio for TransportTui { - fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - PlayheadAudio(self).process(client, scope) - } -} - -impl JackApi for TransportTui { - fn jack (&self) -> &Arc> { - &self.jack - } -} - -impl ClockApi for TransportTui { - fn timebase (&self) -> &Arc { - &self.current.timebase - } - fn quant (&self) -> &Quantize { - &self.quant - } - fn sync (&self) -> &LaunchSync { - &self.sync - } -} - -impl PlayheadApi for TransportTui { - fn current (&self) -> &Instant { - &self.current - } - fn transport (&self) -> &jack::Transport { - &self.transport - } - fn playing (&self) -> &RwLock> { - &self.playing - } - fn started (&self) -> &RwLock> { - &self.started - } -} diff --git a/crates/tek_tui/src/tui_transport_status.rs b/crates/tek_tui/src/tui_transport_status.rs deleted file mode 100644 index c3936650..00000000 --- a/crates/tek_tui/src/tui_transport_status.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::*; - -#[derive(Copy, Clone)] -pub struct TransportStatusBar; - -impl StatusBar for TransportStatusBar { - type State = (); - fn hotkey_fg () -> Color { - TuiTheme::hotkey_fg() - } - fn update (&mut self, state: &()) { - todo!() - } -} - -impl Content for TransportStatusBar { - type Engine = Tui; - fn content (&self) -> impl Widget { - todo!(); - "" - } -}