diff --git a/crates/tek_api/src/arrange_cmd.rs b/crates/tek_api/src/arrange_cmd.rs index fec6e74d..74b5d67d 100644 --- a/crates/tek_api/src/arrange_cmd.rs +++ b/crates/tek_api/src/arrange_cmd.rs @@ -58,6 +58,7 @@ impl Command for ArrangementCommand { impl Command for ArrangementSceneCommand { fn execute (self, state: &mut Arrangement) -> Perhaps { match self { + Self::Delete(index) => { state.scene_del(index); }, _ => todo!() } Ok(None) @@ -67,6 +68,7 @@ impl Command for ArrangementSceneCommand { impl Command for ArrangementTrackCommand { fn execute (self, state: &mut Arrangement) -> Perhaps { match self { + Self::Delete(index) => { state.track_del(index); }, _ => todo!() } Ok(None) diff --git a/crates/tek_api/src/clock.rs b/crates/tek_api/src/clock.rs index aa4bfe4b..72dc5efd 100644 --- a/crates/tek_api/src/clock.rs +++ b/crates/tek_api/src/clock.rs @@ -34,3 +34,15 @@ impl Clock { if pulse % sync == 0 { pulse } else { (pulse / sync + 1) * sync } } } + +impl From for Clock { + fn from (current: Instant) -> Self { + Self { + playing: Some(TransportState::Stopped).into(), + started: None.into(), + quant: 24.into(), + sync: (current.timebase.ppq.get() * 4.).into(), + current, + } + } +} diff --git a/crates/tek_api/src/sequencer.rs b/crates/tek_api/src/sequencer.rs index 091436bd..8f718fda 100644 --- a/crates/tek_api/src/sequencer.rs +++ b/crates/tek_api/src/sequencer.rs @@ -1,8 +1,5 @@ use crate::*; -/// MIDI message structural -pub type PhraseData = Vec>; - #[derive(Debug)] pub struct MIDIPlayer { /// Global timebase diff --git a/crates/tek_cli/Cargo.toml b/crates/tek_cli/Cargo.toml index e8cf13bc..29cc5182 100644 --- a/crates/tek_cli/Cargo.toml +++ b/crates/tek_cli/Cargo.toml @@ -8,33 +8,30 @@ tek_core = { path = "../tek_core" } tek_api = { path = "../tek_api" } tek_tui = { path = "../tek_tui" } -[lib] -path = "src/lib.rs" - [[bin]] name = "tek_mixer" -path = "src/mixer_cli.rs" +path = "src/cli_mixer.rs" [[bin]] name = "tek_track" -path = "src/track_cli.rs" +path = "src/cli_track.rs" [[bin]] name = "tek_sampler" -path = "src/sampler_cli.rs" +path = "src/cli_sampler.rs" [[bin]] name = "tek_plugin" -path = "src/plugin_cli.rs" +path = "src/cli_plugin.rs" [[bin]] name = "tek_sequencer" -path = "src/sequencer_cli.rs" +path = "src/cli_sequencer.rs" [[bin]] name = "tek_arranger" -path = "src/arranger_cli.rs" +path = "src/cli_arranger.rs" [[bin]] name = "tek_transport" -path = "src/transport_cli.rs" +path = "src/cli_transport.rs" diff --git a/crates/tek_cli/src/cli_arranger.rs b/crates/tek_cli/src/cli_arranger.rs index c69c6b08..13d0b135 100644 --- a/crates/tek_cli/src/cli_arranger.rs +++ b/crates/tek_cli/src/cli_arranger.rs @@ -1,6 +1,8 @@ -include!("lib.rs"); use tek_core::clap::{self, Parser}; -pub fn main () -> Usually<()> { ArrangerCli::parse().run() } + +pub fn main () -> Usually<()> { + ArrangerCli::parse().run() +} /// Parses CLI arguments to the `tek_arranger` invocation. #[derive(Debug, Parser)] diff --git a/crates/tek_cli/src/cli_mixer.rs b/crates/tek_cli/src/cli_mixer.rs index 7af8bff5..b52c46bd 100644 --- a/crates/tek_cli/src/cli_mixer.rs +++ b/crates/tek_cli/src/cli_mixer.rs @@ -1,13 +1,16 @@ -//! Multi-track mixer -include!("lib.rs"); use tek_core::clap::{self, Parser}; -pub fn main () -> Usually<()> { MixerCli::parse().run() } + +pub fn main () -> Usually<()> { + MixerCli::parse().run() +} + #[derive(Debug, Parser)] #[command(version, about, long_about = None)] pub struct MixerCli { /// Name of JACK client #[arg(short, long)] name: Option, /// Number of tracks #[arg(short, long)] channels: Option, } + impl MixerCli { fn run (&self) -> Usually<()> { Tui::run(JackClient::new("tek_mixer")?.activate_with(|jack|{ diff --git a/crates/tek_cli/src/cli_plugin.rs b/crates/tek_cli/src/cli_plugin.rs index 1bd62ca5..599fba4d 100644 --- a/crates/tek_cli/src/cli_plugin.rs +++ b/crates/tek_cli/src/cli_plugin.rs @@ -1,13 +1,16 @@ -//! Plugin host -include!("lib.rs"); use tek_core::clap::{self, Parser}; -pub fn main () -> Usually<()> { PluginCli::parse().run() } + +pub fn main () -> Usually<()> { + PluginCli::parse().run() +} + #[derive(Debug, Parser)] #[command(version, about, long_about = None)] pub struct PluginCli { /// Name of JACK client #[arg(short, long)] name: Option, /// Path to plugin #[arg(short, long)] path: Option, } + impl PluginCli { fn run (&self) -> Usually<()> { Tui::run(JackClient::new("tek_plugin")?.activate_with(|jack|{ diff --git a/crates/tek_cli/src/cli_sampler.rs b/crates/tek_cli/src/cli_sampler.rs index 95e0f81a..06e79757 100644 --- a/crates/tek_cli/src/cli_sampler.rs +++ b/crates/tek_cli/src/cli_sampler.rs @@ -1,13 +1,16 @@ -//! Sample player -include!("lib.rs"); use tek_core::clap::{self, Parser}; -pub fn main () -> Usually<()> { SamplerCli::parse().run() } + +pub fn main () -> Usually<()> { + SamplerCli::parse().run() +} + #[derive(Debug, Parser)] #[command(version, about, long_about = None)] pub struct SamplerCli { /// Name of JACK client #[arg(short, long)] name: Option, /// Path to plugin #[arg(short, long)] path: Option, } + impl SamplerCli { fn run (&self) -> Usually<()> { Tui::run(JackClient::new("tek_sampler")?.activate_with(|jack|{ diff --git a/crates/tek_cli/src/cli_sequencer.rs b/crates/tek_cli/src/cli_sequencer.rs index 2fef53e3..4b7f3f39 100644 --- a/crates/tek_cli/src/cli_sequencer.rs +++ b/crates/tek_cli/src/cli_sequencer.rs @@ -1,7 +1,8 @@ -//! Phrase editor. -include!("lib.rs"); use tek_core::clap::{self, Parser}; -pub fn main () -> Usually<()> { SequencerCli::parse().run() } + +pub fn main () -> Usually<()> { + SequencerCli::parse().run() +} #[derive(Debug, Parser)] #[command(version, about, long_about = None)] diff --git a/crates/tek_cli/src/cli_transport.rs b/crates/tek_cli/src/cli_transport.rs index 6296156b..88aadec9 100644 --- a/crates/tek_cli/src/cli_transport.rs +++ b/crates/tek_cli/src/cli_transport.rs @@ -1,10 +1,7 @@ -include!("lib.rs"); +use tek_core::clap::{self, Parser}; + /// Application entrypoint. pub fn main () -> Usually<()> { - Tui::run(JackClient::new("tek_transport")?.activate_with(|jack|{ - let mut transport = TransportView::new(jack, None); - transport.focused = true; - Ok(transport) - })?)?; + Tui::run(JackClient::new("tek_transport")?.activate_with(TransportApp::run)?)? Ok(()) } diff --git a/crates/tek_cli/src/lib.rs b/crates/tek_cli/src/lib.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/crates/tek_snd/src/snd_arrange.rs b/crates/tek_snd/src/snd_arrange.rs index ced88161..5ffd4abf 100644 --- a/crates/tek_snd/src/snd_arrange.rs +++ b/crates/tek_snd/src/snd_arrange.rs @@ -1,18 +1,18 @@ use crate::*; -pub struct ArrangementAudio { - model: Arc> -} +pub struct ArrangerAudio(pub Arc>); -impl From<&Arc>> for ArrangementAudio { - fn from (model: &Arc>) -> Self { - Self { model: model.clone() } +impl Audio for ArrangerAudio { + #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { + ArrangerRefAudio(&mut*self.0.write().unwrap()).process(client, scope) } } -impl Audio for ArrangementAudio { +pub struct ArrangerRefAudio<'a>(pub &'a mut Arrangement); + +impl<'a> Audio for ArrangerRefAudio<'a> { #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - for track in self.model.write().unwrap().tracks.iter_mut() { + for track in self.0.tracks.iter_mut() { if MIDIPlayerAudio::from(&mut track.player).process(client, scope) == Control::Quit { return Control::Quit } diff --git a/crates/tek_snd/src/snd_sequencer.rs b/crates/tek_snd/src/snd_sequencer.rs index b9ce8e15..576ef2b2 100644 --- a/crates/tek_snd/src/snd_sequencer.rs +++ b/crates/tek_snd/src/snd_sequencer.rs @@ -1,11 +1,24 @@ use crate::*; -pub struct SequencerAppAudio<'a>(&'a mut Transport, &'a mut MIDIPlayer); +pub struct SequencerAudio(pub Arc>, pub Arc>); /// JACK process callback for sequencer app -impl<'a> Audio for SequencerAppAudio<'a> { +impl<'a> Audio for SequencerAudio { fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - if TransportAudio::from(&mut*self.0).process(client, scope) == Control::Quit { + SequencerRefAudio( + &mut*self.0.write().unwrap(), + &mut*self.1.write().unwrap() + ).process(client, scope) + } +} + + +pub struct SequencerRefAudio<'a>(pub &'a mut Transport, pub &'a mut MIDIPlayer); + +/// JACK process callback for sequencer embed +impl<'a> Audio for SequencerRefAudio<'a> { + fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { + if TransportRefAudio(&mut*self.0).process(client, scope) == Control::Quit { return Control::Quit } if MIDIPlayerAudio::from(&mut*self.1).process(client, scope) == Control::Quit { diff --git a/crates/tek_snd/src/snd_transport.rs b/crates/tek_snd/src/snd_transport.rs index e25ec578..a20d8b7c 100644 --- a/crates/tek_snd/src/snd_transport.rs +++ b/crates/tek_snd/src/snd_transport.rs @@ -1,16 +1,20 @@ use crate::*; -pub struct TransportAudio<'a>(&'a mut Transport); +pub struct TransportAudio(pub Arc>); -impl<'a> From<&'a mut Transport> for TransportAudio<'a> { - fn from (model: &'a mut Transport) -> Self { - Self(model) +impl Audio for TransportAudio { + fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { + TransportRefAudio( + &mut*self.0.write().unwrap() + ).process(client, scope) } } -impl<'a> Audio for TransportAudio<'a> { +pub struct TransportRefAudio<'a>(pub &'a mut Transport); + +impl<'a> Audio for TransportRefAudio<'a> { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - let state = &mut self.0; + let ref state = self.0; let times = scope.cycle_times().unwrap(); let CycleTimes { current_frames, current_usecs, next_usecs: _, period_usecs: _ } = times; let _chunk_size = scope.n_frames() as usize; diff --git a/crates/tek_tui/src/lib.rs b/crates/tek_tui/src/lib.rs index 87da6826..9c5da899 100644 --- a/crates/tek_tui/src/lib.rs +++ b/crates/tek_tui/src/lib.rs @@ -1,7 +1,8 @@ -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::*; +pub(crate) use tek_api::*; +pub(crate) use tek_snd::*; pub(crate) use std::collections::BTreeMap; pub(crate) use std::sync::{Arc, Mutex, RwLock}; @@ -10,9 +11,6 @@ pub(crate) use std::ffi::OsString; pub(crate) use std::fs::read_dir; submod! { - tui_app - tui_app_foc - tui_arranger tui_arranger_bar tui_arranger_cmd @@ -53,3 +51,142 @@ submod! { tui_transport_cmd tui_transport_foc } + +pub struct AppContainer +where + E: Engine, + M: Send + Sync, + V: Widget + Handle, + C: Command, + A: Audio, + S: StatusBar +{ + pub cursor: (usize, usize), + pub entered: bool, + pub menu_bar: Option>, + pub status_bar: Option, + pub history: Vec, + pub size: Measure, + pub model: Arc>, + pub view: V, + pub audio: A, +} + +impl AppContainer +where + E: Engine, + M: Send + Sync, + V: Widget + Handle, + C: Command, + A: Audio, + S: StatusBar +{ + pub fn new ( + model: &Arc>, + view: V, + audio: A, + menu_bar: Option>, + status_bar: Option, + ) -> Self { + Self { + cursor: (0, 0), + entered: false, + history: vec![], + size: Measure::new(), + model: model.clone(), + view, + audio, + menu_bar, + status_bar, + } + } +} + +impl Content for AppContainer +where + M: Send + Sync, + V: Widget + Handle, + C: Command, + A: Audio, + S: StatusBar, +{ + type Engine = Tui; + fn content (&self) -> impl Widget { + let menus = self.menu_bar.as_ref().map_or_else( + ||&[] as &[Menu<_, _, _>], + |m|m.menus.as_slice() + ); + Split::down( + if self.menu_bar.is_some() { 1 } else { 0 }, + row!(menu in menus.iter() => { + row!(" ", menu.title.as_str(), " ") + }), + Split::up( + if self.status_bar.is_some() { 1 } else { 0 }, + widget(&self.status_bar), + widget(&self.view) + ) + ) + } +} + +#[derive(Debug, Copy, Clone)] +pub enum AppContainerCommand { + Focus(FocusCommand), + Undo, + Redo, + App(T) +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum AppContainerFocus { + Menu, + Content(F), +} + +impl FocusGrid for AppContainer +where + T: Send + Sync, + U: From>> + Widget + Handle + FocusGrid, + C: Command, + A: From>> + Audio, + S: From>> + StatusBar +{ + type Item = AppContainerFocus<::Item>; + fn cursor (&self) -> (usize, usize) { + self.cursor + } + fn cursor_mut (&mut self) -> &mut (usize, usize) { + &mut self.cursor + } + 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 entered (&self) -> Option { + if self.entered { + Some(self.focused()) + } else { + None + } + } + fn layout (&self) -> &[&[Self::Item]] { + &[ + &[AppContainerFocus::Menu], + FocusGrid::layout(&self.ui) + //&[AppContainerFocus::Content(())], + ] + } + fn update_focus (&mut self) { + // TODO + } +} diff --git a/crates/tek_tui/src/tui_app.rs b/crates/tek_tui/src/tui_app.rs deleted file mode 100644 index 6eb2c4b3..00000000 --- a/crates/tek_tui/src/tui_app.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::*; - -pub struct AppContainer -where - T: Send + Sync, - E: Engine, - C: Command, - U: From>> + Widget + Handle, - A: From>> + Audio, - S: From>> + StatusBar -{ - pub cursor: (usize, usize), - pub entered: bool, - pub menu_bar: Option>, - pub status_bar: Option, - pub history: Vec, - pub size: Measure, - pub ui: U, - pub audio: A, - pub model: Arc>, -} - -impl From for AppContainer -where - T: Send + Sync, - E: Engine, - C: Command, - U: From>> + Widget + Handle, - A: From>> + Audio, - S: From>> + StatusBar -{ - fn from (model: T) -> Self { - let model = Arc::new(RwLock::new(model)); - Self { - cursor: (0, 0), - entered: false, - menu_bar: None, - status_bar: None, - history: vec![], - size: Measure::new(), - ui: U::from(model.clone()), - audio: A::from(model.clone()), - model, - } - } -} - -impl Content for AppContainer -where - T: Send + Sync, - C: Command, - U: From>> + Widget + Handle, - A: From>> + Audio, - S: From>> + StatusBar -{ - type Engine = Tui; - fn content (&self) -> impl Widget { - let menus = self.menu_bar.as_ref().map_or_else( - ||&[] as &[Menu<_, _, _>], - |m|m.menus.as_slice() - ); - Split::down( - if self.menu_bar.is_some() { 1 } else { 0 }, - row!(menu in menus.iter() => { - row!(" ", menu.title.as_str(), " ") - }), - Split::up( - if self.status_bar.is_some() { 1 } else { 0 }, - widget(&self.status_bar), - widget(&self.ui) - ) - ) - } -} diff --git a/crates/tek_tui/src/tui_app_foc.rs b/crates/tek_tui/src/tui_app_foc.rs deleted file mode 100644 index 34721905..00000000 --- a/crates/tek_tui/src/tui_app_foc.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::*; - -#[derive(Debug, Copy, Clone)] -pub enum AppContainerCommand { - Focus(FocusCommand), - App(T) -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum AppContainerFocus { - Menu, - Content(F), -} - -impl FocusGrid for AppContainer -where - T: Send + Sync, - C: Command, - U: From>> + Widget + Handle + FocusGrid, - A: From>> + Audio, - S: From>> + StatusBar -{ - type Item = AppContainerFocus<::Item>; - fn cursor (&self) -> (usize, usize) { - self.cursor - } - fn cursor_mut (&mut self) -> &mut (usize, usize) { - &mut self.cursor - } - 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 entered (&self) -> Option { - if self.entered { - Some(self.focused()) - } else { - None - } - } - fn layout (&self) -> &[&[Self::Item]] { - &[ - &[AppContainerFocus::Menu], - FocusGrid::layout(&self.ui) - //&[AppContainerFocus::Content(())], - ] - } - fn update_focus (&mut self) { - // TODO - } -} diff --git a/crates/tek_tui/src/tui_arranger.rs b/crates/tek_tui/src/tui_arranger.rs index 588f08c1..4be87038 100644 --- a/crates/tek_tui/src/tui_arranger.rs +++ b/crates/tek_tui/src/tui_arranger.rs @@ -1,31 +1,107 @@ use crate::*; +pub type ArrangerApp = AppContainer< + Tui, + ArrangerModel, + ArrangerView, + ArrangerViewCommand, + ArrangerAudio, + ArrangerStatusBar +>; + +impl ArrangerApp { + pub fn run <'a> (jack: &Arc>) -> Usually { + let clock = Arc::new(Clock::from(Instant::default())); + + let transport = Arc::new(RwLock::new(tek_api::Transport { + metronome: false, + transport: jack.read().unwrap().transport(), + jack: jack.clone(), + clock: clock.clone() + })); + + let phrases = Arc::new(RwLock::new(PhrasePool { + phrases: vec![] + })); + + let player = Arc::new(RwLock::new(MIDIPlayer::new(jack, &clock, "preview")?)); + + let arrangement = Arc::new(RwLock::new(Arrangement { + jack: jack.clone(), + clock: clock.clone(), + name: Arc::new(RwLock::new(String::new())), + phrases: phrases.read().unwrap().phrases.clone(), // FIXME + tracks: vec![], + scenes: vec![], + })); + + let sequencer = Arc::new(RwLock::new(SequencerModel { + transport: transport.clone(), + phrases: phrases.clone(), + player: player.clone(), + })); + + let model = Arc::new(RwLock::new(ArrangerModel { + arrangement: arrangement.clone(), + sequencer: sequencer.clone(), + transport: transport.clone(), + phrases: phrases.clone(), + })); + + Ok(Self::new( + &model, + ArrangerView::from(&model), + ArrangerAudio(arrangement.clone()), + None, + None + )) + } +} + +pub struct ArrangerModel { + pub arrangement: Arc>, + pub sequencer: Arc>, + pub transport: Arc>, + pub phrases: Arc>, +} + +impl From<&Arc>> for ArrangerView { + fn from (model: &Arc>) -> Self { + let mut view = Self { + model: model.clone(), + sequencer: SequencerView::from(&model.read().unwrap().sequencer), + split: 20, + selected: ArrangementEditorFocus::Clip(0, 0), + mode: ArrangementEditorMode::Vertical(2), + color: Color::Rgb(28, 35, 25).into(), + size: Measure::new(), + focused: false, + entered: false, + }; + view.update_focus(); + view + } +} + /// Root level object for standalone `tek_arranger` pub struct ArrangerView { + pub model: Arc>, /// Sequencer component pub sequencer: SequencerView, - /// Contains all the sequencers. - pub arrangement: ArrangementEditor, /// Height of arrangement pub split: u16, - /// Width and height of app at last render - pub size: Measure, -} - -pub struct ArrangementEditor { - pub model: Arrangement, /// Currently selected element. - pub selected: ArrangementEditorFocus, + pub selected: ArrangementEditorFocus, /// Display mode of arranger - pub mode: ArrangementEditorMode, + pub mode: ArrangementEditorMode, /// Background color of arrangement - pub color: ItemColor, - /// Width and height of arrangement area at last render - pub size: Measure, + pub color: ItemColor, /// Whether the arranger is currently focused - pub focused: bool, + pub focused: bool, /// Whether this is currently in edit mode - pub entered: bool, + pub entered: bool, + /// Width and height of arrangement area at last render + pub size: Measure, } /// Display mode of arranger @@ -37,13 +113,27 @@ pub enum ArrangementEditorMode { Vertical(usize), } +/// Arranger display mode can be cycled +impl ArrangementEditorMode { + /// Cycle arranger display mode + pub fn to_next (&mut self) { + *self = match self { + Self::Horizontal => Self::Vertical(1), + Self::Vertical(1) => Self::Vertical(2), + Self::Vertical(2) => Self::Vertical(2), + Self::Vertical(0) => Self::Horizontal, + Self::Vertical(_) => Self::Vertical(0), + } + } +} + impl Audio for ArrangerView { #[inline] fn process (&mut self, _: &Client, _: &ProcessScope) -> Control { // FIXME: one of these per playing track - if let ArrangementEditorFocus::Clip(t, s) = self.arrangement.selected { - let phrase = self.arrangement.model.scenes.get(s).map(|scene|scene.clips.get(t)); + if let ArrangementEditorFocus::Clip(t, s) = self.selected { + let phrase = self.model.scenes.get(s).map(|scene|scene.clips.get(t)); if let Some(Some(Some(phrase))) = phrase { - if let Some(track) = self.arrangement.model.tracks.get(t) { + if let Some(track) = self.model.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 { @@ -72,18 +162,26 @@ impl Content for ArrangerView { Split::down( self.split, lay!( - widget(&self.arrangement) + Layers::new(move |add|{ + match self.mode { + ArrangementEditorMode::Horizontal => + add(&arranger_content_horizontal(self))?, + ArrangementEditorMode::Vertical(factor) => + add(&arranger_content_vertical(self, factor))? + }; + add(&self.size) + }) .grow_y(1) .border(Lozenge(Style::default() .bg(TuiTheme::border_bg()) - .fg(TuiTheme::border_fg(self.arrangement.focused)))), - widget(&self.arrangement.size), - widget(&format!("[{}] Arrangement", if self.arrangement.entered { + .fg(TuiTheme::border_fg(self.focused)))), + widget(&self.size), + widget(&format!("[{}] Arrangement", if self.entered { "■" } else { " " })) - .fg(TuiTheme::title_fg(self.arrangement.focused)) + .fg(TuiTheme::title_fg(self.focused)) .push_x(1), ), Split::right( @@ -99,52 +197,19 @@ impl Content for ArrangerView { /// General methods for arranger impl ArrangerView { - pub fn new ( - sequencer: SequencerView, - arrangement: ArrangementEditor, - ) -> Self { - let mut app = Self { sequencer, arrangement, split: 15, size: Measure::new() }; - app.update_focus(); - app - } - - /// Toggle global play/pause - pub fn toggle_play (&mut self) -> Usually<()> { - self.sequencer.transport.model.toggle_play() - } - - pub fn next_color (&self) -> ItemColor { - if let ArrangementEditorFocus::Clip(track, scene) = self.arrangement.selected { - let track_color = self.arrangement.model.tracks[track].color; - let scene_color = self.arrangement.model.scenes[scene].color; - track_color.mix(scene_color, 0.5).mix(ItemColor::random(), 0.25) - } else { - panic!("could not compute next color") - } - } - /// Focus the editor with the current phrase pub fn show_phrase (&mut self) { - self.sequencer.editor.show(self.arrangement.phrase().as_ref()); - } - - /// Focus the editor with the current phrase - pub fn edit_phrase (&mut self) { - if self.arrangement.selected.is_clip() && self.arrangement.phrase().is_none() { - self.sequencer.phrases.append_new(None, Some(self.next_color().into())); - self.arrangement.phrase_put(); - } - self.show_phrase(); - self.focus(ArrangerViewFocus::PhraseEditor); - self.sequencer.editor.entered = true; + let arrangement = self.model.read().unwrap().arrangement.read().unwrap(); + self.sequencer.editor.show(self.selected_phrase().as_ref()); } pub fn activate (&mut self) { - match self.arrangement.selected { + let arrangement = self.model.read().unwrap().arrangement.read().unwrap(); + match self.selected { ArrangementEditorFocus::Scene(s) => { - for (t, track) in self.arrangement.model.tracks.iter_mut().enumerate() { + for (t, track) in self.model.tracks.iter_mut().enumerate() { let player = &mut track.player; - let clip = self.arrangement.model.scenes[s].clips[t].as_ref(); + let clip = self.model.scenes[s].clips[t].as_ref(); if player.phrase.is_some() || clip.is_some() { player.enqueue_next(clip); } @@ -156,189 +221,61 @@ impl ArrangerView { //} }, ArrangementEditorFocus::Clip(t, s) => { - let clip = self.arrangement.model.scenes[s].clips[t].as_ref(); - self.arrangement.model.tracks[t].player.enqueue_next(clip); + let clip = self.model.scenes[s].clips[t].as_ref(); + self.model.tracks[t].player.enqueue_next(clip); }, _ => {} } } - pub fn delete (&mut self) { - match self.arrangement.selected { - ArrangementEditorFocus::Track(_) => self.arrangement.track_del(), - ArrangementEditorFocus::Scene(_) => self.arrangement.scene_del(), - ArrangementEditorFocus::Clip(_, _) => self.arrangement.phrase_del(), - _ => {} - } - } - pub fn is_first_row (&self) -> bool { - let selected = self.arrangement.selected; + let arrangement = self.model.read().unwrap().arrangement.read().unwrap(); + let selected = self.selected; selected.is_mix() || selected.is_track() } pub fn is_last_row (&self) -> bool { - let selected = self.arrangement.selected; - (self.arrangement.model.scenes.len() == 0 && (selected.is_mix() || selected.is_track())) || match selected { - ArrangementEditorFocus::Scene(s) => s == self.arrangement.model.scenes.len() - 1, - ArrangementEditorFocus::Clip(_, s) => s == self.arrangement.model.scenes.len() - 1, + let arrangement = self.model.read().unwrap().arrangement.read().unwrap(); + let selected = self.selected; + (self.model.scenes.len() == 0 && (selected.is_mix() || selected.is_track())) || match selected { + ArrangementEditorFocus::Scene(s) => s == self.model.scenes.len() - 1, + ArrangementEditorFocus::Clip(_, s) => s == self.model.scenes.len() - 1, _ => false } } pub fn toggle_loop (&mut self) { - if let Some(phrase) = self.arrangement.phrase() { + if let Some(phrase) = self.selected_phrase() { phrase.write().unwrap().toggle_loop() } } pub fn randomize_color (&mut self) { - match self.arrangement.selected { + let arrangement = self.model.read().unwrap().arrangement.read().unwrap(); + match self.selected { ArrangementEditorFocus::Mix => { - self.arrangement.color = ItemColor::random_dark() + self.color = ItemColor::random_dark() }, ArrangementEditorFocus::Track(t) => { - self.arrangement.model.tracks[t].color = ItemColor::random() + self.model.tracks[t].color = ItemColor::random() }, ArrangementEditorFocus::Scene(s) => { - self.arrangement.model.scenes[s].color = ItemColor::random() + self.model.scenes[s].color = ItemColor::random() }, ArrangementEditorFocus::Clip(t, s) => { - if let Some(phrase) = &self.arrangement.model.scenes[s].clips[t] { + if let Some(phrase) = &self.model.scenes[s].clips[t] { phrase.write().unwrap().color = ItemColorTriplet::random(); } } } } - -} - -/// Arranger display mode can be cycled -impl ArrangementEditorMode { - /// Cycle arranger display mode - pub fn to_next (&mut self) { - *self = match self { - Self::Horizontal => Self::Vertical(1), - Self::Vertical(1) => Self::Vertical(2), - Self::Vertical(2) => Self::Vertical(2), - Self::Vertical(0) => Self::Horizontal, - Self::Vertical(_) => Self::Vertical(0), - } - } -} - -impl Content for ArrangementEditor { - type Engine = Tui; - fn content (&self) -> impl Widget { - Layers::new(move |add|{ - match self.mode { - ArrangementEditorMode::Horizontal => - add(&arranger_content_horizontal(self))?, - ArrangementEditorMode::Vertical(factor) => - add(&arranger_content_vertical(self, factor))? - }; - add(&self.size) - }) - } -} - -impl ArrangementEditor { - pub fn new (model: Arrangement) -> Self { - Self { - model, - selected: ArrangementEditorFocus::Clip(0, 0), - mode: ArrangementEditorMode::Vertical(2), - color: Color::Rgb(28, 35, 25).into(), - size: Measure::new(), - focused: false, - entered: false, - } - } -} - -impl ArrangementEditor { - pub fn track (&self) -> Option<&ArrangementTrack> { - self.selected.track().map(|t|self.model.tracks.get(t)).flatten() - } - pub fn track_mut (&mut self) -> Option<&mut ArrangementTrack> { - self.selected.track().map(|t|self.model.tracks.get_mut(t)).flatten() - } - pub fn scene (&self) -> Option<&ArrangementScene> { + pub fn selected_scene (&self) -> Option<&ArrangementScene> { self.selected.scene().map(|s|self.model.scenes.get(s)).flatten() } - pub fn scene_mut (&mut self) -> Option<&mut ArrangementScene> { + pub fn selected_scene_mut (&mut self) -> Option<&mut ArrangementScene> { self.selected.scene().map(|s|self.model.scenes.get_mut(s)).flatten() } - pub fn phrase (&self) -> Option>> { - self.scene()?.clips.get(self.selected.track()?)?.clone() - } - pub fn track_del (&mut self) { - if let Some(index) = self.selected.track() { self.model.track_del(index); } - } - pub fn scene_del (&mut self) { - if let Some(index) = self.selected.scene() { self.model.scene_del(index); } - } - pub fn track_widths (&self) -> Vec<(usize, usize)> { - let mut widths = vec![]; - let mut total = 0; - for track in self.model.tracks.iter() { - let width = track.width; - widths.push((width, total)); - total += width; - } - widths.push((0, total)); - widths - } - pub fn phrase_del (&mut self) { - let track_index = self.selected.track(); - let scene_index = self.selected.scene(); - track_index - .and_then(|index|self.model.tracks.get_mut(index).map(|track|(index, track))) - .map(|(track_index, _)|scene_index - .and_then(|index|self.model.scenes.get_mut(index)) - .map(|scene|scene.clips[track_index] = None)); - } - pub fn phrase_put (&mut self) { - if let ArrangementEditorFocus::Clip(track, scene) = self.selected { - self.model.scenes[scene].clips[track] = Some( - self.model.phrase().clone() - ); - } - } - pub fn phrase_get (&mut self) { - if let ArrangementEditorFocus::Clip(track, scene) = self.selected { - if let Some(phrase) = &self.model.scenes[scene].clips[track] { - let mut phrases = self.model.phrases.write().unwrap(); - if let Some(index) = phrases.index_of(&*phrase.read().unwrap()) { - phrases.phrase = index; - } - } - } - } - pub fn phrase_next (&mut self) { - if let ArrangementEditorFocus::Clip(track, scene) = self.selected { - if let Some(ref mut phrase) = self.model.scenes[scene].clips[track] { - let phrases = self.model.phrases.read().unwrap(); - let index = phrases.index_of(&*phrase.read().unwrap()); - if let Some(index) = index { - if index < phrases.len().saturating_sub(1) { - *phrase = phrases[index + 1].clone(); - } - } - } - } - } - pub fn phrase_prev (&mut self) { - if let ArrangementEditorFocus::Clip(track, scene) = self.selected { - if let Some(ref mut phrase) = self.model.scenes[scene].clips[track] { - let phrases = self.model.phrases.read().unwrap(); - let index = phrases.index_of(&*phrase.read().unwrap()); - if let Some(index) = index { - if index > 0 { - *phrase = phrases[index - 1].clone(); - } - } - } - } + pub fn selected_phrase (&self) -> Option>> { + self.selected_scene()?.clips.get(self.selected.track()?)?.clone() } } diff --git a/crates/tek_tui/src/tui_arranger_cmd.rs b/crates/tek_tui/src/tui_arranger_cmd.rs index 69882540..e9049394 100644 --- a/crates/tek_tui/src/tui_arranger_cmd.rs +++ b/crates/tek_tui/src/tui_arranger_cmd.rs @@ -10,6 +10,13 @@ pub enum ArrangerViewCommand { EditPhrase(Option>>), } +#[derive(Clone)] +pub enum ArrangementEditorCommand { + Edit(ArrangementCommand), + Select(ArrangementEditorFocus), + Zoom(usize), +} + /// Handle top-level events in standalone arranger. impl Handle for ArrangerView { fn handle (&mut self, i: &TuiInput) -> Perhaps { @@ -52,10 +59,10 @@ impl InputToCommand> for ArrangerViewCommand { }, ArrangerViewFocus::Arrangement => match input.event() { key!(KeyCode::Char('e')) => EditPhrase( - view.arrangement.phrase() + view.selected_phrase() ), _ => Arrangement( - ArrangementEditorCommand::input_to_command(&view.arrangement, &input)? + ArrangementEditorCommand::input_to_command(&view, &input)? ) } } @@ -75,7 +82,7 @@ impl Command> for ArrangerViewCommand { Self::Transport(cmd) => delegate(cmd, Self::Transport, &mut view.sequencer.transport), Self::Arrangement(cmd) => - delegate(cmd, Self::Arrangement, &mut view.arrangement), + delegate(cmd, Self::Arrangement, &mut view), Self::EditPhrase(phrase) => { view.sequencer.editor.phrase = phrase.clone(); view.focus(ArrangerViewFocus::PhraseEditor); @@ -89,22 +96,8 @@ impl Command> for ArrangerViewCommand { } } -#[derive(Clone)] -pub enum ArrangementEditorCommand { - Edit(ArrangementCommand), - Select(ArrangementEditorFocus), - Zoom(usize), -} - -/// Handle events for arrangement. -impl Handle for ArrangementEditor { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - ArrangementEditorCommand::execute_with_state(self, from) - } -} - -impl InputToCommand> for ArrangementEditorCommand { - fn input_to_command (state: &ArrangementEditor, input: &TuiInput) -> Option { +impl InputToCommand> for ArrangementEditorCommand { + fn input_to_command (state: &ArrangerView, input: &TuiInput) -> Option { use ArrangementEditorCommand as Cmd; use ArrangementCommand as Edit; use ArrangementEditorFocus as Focus; @@ -217,8 +210,8 @@ impl InputToCommand> for ArrangementEditorCommand { } } -impl Command> for ArrangementEditorCommand { - fn execute (self, view: &mut ArrangementEditor) -> Perhaps { +impl Command> for ArrangementEditorCommand { + fn execute (self, view: &mut ArrangerView) -> Perhaps { match self { Self::Zoom(zoom) => { todo!(); @@ -233,3 +226,85 @@ impl Command> for ArrangementEditorCommand { Ok(None) } } + + //pub fn phrase_next (&mut self) { + //if let ArrangementEditorFocus::Clip(track, scene) = self.selected { + //if let Some(ref mut phrase) = self.model.scenes[scene].clips[track] { + //let phrases = self.model.phrases.read().unwrap(); + //let index = phrases.index_of(&*phrase.read().unwrap()); + //if let Some(index) = index { + //if index < phrases.len().saturating_sub(1) { + //*phrase = phrases[index + 1].clone(); + //} + //} + //} + //} + //} + //pub fn phrase_prev (&mut self) { + //if let ArrangementEditorFocus::Clip(track, scene) = self.selected { + //if let Some(ref mut phrase) = self.model.scenes[scene].clips[track] { + //let phrases = self.model.phrases.read().unwrap(); + //let index = phrases.index_of(&*phrase.read().unwrap()); + //if let Some(index) = index { + //if index > 0 { + //*phrase = phrases[index - 1].clone(); + //} + //} + //} + //} + //} + + //pub fn phrase_get (&mut self) { + //if let ArrangementEditorFocus::Clip(track, scene) = self.selected { + //if let Some(phrase) = &self.model.scenes[scene].clips[track] { + //let mut phrases = self.model.phrases.write().unwrap(); + //if let Some(index) = &*phrases.index_of(&*phrase.read().unwrap()) { + //self.model.phrase = index; + //} + //} + //} + //} + + ///// Focus the editor with the current phrase + //pub fn edit_phrase (&mut self) { + //if self.arrangement.selected.is_clip() && self.arrangement.phrase().is_none() { + //self.sequencer.phrases.append_new(None, Some(self.next_color().into())); + //self.arrangement.phrase_put(); + //} + //self.show_phrase(); + //self.focus(ArrangerViewFocus::PhraseEditor); + //self.sequencer.editor.entered = true; + //} + + //pub fn next_color (&self) -> ItemColor { + //if let ArrangementEditorFocus::Clip(track, scene) = self.arrangement.selected { + //let track_color = self.arrangement.model.tracks[track].color; + //let scene_color = self.arrangement.model.scenes[scene].color; + //track_color.mix(scene_color, 0.5).mix(ItemColor::random(), 0.25) + //} else { + //panic!("could not compute next color") + //} + //} + //pub fn phrase_del (&mut self) { + //let track_index = self.selected.track(); + //let scene_index = self.selected.scene(); + //track_index + //.and_then(|index|self.model.tracks.get_mut(index).map(|track|(index, track))) + //.map(|(track_index, _)|scene_index + //.and_then(|index|self.model.scenes.get_mut(index)) + //.map(|scene|scene.clips[track_index] = None)); + //} + //pub fn phrase_put (&mut self) { + //if let ArrangementEditorFocus::Clip(track, scene) = self.selected { + //self.model.scenes[scene].clips[track] = self.selected_phrase().clone(); + //} + //} + //pub fn selected_scene (&self) -> Option<&ArrangementScene> { + //self.selected.scene().map(|s|self.model.scenes.get(s)).flatten() + //} + //pub fn selected_scene_mut (&mut self) -> Option<&mut ArrangementScene> { + //self.selected.scene().map(|s|self.model.scenes.get_mut(s)).flatten() + //} + //pub fn selected_phrase (&self) -> Option>> { + //self.selected_scene()?.clips.get(self.selected.track()?)?.clone() + //} diff --git a/crates/tek_tui/src/tui_arranger_foc.rs b/crates/tek_tui/src/tui_arranger_foc.rs index cc3a71d3..488a39e1 100644 --- a/crates/tek_tui/src/tui_arranger_foc.rs +++ b/crates/tek_tui/src/tui_arranger_foc.rs @@ -25,18 +25,17 @@ impl FocusGrid for ArrangerView { fn focus_enter (&mut self) { let focused = self.focused(); if !self.entered { - self.entered = true; - use ArrangerViewFocus::*; - self.arrangement.entered = focused == Arrangement; - //self.sequencer.transport.entered = focused == Transport; - self.sequencer.editor.entered = focused == PhraseEditor; - self.sequencer.phrases.entered = focused == PhrasePool; + self.entered = + focused == ArrangerViewFocus::Arrangement; + self.sequencer.editor.entered = + focused == ArrangerViewFocus::PhraseEditor; + self.sequencer.phrases.entered = + focused == ArrangerViewFocus::PhrasePool; } } fn focus_exit (&mut self) { if self.entered { self.entered = false; - self.arrangement.entered = false; self.sequencer.editor.entered = false; self.sequencer.phrases.entered = false; } @@ -57,13 +56,15 @@ impl FocusGrid for ArrangerView { ] } fn update_focus (&mut self) { - use ArrangerViewFocus::*; let focused = self.focused(); - self.arrangement.focused = focused == Arrangement; - self.sequencer.transport.focused = focused == Transport; - self.sequencer.phrases.focused = focused == PhrasePool; - self.sequencer.editor.focused = focused == PhraseEditor; - //self.update_status(); + self.focused = + focused == ArrangerViewFocus::Arrangement; + self.sequencer.transport.focused = + focused == ArrangerViewFocus::Transport; + self.sequencer.phrases.focused = + focused == ArrangerViewFocus::PhrasePool; + self.sequencer.editor.focused = + focused == ArrangerViewFocus::PhraseEditor; } } diff --git a/crates/tek_tui/src/tui_arranger_hor.rs b/crates/tek_tui/src/tui_arranger_hor.rs index f37327f9..05582c9a 100644 --- a/crates/tek_tui/src/tui_arranger_hor.rs +++ b/crates/tek_tui/src/tui_arranger_hor.rs @@ -1,10 +1,10 @@ use crate::*; pub fn arranger_content_horizontal ( - state: &ArrangementEditor, + view: &ArrangerView, ) -> impl Widget + use<'_> { - let focused = state.focused; - let _tracks = state.model.tracks.as_slice(); + let focused = view.focused; + let _tracks = view.model.read().unwrap().arrangement.read().unwrap().tracks.as_slice(); lay!( focused.then_some(Background(TuiTheme::border_bg())), row!( @@ -158,8 +158,8 @@ pub fn arranger_content_horizontal ( }), // scenes CustomWidget::new(|_|{todo!()}, |to: &mut TuiOutput|{ - let selected = &state.selected; - let scenes = &state.model.scenes; + let selected = &view.selected; + let scenes = &view.model.scenes; let area = to.area(); let mut x2 = 0; let [x, y, _, height] = area; diff --git a/crates/tek_tui/src/tui_arranger_ver.rs b/crates/tek_tui/src/tui_arranger_ver.rs index ae0648b3..7dc02b6c 100644 --- a/crates/tek_tui/src/tui_arranger_ver.rs +++ b/crates/tek_tui/src/tui_arranger_ver.rs @@ -1,14 +1,26 @@ use crate::*; +fn track_widths (tracks: &[ArrangementTrack]) -> Vec<(usize, usize)> { + let mut widths = vec![]; + let mut total = 0; + for track in tracks.iter() { + let width = track.width; + widths.push((width, total)); + total += width; + } + widths.push((0, total)); + widths +} + pub fn arranger_content_vertical ( - view: &ArrangerView, + view: &ArrangerView, factor: usize ) -> impl Widget + use<'_> { - let tracks = view.arrangement.model.tracks.as_ref() as &[ArrangementTrack]; - let scenes = view.arrangement.model.scenes.as_ref(); - let cols = view.arrangement.track_widths(); + let tracks = view.model.tracks.as_slice(); + let scenes = view.model.scenes.as_slice(); + let cols = track_widths(tracks); let rows = ArrangementScene::ppqs(scenes, factor); - let bg = view.arrangement.color; + let bg = view.color; let clip_bg = TuiTheme::border_bg(); let sep_fg = TuiTheme::separator_fg(false); let header_h = 3u16;//5u16; @@ -132,8 +144,8 @@ pub fn arranger_content_vertical ( // cursor add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ let area = to.area(); - let focused = view.arrangement.focused; - let selected = view.arrangement.selected; + let focused = view.focused; + let selected = view.selected; let get_track_area = |t: usize| [ scenes_w + area.x() + cols[t].1 as u16, area.y(), cols[t].0 as u16, area.h(), @@ -185,7 +197,7 @@ pub fn arranger_content_vertical ( }) })) }).bg(bg.rgb); - let color = TuiTheme::title_fg(view.arrangement.focused); + let color = TuiTheme::title_fg(view.focused); let size = format!("{}x{}", view.size.w(), view.size.h()); let lower_right = TuiStyle::fg(size, color).pull_x(1).align_se().fill_xy(); lay!(arrangement, lower_right) diff --git a/crates/tek_tui/src/tui_sequencer.rs b/crates/tek_tui/src/tui_sequencer.rs index d4c63d15..05da766f 100644 --- a/crates/tek_tui/src/tui_sequencer.rs +++ b/crates/tek_tui/src/tui_sequencer.rs @@ -1,19 +1,80 @@ use crate::*; -use std::cmp::PartialEq; + +pub type SequencerApp = AppContainer< + Tui, + SequencerModel, + SequencerView, + SequencerViewCommand, + SequencerAudio, + SequencerStatusBar +>; + +impl SequencerApp { + pub fn run <'a> (jack: &Arc>) -> Usually { + let clock = Arc::new(Clock::from(Instant::default())); + + let transport = Arc::new(RwLock::new(tek_api::Transport { + metronome: false, + transport: jack.read().unwrap().transport(), + jack: jack.clone(), + clock: clock.clone() + })); + + let phrases = Arc::new(RwLock::new(PhrasePool { + phrases: vec![] // FIXME + })); + + let player = Arc::new(RwLock::new(MIDIPlayer::new(jack, &clock, "preview")?)); + + let model = Arc::new(RwLock::new(SequencerModel { + transport: transport.clone(), + phrases: phrases.clone(), + player: player.clone() + })); + + Ok(Self::new( + &model, + SequencerView::from(&model), + SequencerAudio(transport.clone(), player.clone()), + None, + None, + )) + } +} + +pub struct SequencerModel { + /// State of the JACK transport. + pub transport: Arc>, + /// State of the phrase pool. + pub phrases: Arc>, + /// State of the phrase player. + pub player: Arc>, +} + +impl From<&Arc>> for SequencerView { + fn from (model: &Arc>) -> Self { + Self { + split: 20, + transport: TransportView::from(&model.read().unwrap().transport), + phrases: PhrasePoolView::from(&model.read().unwrap().phrases), + editor: PhraseEditor::new(), + model: model.clone(), + } + } +} /// Root level object for standalone `tek_sequencer`. /// Also embeddable, in which case the `player` is used for preview. pub struct SequencerView { - /// Controls the JACK transport. + pub model: Arc>, + /// Displays the JACK transport. pub transport: TransportView, + /// Displays the phrase pool + pub phrases: PhrasePoolView, + /// Displays the phrase editor + pub editor: PhraseEditor, /// Width of phrase pool pub split: u16, - /// Pool of all phrases available to the sequencer - pub phrases: PhrasePoolView, - /// Phrase editor view - pub editor: PhraseEditor, - /// Phrase player - pub player: MIDIPlayer, } impl Content for SequencerView { diff --git a/crates/tek_tui/src/tui_transport.rs b/crates/tek_tui/src/tui_transport.rs index 5f8d9db6..187e7bad 100644 --- a/crates/tek_tui/src/tui_transport.rs +++ b/crates/tek_tui/src/tui_transport.rs @@ -1,16 +1,56 @@ use crate::*; use tek_api::Transport; +pub type TransportApp = AppContainer< + Tui, + Transport, + TransportView, + TransportViewCommand, + TransportAudio, + TransportStatusBar +>; + +impl TransportApp { + pub fn run <'a> (jack: &Arc>) -> Usually { + let model = Arc::new(RwLock::new(Transport { + metronome: false, + transport: jack.read().unwrap().transport(), + jack: jack.clone(), + clock: Arc::new(Clock::from(Instant::default())) + })); + + Ok(Self::new( + &model, + TransportView::from(&model), + TransportAudio(model.clone()), + None, + None, + )) + } +} + /// Stores and displays time-related info. #[derive(Debug)] pub struct TransportView { _engine: PhantomData, - pub model: Transport, + pub model: Arc>, pub focus: TransportViewFocus, pub focused: bool, pub size: Measure, } +impl From<&Arc>> for TransportView { + fn from (model: &Arc>) -> Self { + Self { + _engine: Default::default(), + focused: false, + focus: TransportViewFocus::PlayPause, + size: Measure::new(), + model: model.clone() + } + } +} + impl TransportView { pub fn new (jack: &Arc>, clock: Option<&Arc>) -> Self { Self { @@ -22,19 +62,18 @@ impl TransportView { metronome: false, transport: jack.read().unwrap().transport(), jack: jack.clone(), - clock: match clock { - Some(clock) => clock.clone(), - None => { - let timebase = Arc::new(Timebase::default()); - Arc::new(Clock { - playing: Some(TransportState::Stopped).into(), - quant: 24.into(), - sync: (timebase.ppq.get() * 4.).into(), - current: Instant::default(), - started: None.into(), - }) - } - }, + clock: if let Some(clock) = clock { + clock.clone() + } else { + let current = Instant::default(); + Arc::new(Clock { + playing: Some(TransportState::Stopped).into(), + started: None.into(), + quant: 24.into(), + sync: (current.timebase.ppq.get() * 4.).into(), + current, + }) + } } } } diff --git a/crates/tek_tui/src/tui_transport_cmd.rs b/crates/tek_tui/src/tui_transport_cmd.rs index 6e623f7f..867be8e9 100644 --- a/crates/tek_tui/src/tui_transport_cmd.rs +++ b/crates/tek_tui/src/tui_transport_cmd.rs @@ -17,36 +17,37 @@ impl InputToCommand> for TransportViewCommand { use TransportViewFocus as Focus; use FocusCommand as FocusCmd; use TransportCommand as Cmd; + let model = view.model.read().unwrap(); Some(match input.event() { key!(KeyCode::Left) => Self::Focus(FocusCmd::Prev), key!(KeyCode::Right) => Self::Focus(FocusCmd::Next), key!(KeyCode::Char('.')) => Self::Transport(match view.focus { - Focus::Bpm => Cmd::SetBpm(view.model.clock.timebase().bpm.get() + 1.0), - Focus::Quant => Cmd::SetQuant(next_note_length(view.model.clock.quant.get()as usize)as f64), - Focus::Sync => Cmd::SetSync(next_note_length(view.model.clock.sync.get()as usize)as f64+1.), + Focus::Bpm => Cmd::SetBpm(model.clock.timebase().bpm.get() + 1.0), + Focus::Quant => Cmd::SetQuant(next_note_length(model.clock.quant.get()as usize)as f64), + Focus::Sync => Cmd::SetSync(next_note_length(model.clock.sync.get()as usize)as f64+1.), Focus::PlayPause => {todo!()}, Focus::Clock => {todo!()} }), key!(KeyCode::Char(',')) => Self::Transport(match view.focus { - Focus::Bpm => Cmd::SetBpm(view.model.clock.timebase().bpm.get() - 1.0), - Focus::Quant => Cmd::SetQuant(prev_note_length(view.model.clock.quant.get()as usize)as f64), - Focus::Sync => Cmd::SetSync(prev_note_length(view.model.clock.sync.get()as usize)as f64+1.), + Focus::Bpm => Cmd::SetBpm(model.clock.timebase().bpm.get() - 1.0), + Focus::Quant => Cmd::SetQuant(prev_note_length(model.clock.quant.get()as usize)as f64), + Focus::Sync => Cmd::SetSync(prev_note_length(model.clock.sync.get()as usize)as f64+1.), Focus::PlayPause => {todo!()}, Focus::Clock => {todo!()} }), key!(KeyCode::Char('>')) => Self::Transport(match view.focus { - Focus::Bpm => Cmd::SetBpm(view.model.clock.timebase().bpm.get() + 0.001), - Focus::Quant => Cmd::SetQuant(next_note_length(view.model.clock.quant.get()as usize)as f64), - Focus::Sync => Cmd::SetSync(next_note_length(view.model.clock.sync.get()as usize)as f64+1.), + Focus::Bpm => Cmd::SetBpm(model.clock.timebase().bpm.get() + 0.001), + Focus::Quant => Cmd::SetQuant(next_note_length(model.clock.quant.get()as usize)as f64), + Focus::Sync => Cmd::SetSync(next_note_length(model.clock.sync.get()as usize)as f64+1.), Focus::PlayPause => {todo!()}, Focus::Clock => {todo!()} }), key!(KeyCode::Char('<')) => Self::Transport(match view.focus { - Focus::Bpm => Cmd::SetBpm(view.model.clock.timebase().bpm.get() - 0.001), - Focus::Quant => Cmd::SetQuant(prev_note_length(view.model.clock.quant.get()as usize)as f64), - Focus::Sync => Cmd::SetSync(prev_note_length(view.model.clock.sync.get()as usize)as f64+1.), + Focus::Bpm => Cmd::SetBpm(model.clock.timebase().bpm.get() - 0.001), + Focus::Quant => Cmd::SetQuant(prev_note_length(model.clock.quant.get()as usize)as f64), + Focus::Sync => Cmd::SetSync(prev_note_length(model.clock.sync.get()as usize)as f64+1.), Focus::PlayPause => {todo!()}, Focus::Clock => {todo!()} }), @@ -58,22 +59,37 @@ impl InputToCommand> for TransportViewCommand { impl Command> for TransportViewCommand { fn execute (self, view: &mut TransportView) -> Perhaps { + let model = view.model.read().unwrap(); Ok(Some(match self { Self::Focus(command) => Self::Focus({ use FocusCommand::*; match command { - Next => { todo!() }, - Prev => { todo!() }, - _ => { todo!() } + Next => { + todo!() + }, + Prev => { + todo!() + }, + _ => { + todo!() + } } }), Self::Transport(command) => Self::Transport({ use TransportCommand::*; match command { - SetBpm(bpm) => SetBpm(view.model.clock.timebase().bpm.set(bpm)), - SetQuant(quant) => SetQuant(view.model.clock.quant.set(quant)), - SetSync(sync) => SetSync(view.model.clock.sync.set(sync)), - _ => { todo!() } + SetBpm(bpm) => SetBpm( + model.clock.timebase().bpm.set(bpm) + ), + SetQuant(quant) => SetQuant( + model.clock.quant.set(quant) + ), + SetSync(sync) => SetSync( + model.clock.sync.set(sync) + ), + _ => { + todo!() + } } }), }))