use crate::*; /// Create app state from JACK handle. impl TryFrom<&Arc>> for TransportApp { type Error = Box; fn try_from (jack: &Arc>) -> Usually { Ok(Self::new(TransportView { metronome: false, transport: jack.read().unwrap().transport(), jack: jack.clone(), clock: Arc::new(Clock::from(Instant::default())), focused: false, focus: TransportViewFocus::PlayPause, size: Measure::new(), }.into(), None, None)) } } /// Root type of application. pub type TransportApp = AppView< E, TransportView, TransportAppCommand, TransportStatusBar >; /// Handle input. impl Handle for TransportApp { fn handle (&mut self, from: &TuiInput) -> Perhaps { TransportAppCommand::execute_with_state(self, from) } } pub type TransportAppCommand = AppViewCommand; #[derive(Clone, Debug)] pub enum TransportCommand { Clock(ClockCommand), Playhead(PlayheadCommand), } impl InputToCommand> for TransportAppCommand { fn input_to_command (app: &TransportApp, input: &TuiInput) -> Option { use KeyCode::{Left, Right}; use FocusCommand::{Prev, Next}; use AppViewCommand::{Focus, App}; Some(match input.event() { key!(Left) => Focus(Prev), key!(Right) => Focus(Next), _ => TransportCommand::input_to_command(app.app, input).map(App) }) } } impl InputToCommand> for TransportCommand { fn input_to_command (app: &TransportApp, input: &TuiInput) -> Option { use KeyCode::Char; use AppViewFocus::Content; use ClockCommand::{SetBpm, SetQuant, SetSync}; use TransportViewFocus::{Bpm, Quant, Sync, PlayPause, Clock}; let clock = app.app.model.clock(); Some(match input.event() { key!(Char('.')) => match app.focused() { Content(Bpm) => SetBpm(clock.timebase().bpm.get() + 1.0), Content(Quant) => SetQuant(next_note_length(clock.quant.get()as usize)as f64), Content(Sync) => SetSync(next_note_length(clock.sync.get()as usize)as f64+1.), Content(PlayPause) => {todo!()}, Content(Clock) => {todo!()}, _ => {todo!()} }, key!(KeyCode::Char(',')) => match app.focused() { Content(Bpm) => SetBpm(clock.timebase().bpm.get() - 1.0), Content(Quant) => SetQuant(prev_note_length(clock.quant.get()as usize)as f64), Content(Sync) => SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.), Content(PlayPause) => {todo!()}, Content(Clock) => {todo!()} _ => {todo!()} }, key!(KeyCode::Char('>')) => match app.focused() { Content(Bpm) => SetBpm(clock.timebase().bpm.get() + 0.001), Content(Quant) => SetQuant(next_note_length(clock.quant.get()as usize)as f64), Content(Sync) => SetSync(next_note_length(clock.sync.get()as usize)as f64+1.), Content(PlayPause) => {todo!()}, Content(Clock) => {todo!()} _ => {todo!()} }, key!(KeyCode::Char('<')) => match app.focused() { Content(Bpm) => SetBpm(clock.timebase().bpm.get() - 0.001), Content(Quant) => SetQuant(prev_note_length(clock.quant.get()as usize)as f64), Content(Sync) => SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.), Content(PlayPause) => {todo!()}, Content(Clock) => {todo!()} _ => {todo!()} }, _ => return None }) } } impl Command> for TransportAppCommand { fn execute (self, state: &mut TransportApp) -> Perhaps { use AppViewCommand::{Focus, App}; use FocusCommand::{Next, Prev}; Ok(Some(match self { App(command) => if let Some(undo) = TransportCommand::execute(command, state)? { App(undo) } else { return Ok(None) }, Focus(command) => Focus(match command { Next => { todo!() }, Prev => { todo!() }, _ => { todo!() } }), _ => todo!() })) } } impl Command> for TransportCommand { fn execute (self, state: &mut TransportApp) -> Perhaps { use ClockCommand::{SetBpm, SetQuant, SetSync}; let clock = state.app.model.clock(); Ok(Some(match self { SetBpm(bpm) => SetBpm(clock.timebase().bpm.set(bpm)), SetQuant(quant) => SetQuant(clock.quant.set(quant)), SetSync(sync) => SetSync(clock.sync.set(sync)), _ => return Ok(None) })) } } /// Stores and displays time-related info. #[derive(Debug)] pub struct TransportView { _engine: PhantomData, 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: TransportViewFocus, focused: bool, size: Measure, } /// Which item of the transport toolbar is focused #[derive(Clone, Copy, Debug, PartialEq)] pub enum TransportViewFocus { Bpm, Sync, PlayPause, Clock, Quant, } 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) } } #[derive(Copy, Clone)] pub struct TransportStatusBar; impl Content for TransportView { type Engine = Tui; fn content (&self) -> impl Widget { lay!( self.focus.wrap(self.focused, TransportViewFocus::PlayPause, &Styled( None, match *self.model.clock().playing.read().unwrap() { Some(TransportState::Rolling) => "▶ PLAYING", Some(TransportState::Starting) => "READY ...", Some(TransportState::Stopped) => "⏹ STOPPED", _ => unreachable!(), } ).min_xy(11, 2).push_x(1)).align_x().fill_x(), row!( self.focus.wrap(self.focused, TransportViewFocus::Bpm, &Outset::X(1u16, { let bpm = self.model.clock().timebase().bpm.get(); row! { "BPM ", format!("{}.{:03}", bpm as usize, (bpm * 1000.0) % 1000.0) } })), //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, TransportViewFocus::Sync, &Outset::X(1u16, row! { "SYNC ", pulses_to_name(self.model.clock().sync.get() as usize) })) ).align_w().fill_x(), self.focus.wrap(self.focused, TransportViewFocus::Clock, &{ let time1 = self.model.clock().current.format_beat(); let time2 = self.model.clock().current.usec.format_msu(); row!("B" ,time1.as_str(), " T", time2.as_str()).outset_x(1) }).align_e().fill_x(), ).fill_x().bg(Color::Rgb(40, 50, 30)) } } impl Audio for TransportView { fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { self.model.process(client, scope) } } 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!(); "" } } impl FocusGrid for TransportApp { type Item = AppViewFocus; 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]] { use AppViewFocus::*; use TransportViewFocus::*; &[ &[Menu], &[ Content(Bpm), Content(Sync), Content(PlayPause), Content(Clock), Content(Quant), ], ] } fn update_focus (&mut self) { // TODO } } impl HasJack for TransportView { fn jack (&self) -> &Arc> { &self.jack } } impl HasClock for TransportView { fn clock (&self) -> &Arc { &self.clock } } impl std::fmt::Debug for TransportView { fn fmt (&self, f: &mut std::fmt::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() } }