diff --git a/crates/tek_api/src/api_clock.rs b/crates/tek_api/src/api_clock.rs index 1bd979e9..b023967e 100644 --- a/crates/tek_api/src/api_clock.rs +++ b/crates/tek_api/src/api_clock.rs @@ -1,6 +1,6 @@ use crate::*; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum ClockCommand { SetBpm(f64), SetQuant(f64), diff --git a/crates/tek_api/src/api_player.rs b/crates/tek_api/src/api_player.rs index 1169b6e6..7d51defc 100644 --- a/crates/tek_api/src/api_player.rs +++ b/crates/tek_api/src/api_player.rs @@ -7,27 +7,10 @@ pub trait HasPlayer: HasJack { pub trait PlayerApi: MidiInputApi + MidiOutputApi + Send + Sync {} -pub trait HasMidiBuffer { - fn midi_buffer (&self) -> &Vec>>; - fn midi_buffer_mut (&self) -> &mut Vec>>; - +pub trait HasPhrase: PlayheadApi { fn reset (&self) -> bool; fn reset_mut (&mut self) -> &mut bool; - /// Clear the section of the output buffer that we will be using, - /// emitting "all notes off" at start of buffer if requested. - fn clear (&mut self, scope: &ProcessScope, force_reset: bool) { - for frame in &mut self.midi_buffer_mut()[0..scope.n_frames() as usize] { - frame.clear(); - } - if self.reset() || force_reset { - all_notes_off(&mut self.midi_buffer_mut()); - *self.reset_mut() = false; - } - } -} - -pub trait HasPhrase: PlayheadApi + HasMidiBuffer { fn phrase (&self) -> &Option<(Instant, Option>>)>; fn phrase_mut (&self) @@ -54,7 +37,7 @@ pub trait HasPhrase: PlayheadApi + HasMidiBuffer { } } -pub trait MidiInputApi: PlayheadApi + HasMidiBuffer + HasPhrase { +pub trait MidiInputApi: PlayheadApi + HasPhrase { fn midi_ins (&self) -> &Vec>; fn midi_ins_mut (&self) -> &mut Vec>; fn has_midi_ins (&self) -> bool { @@ -80,7 +63,11 @@ pub trait MidiInputApi: PlayheadApi + HasMidiBuffer + HasPhrase { fn notes_in (&self) -> &Arc>; - fn record (&mut self, scope: &ProcessScope) { + fn record ( + &mut self, + scope: &ProcessScope, + midi_buf: &mut Vec>>, + ) { let sample0 = scope.last_frame_time() as usize; if let (true, Some((started, phrase))) = (self.is_rolling(), self.phrase()) { let start = started.sample.get() as usize; @@ -92,7 +79,7 @@ pub trait MidiInputApi: PlayheadApi + HasMidiBuffer + HasPhrase { for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { if let LiveEvent::Midi { message, .. } = event { if self.monitoring() { - self.midi_buffer_mut()[sample].push(bytes.to_vec()) + midi_buf[sample].push(bytes.to_vec()) } if self.recording() { if let Some(phrase) = phrase { @@ -117,12 +104,16 @@ pub trait MidiInputApi: PlayheadApi + HasMidiBuffer + HasPhrase { } } - fn monitor (&mut self, scope: &ProcessScope) { + fn monitor ( + &mut self, + scope: &ProcessScope, + midi_buf: &mut Vec>>, + ) { let mut notes_in = self.notes_in().write().unwrap(); for input in self.midi_ins_mut().iter() { for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { if let LiveEvent::Midi { message, .. } = event { - self.midi_buffer_mut()[sample].push(bytes.to_vec()); + midi_buf[sample].push(bytes.to_vec()); update_keys(&mut notes_in, &message); } } @@ -131,7 +122,7 @@ pub trait MidiInputApi: PlayheadApi + HasMidiBuffer + HasPhrase { } -pub trait MidiOutputApi: PlayheadApi + HasMidiBuffer + HasPhrase { +pub trait MidiOutputApi: PlayheadApi + HasPhrase { fn midi_outs (&self) -> &Vec>; fn midi_outs_mut (&mut self) -> &mut Vec>; @@ -144,6 +135,22 @@ pub trait MidiOutputApi: PlayheadApi + HasMidiBuffer + HasPhrase { self.midi_outs().len() > 0 } + /// Clear the section of the output buffer that we will be using, + /// emitting "all notes off" at start of buffer if requested. + fn clear ( + &mut self, + scope: &ProcessScope, + midi_buf: &mut Vec>>, + reset: bool + ) { + for frame in &mut midi_buf[0..scope.n_frames() as usize] { + frame.clear(); + } + if reset { + all_notes_off(midi_buf); + } + } + fn play ( &mut self, scope: &ProcessScope, @@ -298,26 +305,26 @@ pub struct PlayerAudio<'a, T: PlayerApi>( /// JACK process callback for a sequencer's phrase player/recorder. impl<'a, T: PlayerApi> Audio for PlayerAudio<'a, T> { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - let model = &mut self.0; - let note_buffer = &mut self.1; - let output_buffer = &mut self.2; + let model = &mut self.0; + let note_buf = &mut self.1; + let midi_buf = &mut self.2; // Clear output buffer(s) - model.clear(scope, false); + model.clear(scope, midi_buf, false); // Write chunk of phrase to output, handle switchover - if model.play(scope, note_buffer, output_buffer) { - model.switchover(scope, note_buffer, output_buffer); + if model.play(scope, note_buf, midi_buf) { + model.switchover(scope, note_buf, midi_buf); } if model.has_midi_ins() { if model.recording() || model.monitoring() { // Record and/or monitor input - model.record(scope) + model.record(scope, midi_buf) } else if model.has_midi_outs() && model.monitoring() { // Monitor input to output - model.monitor(scope) + model.monitor(scope, midi_buf) } } // Write to output port(s) - model.write(scope, output_buffer); + model.write(scope, midi_buf); Control::Continue } } diff --git a/crates/tek_api/src/api_playhead.rs b/crates/tek_api/src/api_playhead.rs index b7023667..9d369b26 100644 --- a/crates/tek_api/src/api_playhead.rs +++ b/crates/tek_api/src/api_playhead.rs @@ -1,6 +1,6 @@ use crate::*; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum PlayheadCommand { Play(Option), Pause(Option), diff --git a/crates/tek_api/src/api_track.rs b/crates/tek_api/src/api_track.rs index b1d920b9..2845ed3a 100644 --- a/crates/tek_api/src/api_track.rs +++ b/crates/tek_api/src/api_track.rs @@ -3,6 +3,18 @@ use crate::*; pub trait HasTracks: Send + Sync { fn tracks (&self) -> &Vec; fn tracks_mut (&mut self) -> &mut Vec; +} + +impl HasTracks for Vec { + fn tracks (&self) -> &Vec { + self + } + fn tracks_mut (&mut self) -> &mut Vec { + self + } +} + +pub trait ArrangerTracksApi: HasTracks { fn track_add (&mut self, name: Option<&str>, color: Option)-> Usually<&mut T>; fn track_del (&mut self, index: usize); fn track_default_name (&self) -> String { @@ -21,16 +33,6 @@ pub enum ArrangerTrackCommand { SetZoom(usize), } -//impl Command for ArrangerTrackCommand { - //fn execute (self, state: &mut T) -> Perhaps { - //match self { - //Self::Delete(index) => { state.track_del(index); }, - //_ => todo!() - //} - //Ok(None) - //} -//} - pub trait ArrangerTrackApi: PlayerApi + Send + Sync + Sized { /// Name of track fn name (&self) -> &Arc>; diff --git a/crates/tek_core/src/focus.rs b/crates/tek_core/src/focus.rs index 0325d97b..f18dc26e 100644 --- a/crates/tek_core/src/focus.rs +++ b/crates/tek_core/src/focus.rs @@ -12,7 +12,7 @@ pub enum FocusCommand { Exit } -impl Command for FocusCommand { +impl Command for FocusCommand { fn execute (self, state: &mut F) -> Perhaps { use FocusCommand::*; match self { @@ -24,93 +24,124 @@ impl Command for FocusCommand { Right => { state.focus_right(); }, Enter => { state.focus_enter(); }, Exit => { state.focus_exit(); }, + _ => {} } Ok(None) } } -pub trait FocusGrid { +pub trait HasFocus { + type Item: Copy + PartialEq + Debug; + fn focused (&self) -> Self::Item; + fn focus (&mut self, target: Self::Item); +} + +pub trait FocusOrder { + fn focus_next (&mut self); + fn focus_prev (&mut self); +} + +pub trait FocusEnter { type Item: Copy + PartialEq + Debug; - fn layout (&self) -> &[&[Self::Item]]; - fn cursor (&self) -> (usize, usize); - fn cursor_mut (&mut self) -> &mut (usize, usize); - fn update_focus (&mut self) {} fn focus_enter (&mut self) {} fn focus_exit (&mut self) {} - fn entered (&self) -> Option; + fn focus_entered (&self) -> Option; +} + +pub trait FocusGrid { + type Item: Copy + PartialEq + Debug; + fn focus_layout (&self) -> &[&[Self::Item]]; + fn focus_cursor (&self) -> (usize, usize); + fn focus_cursor_mut (&mut self) -> &mut (usize, usize); + fn focus_update (&mut self) {} + fn focus_up (&mut self) { + let layout = self.focus_layout(); + let (x, y) = self.focus_cursor(); + let next_y = if y == 0 { layout.len().saturating_sub(1) } else { y - 1 }; + let next_x = if layout[y].len() == layout[next_y].len() { x } else { + ((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize + }; + *self.focus_cursor_mut() = (next_x, next_y); + self.focus_update(); + } + fn focus_down (&mut self) { + let layout = self.focus_layout(); + let (x, y) = self.focus_cursor(); + let next_y = if y >= layout.len().saturating_sub(1) { 0 } else { y + 1 }; + let next_x = if layout[y].len() == layout[next_y].len() { x } else { + ((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize + }; + *self.focus_cursor_mut() = (next_x, next_y); + self.focus_update(); + } + fn focus_left (&mut self) { + let layout = self.focus_layout(); + let (x, y) = self.focus_cursor(); + let next_x = if x == 0 { layout[y].len().saturating_sub(1) } else { x - 1 }; + *self.focus_cursor_mut() = (next_x, y); + self.focus_update(); + } + fn focus_right (&mut self) { + let layout = self.focus_layout(); + let (x, y) = self.focus_cursor(); + let next_x = if x >= layout[y].len().saturating_sub(1) { 0 } else { x + 1 }; + *self.focus_cursor_mut() = (next_x, y); + self.focus_update(); + } +} + +impl HasFocus for U +where + T: Copy + PartialEq + Debug, + U: FocusGrid + FocusOrder, +{ + type Item = T; fn focused (&self) -> Self::Item { - let (x, y) = self.cursor(); - self.layout()[y][x] + let (x, y) = self.focus_cursor(); + self.focus_layout()[y][x] } fn focus (&mut self, target: Self::Item) { while self.focused() != target { self.focus_next() } } - fn focus_up (&mut self) { - let layout = self.layout(); - let (x, y) = self.cursor(); - let next_y = if y == 0 { layout.len().saturating_sub(1) } else { y - 1 }; - let next_x = if layout[y].len() == layout[next_y].len() { x } else { - ((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize - }; - *self.cursor_mut() = (next_x, next_y); - self.update_focus(); - } - fn focus_down (&mut self) { - let layout = self.layout(); - let (x, y) = self.cursor(); - let next_y = if y >= layout.len().saturating_sub(1) { 0 } else { y + 1 }; - let next_x = if layout[y].len() == layout[next_y].len() { x } else { - ((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize - }; - *self.cursor_mut() = (next_x, next_y); - self.update_focus(); - } - fn focus_left (&mut self) { - let layout = self.layout(); - let (x, y) = self.cursor(); - let next_x = if x == 0 { layout[y].len().saturating_sub(1) } else { x - 1 }; - *self.cursor_mut() = (next_x, y); - self.update_focus(); - } - fn focus_right (&mut self) { - let layout = self.layout(); - let (x, y) = self.cursor(); - let next_x = if x >= layout[y].len().saturating_sub(1) { 0 } else { x + 1 }; - *self.cursor_mut() = (next_x, y); - self.update_focus(); - } +} + +impl FocusOrder for U +where + T: Copy + PartialEq + Debug, + U: HasFocus + FocusGrid + FocusEnter +{ fn focus_next (&mut self) { let current = self.focused(); - let (x, y) = self.cursor(); - if x < self.layout()[y].len().saturating_sub(1) { + let (x, y) = self.focus_cursor(); + if x < self.focus_layout()[y].len().saturating_sub(1) { self.focus_right(); } else { self.focus_down(); - self.cursor_mut().0 = 0; + self.focus_cursor_mut().0 = 0; } if self.focused() == current { // FIXME: prevent infinite loop self.focus_next() } self.focus_exit(); - self.update_focus(); + self.focus_update(); } fn focus_prev (&mut self) { let current = self.focused(); - let (x, _) = self.cursor(); + let (x, _) = self.focus_cursor(); if x > 0 { self.focus_left(); } else { self.focus_up(); - let (_, y) = self.cursor(); - let next_x = self.layout()[y].len().saturating_sub(1); - self.cursor_mut().0 = next_x; + let (_, y) = self.focus_cursor(); + let next_x = self.focus_layout()[y].len().saturating_sub(1); + self.focus_cursor_mut().0 = next_x; } if self.focused() == current { // FIXME: prevent infinite loop self.focus_prev() } self.focus_exit(); - self.update_focus(); + self.focus_update(); } } diff --git a/crates/tek_core/src/space.rs b/crates/tek_core/src/space.rs index 776bf994..4e6fdeab 100644 --- a/crates/tek_core/src/space.rs +++ b/crates/tek_core/src/space.rs @@ -883,6 +883,7 @@ impl Measure { pub fn set_h (&self, h: impl Into) { self.2.store(h.into(), Ordering::Relaxed) } pub fn set_wh (&self, w: impl Into, h: impl Into) { self.set_w(w); self.set_h(h); } pub fn new () -> Self { Self(PhantomData::default(), 0.into(), 0.into()) } + pub fn format (&self) -> String { format!("{}x{}", self.w(), self.h()) } } impl Widget for Measure { diff --git a/crates/tek_tui/src/lib.rs b/crates/tek_tui/src/lib.rs index 27af547b..9473d1d3 100644 --- a/crates/tek_tui/src/lib.rs +++ b/crates/tek_tui/src/lib.rs @@ -13,6 +13,8 @@ use std::fmt::Debug; submod! { tui_arranger + tui_arranger_track + tui_arranger_scene //tui_mixer // TODO tui_phrase //tui_plugin // TODO @@ -24,9 +26,11 @@ submod! { //tui_sampler // TODO //tui_sampler_cmd tui_sequencer + tui_sequencer_cmd tui_status tui_theme tui_transport + tui_transport_cmd } pub struct AppView diff --git a/crates/tek_tui/src/tui_arranger.rs b/crates/tek_tui/src/tui_arranger.rs index dcbeb0ff..c7dde4c1 100644 --- a/crates/tek_tui/src/tui_arranger.rs +++ b/crates/tek_tui/src/tui_arranger.rs @@ -6,17 +6,25 @@ impl TryFrom<&Arc>> for ArrangerApp { Ok(Self::new(ArrangerView { 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(), - focused: false, entered: false, + quant: Default::default(), + sync: Default::default(), + splits: [20, 20], + note_buf: vec![], + midi_buf: vec![], }.into(), None, None)) } } @@ -28,9 +36,14 @@ pub type ArrangerApp = AppView< ArrangerStatusBar >; -impl Audio for ArrangerApp { +impl Audio for ArrangerApp { fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - ArrangerRefAudio(self.app).process(client, scope) + TracksAudio( + &mut self.app.tracks, + &mut self.app.note_buf, + &mut self.app.midi_buf, + Default::default(), + ).process(client, scope) } } @@ -78,39 +91,35 @@ impl InputToCommand> for ArrangerAppCommand { Self::App(Playhead(PlayheadCommand::Play(None))) }, _ => Self::App(match view.focused() { - Content(ArrangerViewFocus::Transport) => { - match TransportCommand::input_to_command(&view.app.transport, input)? { - Focus(command) => { + Content(ArrangerFocus::Transport) => { + use TransportCommand::{Clock, Playhead}; + match TransportCommand::input_to_command(view, input)? { + Clock(command) => { todo!() }, - App(Clock(command)) => { - todo!() - }, - App(Playhead(command)) => { + Playhead(command) => { todo!() }, } }, - Content(ArrangerViewFocus::PhraseEditor) => Editor( + Content(ArrangerFocus::PhraseEditor) => Editor( PhraseEditorCommand::input_to_command(&view.app.editor, input)? ), - Content(ArrangerViewFocus::PhrasePool) => match input.event() { + Content(ArrangerFocus::PhrasePool) => match input.event() { key!(KeyCode::Char('e')) => EditPhrase( - Some(view.app.phrases.phrase().clone()) + Some(view.app.phrase().clone()) ), _ => Phrases( - PhrasePoolViewCommand::input_to_command(&view.app.phrases, input)? + PhrasePoolViewCommand::input_to_command(view, input)? ) }, - Content(ArrangerViewFocus::Arranger) => { + Content(ArrangerFocus::Arranger) => { use ArrangerSelection as Select; use ArrangerTrackCommand as Track; use ArrangerClipCommand as Clip; use ArrangerSceneCommand as Scene; match input.event() { - key!(KeyCode::Char('e')) => EditPhrase( - view.selected_phrase() - ), + key!(KeyCode::Char('e')) => EditPhrase(view.phrase()), _ => match input.event() { // FIXME: boundary conditions @@ -250,7 +259,7 @@ impl Command> for ArrangerViewCommand { Select(selected) => { state.selected = selected; Ok(None) }, EditPhrase(phrase) => { state.editor.phrase = phrase.clone(); - state.focus(ArrangerViewFocus::PhraseEditor); + state.focus(ArrangerFocus::PhraseEditor); state.focus_enter(); Ok(None) } @@ -277,10 +286,10 @@ pub struct ArrangerView { selected: ArrangerSelection, mode: ArrangerMode, color: ItemColor, - editor: PhraseEditor, - focused: bool, entered: bool, size: Measure, + note_buf: Vec, + midi_buf: Vec>>, } impl HasJack for ArrangerView { @@ -332,15 +341,27 @@ impl HasTracks for ArrangerView { fn tracks_mut (&mut self) -> &mut Vec { &mut self.tracks } +} + +impl ArrangerTracksApi for ArrangerView { fn track_add (&mut self, name: Option<&str>, color: Option) -> Usually<&mut ArrangerTrack> { let name = name.map_or_else(||self.track_default_name(), |x|x.to_string()); let track = ArrangerTrack { - name: Arc::new(name.into()), - color: color.unwrap_or_else(||ItemColor::random()), - width: name.len() + 2, - //player: MIDIPlayer::new(&self.jack(), &self.clock(), name.as_str())?, + width: name.len() + 2, + name: Arc::new(name.into()), + color: color.unwrap_or_else(||ItemColor::random()), + midi_ins: vec![], + midi_outs: vec![], + reset: true, + recording: false, + monitoring: false, + overdub: false, + play_phrase: None, + next_phrase: None, + notes_in: RwLock::new([false;128]).into(), + notes_out: RwLock::new([false;128]).into(), }; self.tracks_mut().push(track); let index = self.tracks().len() - 1; @@ -387,7 +408,7 @@ pub enum ArrangerMode { /// Sections in the arranger app that may be focused #[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum ArrangerViewFocus { +pub enum ArrangerFocus { /// The transport (toolbar) is focused Transport, /// The arrangement (grid) is focused @@ -444,9 +465,9 @@ impl Content for ArrangerView { fn content (&self) -> impl Widget { Split::up( 1, - widget(&self.transport), + widget(&TransportRef(self)), Split::down( - self.split, + self.splits[0], lay!( Layers::new(move |add|{ match self.mode { @@ -471,9 +492,9 @@ impl Content for ArrangerView { .push_x(1), ), Split::right( - self.split, + self.splits[1], widget(&self.phrases), - widget(&self.editor), + widget(&PhraseEditorRef(self)), ) ) ) @@ -481,7 +502,7 @@ impl Content for ArrangerView { } /// General methods for arranger -impl ArrangerView { +impl ArrangerView { /// Focus the editor with the current phrase pub fn show_phrase (&mut self) { @@ -562,6 +583,30 @@ impl ArrangerView { } } +impl TransportViewState for ArrangerView { + fn focus (&self) -> TransportViewFocus { + self.focus + } + fn focused (&self) -> bool { + self.focused + } + fn transport_state (&self) -> Option { + *self.playing().read().unwrap() + } + fn bpm_value (&self) -> f64 { + self.bpm().get() + } + fn sync_value (&self) -> f64 { + self.sync().get() + } + fn format_beat (&self) -> String { + self.current().format_beat() + } + fn format_msu (&self) -> String { + self.current().usec.format_msu() + } +} + impl Audio for ArrangerView { #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { if self.process(client, scope) == Control::Quit { @@ -635,7 +680,7 @@ impl Audio for ArrangerView { //self.arrangement.phrase_put(); //} //self.show_phrase(); - //self.focus(ArrangerViewFocus::PhraseEditor); + //self.focus(ArrangerFocus::PhraseEditor); //self.editor.entered = true; //} @@ -672,18 +717,10 @@ impl Audio for ArrangerView { //self.selected_scene()?.clips.get(self.selected.track()?)?.clone() //} -/// Focus layout of arranger app -impl FocusGrid for ArrangerApp { - type Item = AppViewFocus; - fn cursor (&self) -> (usize, usize) { - self.cursor - } - fn cursor_mut (&mut self) -> &mut (usize, usize) { - &mut self.cursor - } +impl FocusEnter for ArrangerApp { fn focus_enter (&mut self) { use AppViewFocus::*; - use ArrangerViewFocus::*; + use ArrangerFocus::*; let focused = self.focused(); if !self.entered { self.entered = focused == Content(Arranger); @@ -698,16 +735,27 @@ impl FocusGrid for ArrangerApp { self.app.phrases.entered = false; } } - fn entered (&self) -> Option { + fn focus_entered (&self) -> Option { if self.entered { Some(self.focused()) } else { None } } - fn layout (&self) -> &[&[Self::Item]] { +} + +/// Focus layout of arranger app +impl FocusGrid for ArrangerApp { + type Item = AppViewFocus; + 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 AppViewFocus::*; - use ArrangerViewFocus::*; + use ArrangerFocus::*; &[ &[Menu, Menu ], &[Content(Transport), Content(Transport) ], @@ -715,19 +763,15 @@ impl FocusGrid for ArrangerApp { &[Content(PhrasePool), Content(PhraseEditor)], ] } - fn update_focus (&mut self) { + fn focus_update (&mut self) { use AppViewFocus::*; - use ArrangerViewFocus::*; + use ArrangerFocus::*; let focused = self.focused(); - self.app.focused = focused == Content(Arranger); - self.app.transport.focused = focused == Content(Transport); - self.app.phrases.focused = focused == Content(PhrasePool); - self.app.editor.focused = focused == Content(PhraseEditor); if let Some(mut status_bar) = self.status_bar { status_bar.update(&( self.focused(), self.app.selected, - self.app.editor.entered + focused == Content(PhraseEditor) && self.entered )) } } @@ -827,7 +871,7 @@ impl ArrangerSelection { impl StatusBar for ArrangerStatusBar { - type State = (AppViewFocus, ArrangerSelection, bool); + type State = (AppViewFocus, ArrangerSelection, bool); fn hotkey_fg () -> Color where Self: Sized { TuiTheme::hotkey_fg() @@ -836,15 +880,15 @@ impl StatusBar for ArrangerStatusBar { use AppViewFocus::*; if let Content(focused) = focused { *self = match focused { - ArrangerViewFocus::Transport => ArrangerStatusBar::Transport, - ArrangerViewFocus::Arranger => match selected { + ArrangerFocus::Transport => ArrangerStatusBar::Transport, + ArrangerFocus::Arranger => match selected { ArrangerSelection::Mix => ArrangerStatusBar::ArrangerMix, ArrangerSelection::Track(_) => ArrangerStatusBar::ArrangerTrack, ArrangerSelection::Scene(_) => ArrangerStatusBar::ArrangerScene, ArrangerSelection::Clip(_, _) => ArrangerStatusBar::ArrangerClip, }, - ArrangerViewFocus::PhrasePool => ArrangerStatusBar::PhrasePool, - ArrangerViewFocus::PhraseEditor => match entered { + ArrangerFocus::PhrasePool => ArrangerStatusBar::PhrasePool, + ArrangerFocus::PhraseEditor => match entered { true => ArrangerStatusBar::PhraseEdit, false => ArrangerStatusBar::PhraseView, }, @@ -1386,182 +1430,3 @@ pub fn arranger_content_horizontal ( ) ) } - -#[derive(Default, Debug, Clone)] -pub struct ArrangerScene { - /// Name of scene - pub name: Arc>, - /// Clips in scene, one per track - pub clips: Vec>>>, - /// Identifying color of scene - pub color: ItemColor, -} - -impl ArrangerSceneApi for ArrangerScene { - fn name (&self) -> &Arc> { - &self.name - } - fn clips (&self) -> &Vec>>> { - &self.clips - } - fn color (&self) -> ItemColor { - self.color - } -} - -#[derive(Debug)] -pub struct ArrangerTrack { - /// Name of track - pub name: Arc>, - /// Preferred width of track column - pub width: usize, - /// Identifying color of track - pub color: ItemColor, - /// 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>, -} - -impl ArrangerTrackApi for ArrangerTrack { - /// Name of track - fn name (&self) -> &Arc> { - &self.name - } - /// Preferred width of track column - fn width (&self) -> usize { - self.width - } - /// Preferred width of track column - fn width_mut (&mut self) -> &mut usize { - &mut self.width - } - /// Identifying color of track - fn color (&self) -> ItemColor { - self.color - } -} - -impl HasMidiBuffer for ArrangerTrack { - fn midi_buffer (&self) -> &Vec>> { - todo!() - } - fn midi_buffer_mut (&self) -> &mut Vec>> { - todo!() - } - fn reset (&self) -> bool { - todo!() - } - fn reset_mut (&mut self) -> &mut bool { - todo!() - } -} - -impl HasPhrase for ArrangerTrack { - fn phrase (&self) -> &Option<(Instant, Option>>)> { - todo!() - } - fn phrase_mut (&self) -> &mut Option<(Instant, Option>>)> { - todo!() - } - fn next_phrase (&self) -> &Option<(Instant, Option>>)> { - todo!() - } - fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option>>)> { - todo!() - } -} - -impl MidiInputApi for ArrangerTrack { - 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 ArrangerTrack { - 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 ArrangerTrack { - fn timebase (&self) -> &Arc { - todo!() - } - fn quant (&self) -> &Quantize { - todo!() - } - fn sync (&self) -> &LaunchSync { - todo!() - } -} - -impl PlayheadApi for ArrangerTrack { - fn current(&self) -> &Instant { - todo!() - } - fn transport(&self) -> &Transport { - todo!() - } - fn playing(&self) -> &RwLock> { - todo!() - } - fn started(&self) -> &RwLock> { - todo!() - } -} - -impl PlayerApi for ArrangerTrack {} diff --git a/crates/tek_tui/src/tui_arranger_scene.rs b/crates/tek_tui/src/tui_arranger_scene.rs new file mode 100644 index 00000000..062f19fd --- /dev/null +++ b/crates/tek_tui/src/tui_arranger_scene.rs @@ -0,0 +1,23 @@ +use crate::*; + +#[derive(Default, Debug, Clone)] +pub struct ArrangerScene { + /// Name of scene + pub name: Arc>, + /// Clips in scene, one per track + pub clips: Vec>>>, + /// Identifying color of scene + pub color: ItemColor, +} + +impl ArrangerSceneApi for ArrangerScene { + fn name (&self) -> &Arc> { + &self.name + } + fn clips (&self) -> &Vec>>> { + &self.clips + } + fn color (&self) -> ItemColor { + self.color + } +} diff --git a/crates/tek_tui/src/tui_arranger_track.rs b/crates/tek_tui/src/tui_arranger_track.rs new file mode 100644 index 00000000..f34a2539 --- /dev/null +++ b/crates/tek_tui/src/tui_arranger_track.rs @@ -0,0 +1,150 @@ +use crate::*; + +#[derive(Debug)] +pub struct ArrangerTrack { + /// Name of track + name: Arc>, + /// Preferred width of track column + width: usize, + /// Identifying color of track + color: ItemColor, + /// 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_ins: Vec>, + /// Play from current sequence to MIDI ports + midi_outs: Vec>, + /// Notes currently held at input + notes_in: Arc>, + /// Notes currently held at output + notes_out: Arc>, + ///// MIDI output buffer + //midi_note: Vec, + ///// MIDI output buffer + //midi_chunk: Vec>>, +} + +impl ArrangerTrackApi for ArrangerTrack { + /// Name of track + fn name (&self) -> &Arc> { + &self.name + } + /// Preferred width of track column + fn width (&self) -> usize { + self.width + } + /// Preferred width of track column + fn width_mut (&mut self) -> &mut usize { + &mut self.width + } + /// Identifying color of track + fn color (&self) -> ItemColor { + self.color + } +} + +impl HasPhrase for ArrangerTrack { + fn reset (&self) -> bool { + self.reset + } + fn reset_mut (&mut self) -> &mut bool { + &mut self.reset + } + fn phrase (&self) -> &Option<(Instant, Option>>)> { + todo!() + } + fn phrase_mut (&self) -> &mut Option<(Instant, Option>>)> { + todo!() + } + fn next_phrase (&self) -> &Option<(Instant, Option>>)> { + todo!() + } + fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option>>)> { + todo!() + } +} + +impl MidiInputApi for ArrangerTrack { + 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 ArrangerTrack { + 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 ArrangerTrack { + fn timebase (&self) -> &Arc { + todo!() + } + fn quant (&self) -> &Quantize { + todo!() + } + fn sync (&self) -> &LaunchSync { + todo!() + } +} + +impl PlayheadApi for ArrangerTrack { + fn current (&self) -> &Instant { + todo!() + } + fn transport (&self) -> &Transport { + todo!() + } + fn playing (&self) -> &RwLock> { + todo!() + } + fn started (&self) -> &RwLock> { + todo!() + } +} + +impl PlayerApi for ArrangerTrack {} + diff --git a/crates/tek_tui/src/tui_phrase.rs b/crates/tek_tui/src/tui_phrase.rs index c746bb50..30b3a029 100644 --- a/crates/tek_tui/src/tui_phrase.rs +++ b/crates/tek_tui/src/tui_phrase.rs @@ -27,22 +27,84 @@ pub struct PhraseEditor { pub notes_out: Arc>, /// Current position of global playhead pub now: Arc, - /// Width of notes area at last render - pub width: AtomicUsize, - /// Height of notes area at last render - pub height: AtomicUsize, + /// Width and height of notes area at last render + pub size: Measure } -impl Content for PhraseEditor { +impl Widget for PhraseEditor { + type Engine = Tui; + fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> { + PhraseEditorRef(&self, Default::default()).layout(to) + } + fn render (&self, to: &mut TuiOutput) -> Usually<()> { + PhraseEditorRef(&self, Default::default()).render(to) + } +} + +pub struct PhraseEditorRef<'a, T: PhraseEditorViewState>(pub &'a T); + +pub trait PhraseEditorViewState: Send + Sync { + fn focused (&self) -> bool; + fn entered (&self) -> bool; + fn keys (&self) -> &Buffer; + fn phrase (&self) -> &Option>>; + fn buffer (&self) -> &BigBuffer; + fn note_len (&self) -> usize; + fn note_axis (&self) -> &RwLock>; + fn time_axis (&self) -> &RwLock>; + fn size (&self) -> &Measure; + fn now (&self) -> &Arc; +} + +impl PhraseEditorViewState for PhraseEditor { + fn focused (&self) -> bool { + &self.focused + } + fn entered (&self) -> bool { + &self.entered + } + fn keys (&self) -> &Buffer { + &self.keys + } + fn phrase (&self) -> &Option>> { + &self.phrase + } + fn buffer (&self) -> &BigBuffer { + &self.buffer + } + fn note_len (&self) -> usize { + &self.note_len + } + fn note_axis (&self) -> &RwLock> { + &self.note_axis + } + fn time_axis (&self) -> &RwLock> { + &self.time_axis + } + fn size (&self) -> &Measure { + &self.size + } + fn now (&self) -> &Arc { + &self.now + } +} + +impl<'a, T: PhraseEditorViewState> Content for PhraseEditorRef<'a, T> { type Engine = Tui; fn content (&self) -> impl Widget { - let Self { focused, entered, keys, phrase, buffer, note_len, .. } = self; - let FixedAxis { - start: note_start, point: note_point, clamp: note_clamp - } = *self.note_axis.read().unwrap(); - let ScaledAxis { - start: time_start, point: time_point, clamp: time_clamp, scale: time_scale - } = *self.time_axis.read().unwrap(); + let phrase = self.0.phrase(); + let size = self.0.size(); + let focused = self.0.focused(); + let entered = self.0.entered(); + let keys = self.0.keys(); + let buffer = self.0.buffer(); + let note_len = self.0.note_len(); + let note_axis = self.0.note_axis(); + let time_axis = self.0.time_axis(); + let FixedAxis { start: note_start, point: note_point, clamp: note_clamp } + = *note_axis.read().unwrap(); + let ScaledAxis { start: time_start, point: time_point, clamp: time_clamp, scale: time_scale } + = *time_axis.read().unwrap(); //let color = Color::Rgb(0,255,0); //let color = phrase.as_ref().map(|p|p.read().unwrap().color.base.rgb).unwrap_or(color); let keys = CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_w(5))), move|to: &mut TuiOutput|{ @@ -59,9 +121,8 @@ impl Content for PhraseEditor { let notes = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{ let area = to.area(); let h = area.h() as usize; - self.height.store(h, Ordering::Relaxed); - self.width.store(area.w() as usize, Ordering::Relaxed); - let mut axis = self.note_axis.write().unwrap(); + size.set_wh(area.w(), h); + let mut axis = note_axis.write().unwrap(); if let Some(point) = axis.point { if point.saturating_sub(axis.start) > (h * 2).saturating_sub(1) { axis.start += 2; @@ -84,11 +145,11 @@ impl Content for PhraseEditor { }) }).fill_x(); let cursor = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{ - Ok(if *focused && *entered { + Ok(if focused && entered { let area = to.area(); if let (Some(time), Some(note)) = (time_point, note_point) { let x1 = area.x() + (time / time_scale) as u16; - let x2 = x1 + (self.note_len / time_scale) as u16; + let x2 = x1 + (note_len / time_scale) as u16; let y = area.y() + note.saturating_sub(note_start) as u16 / 2; let c = if note % 2 == 0 { "▀" } else { "▄" }; for x in x1..x2 { @@ -103,7 +164,7 @@ impl Content for PhraseEditor { |to:[u16;2]|Ok(Some(to.clip_h(1))), move|to: &mut TuiOutput|{ if let Some(_) = phrase { - let now = self.now.get() as usize; // TODO FIXME: self.now % phrase.read().unwrap().length; + let now = self.0.now().get() as usize; // TODO FIXME: self.now % phrase.read().unwrap().length; let time_clamp = time_clamp .expect("time_axis of sequencer expected to be clamped"); for x in 0..(time_clamp/time_scale).saturating_sub(time_start) { @@ -119,32 +180,28 @@ impl Content for PhraseEditor { Ok(()) } ).push_x(6).align_sw(); - let border_color = if *focused{Color::Rgb(100, 110, 40)}else{Color::Rgb(70, 80, 50)}; - let title_color = if *focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)}; + let border_color = if focused{Color::Rgb(100, 110, 40)}else{Color::Rgb(70, 80, 50)}; + let title_color = if focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)}; let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color)); let note_area = lay!(notes, cursor).fill_x(); let piano_roll = row!(keys, note_area).fill_x(); let content = piano_roll.bg(Color::Rgb(40, 50, 30)).border(border); let content = lay!(content, playhead); - let mut upper_left = format!("[{}] Sequencer", if *entered {"■"} else {" "}); + let mut upper_left = format!("[{}] Sequencer", if entered {"■"} else {" "}); if let Some(phrase) = phrase { upper_left = format!("{upper_left}: {}", phrase.read().unwrap().name); } - let mut lower_right = format!( - "┤{}x{}├", - self.width.load(Ordering::Relaxed), - self.height.load(Ordering::Relaxed), - ); + let mut lower_right = format!("┤{}├", size.format()); lower_right = format!("┤Zoom: {}├─{lower_right}", pulses_to_name(time_scale)); //lower_right = format!("Zoom: {} (+{}:{}*{}|{})", //pulses_to_name(time_scale), //time_start, time_point.unwrap_or(0), //time_scale, time_clamp.unwrap_or(0), //); - if *focused && *entered { + if focused && entered { lower_right = format!("┤Note: {} {}├─{lower_right}", - self.note_axis.read().unwrap().point.unwrap(), - pulses_to_name(*note_len)); + note_axis.read().unwrap().point.unwrap(), + pulses_to_name(note_len)); //lower_right = format!("Note: {} (+{}:{}|{}) {upper_right}", //pulses_to_name(*note_len), //note_start, diff --git a/crates/tek_tui/src/tui_sequencer.rs b/crates/tek_tui/src/tui_sequencer.rs index 972e9945..167acc77 100644 --- a/crates/tek_tui/src/tui_sequencer.rs +++ b/crates/tek_tui/src/tui_sequencer.rs @@ -1,5 +1,12 @@ use crate::*; +pub type SequencerApp = AppView< + E, + SequencerView, + SequencerViewCommand, + SequencerStatusBar, +>; + impl TryFrom<&Arc>> for SequencerApp { type Error = Box; fn try_from (jack: &Arc>) -> Usually { @@ -12,82 +19,15 @@ impl TryFrom<&Arc>> for SequencerApp { focused: false, focus: TransportViewFocus::PlayPause, size: Measure::new(), - phrases: PhrasePoolView::from(&model.phrases()), + phrases: vec![], editor: PhraseEditor::new(), }.into(), None, None)) } } -pub type SequencerApp = AppView< - E, - SequencerView, - SequencerViewCommand, - SequencerStatusBar, ->; - impl Handle for SequencerApp { fn handle (&mut self, i: &TuiInput) -> Perhaps { - SequencerAppCommand::execute_with_state(self, i) - } -} - -pub type SequencerAppCommand = AppViewCommand; - -#[derive(Clone, Debug, PartialEq)] -pub enum SequencerViewCommand { - Transport(TransportCommand), - Phrases(PhrasePoolViewCommand), - Editor(PhraseEditorCommand), -} - -impl InputToCommand> for SequencerAppCommand { - fn input_to_command (state: &SequencerApp, input: &TuiInput) -> Option { - use AppViewFocus::*; - use FocusCommand::*; - use SequencerViewCommand::*; - match input.event() { - key!(KeyCode::Tab) => Some(Self::Focus(Next)), - key!(Shift-KeyCode::Tab) => Some(Self::Focus(Prev)), - key!(KeyCode::BackTab) => Some(Self::Focus(Prev)), - key!(Shift-KeyCode::BackTab) => Some(Self::Focus(Prev)), - key!(KeyCode::Up) => Some(Self::Focus(Up)), - key!(KeyCode::Down) => Some(Self::Focus(Down)), - key!(KeyCode::Left) => Some(Self::Focus(Left)), - key!(KeyCode::Right) => Some(Self::Focus(Right)), - _ => Some(Self::App(match state.focused() { - Content(SequencerFocus::Transport) => - TransportCommand::input_to_command(&state.transport, input) - .map(Transport), - Content(SequencerFocus::PhrasePool) => - PhrasePoolViewCommand::input_to_command(&state.phrases, input) - .map(Phrases), - Content(SequencerFocus::PhraseEditor) => - PhraseEditorCommand::input_to_command(&state.editor, input) - .map(Editor), - _ => return None, - })) - } - } -} - -impl Command> for SequencerAppCommand { - fn execute (self, state: &mut SequencerApp) -> Perhaps { - use AppViewCommand::*; - match self { - Focus(cmd) => delegate(cmd, Focus, state), - App(cmd) => delegate(cmd, App, state), - } - } -} - -impl Command> for SequencerViewCommand { - fn execute (self, state: &mut SequencerApp) -> Perhaps { - use SequencerViewCommand::*; - match self { - Phrases(cmd) => delegate(cmd, Phrases, &mut state.phrases), - Editor(cmd) => delegate(cmd, Editor, &mut state.editor), - Transport(cmd) => delegate(cmd, Transport, &mut state.transport) - } + SequencerCommand::execute_with_state(self, i) } } @@ -154,7 +94,7 @@ impl Content for SequencerView { type Engine = Tui; fn content (&self) -> impl Widget { col!( - self.transport, + widget(&TransportRefView(self)), Split::right(20, widget(&self.phrases), widget(&self.editor) @@ -187,14 +127,11 @@ impl Content for SequencerStatusBar { } } -impl FocusGrid for SequencerApp { +impl HasFocus for SequencerApp { type Item = AppViewFocus; - fn cursor (&self) -> (usize, usize) { - self.cursor - } - fn cursor_mut (&mut self) -> &mut (usize, usize) { - &mut self.cursor - } +} + +impl FocusEnter for SequencerApp { fn focus_enter (&mut self) { let focused = self.focused(); if !self.entered { @@ -208,14 +145,24 @@ impl FocusGrid for SequencerApp { // TODO } } - fn entered (&self) -> Option { + fn focus_entered (&self) -> Option { if self.entered { Some(self.focused()) } else { None } } - fn layout (&self) -> &[&[Self::Item]] { +} + +impl FocusGrid for SequencerApp { + type Item = AppViewFocus; + 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 AppViewFocus::*; use SequencerFocus::*; &[ @@ -224,14 +171,14 @@ impl FocusGrid for SequencerApp { &[Content(PhrasePool), Content(PhraseEditor)], ] } - fn update_focus (&mut self) { + fn focus_update (&mut self) { // TODO } } impl HasJack for SequencerView { fn jack (&self) -> &Arc> { - self.transport.jack() + &self.jack } } @@ -244,106 +191,121 @@ impl HasPhrases for SequencerView { } } -impl HasMidiBuffer for SequencerView { - fn midi_buffer (&self) -> &Vec>> { - todo!() - } - fn midi_buffer_mut (&self) -> &mut Vec>> { - todo!() - } - fn reset (&self) -> bool { - todo!() - } - fn reset_mut (&mut self) -> &mut bool { - todo!() - } -} - impl HasPhrase for SequencerView { - fn phrase (&self) -> &Option<(Instant, Option>>)> { - todo!() - } - fn phrase_mut (&self) -> &mut Option<(Instant, Option>>)> { - todo!() - } - fn next_phrase (&self) -> &Option<(Instant, Option>>)> { - todo!() - } - fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option>>)> { - todo!() - } + fn reset (&self) -> bool { + self.reset + } + fn reset_mut (&mut self) -> &mut bool { + &mut self.reset + } + fn phrase (&self) -> &Option<(Instant, Option>>)> { + todo!() + } + fn phrase_mut (&self) -> &mut Option<(Instant, Option>>)> { + todo!() + } + fn next_phrase (&self) -> &Option<(Instant, Option>>)> { + todo!() + } + fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option>>)> { + todo!() + } } impl MidiInputApi for SequencerView { - 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!() - } + 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 SequencerView { - 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!() - } + 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 SequencerView { - fn timebase (&self) -> &Arc { - todo!() - } - fn quant (&self) -> &Quantize { - todo!() - } - fn sync (&self) -> &LaunchSync { - todo!() - } + fn timebase (&self) -> &Arc { + todo!() + } + fn quant (&self) -> &Quantize { + todo!() + } + fn sync (&self) -> &LaunchSync { + todo!() + } } impl PlayheadApi for SequencerView { - fn current(&self) -> &Instant { - todo!() - } - fn transport(&self) -> &Transport { - todo!() - } - fn playing(&self) -> &RwLock> { - todo!() - } - fn started(&self) -> &RwLock> { - todo!() - } + fn current(&self) -> &Instant { + todo!() + } + fn transport(&self) -> &Transport { + todo!() + } + fn playing(&self) -> &RwLock> { + todo!() + } + fn started(&self) -> &RwLock> { + todo!() + } } impl PlayerApi for SequencerView {} + +impl TransportViewState for SequencerView { + fn focus (&self) -> TransportViewFocus { + self.focus + } + fn focused (&self) -> bool { + self.focused + } + fn transport_state (&self) -> Option { + *self.playing().read().unwrap() + } + fn bpm_value (&self) -> f64 { + self.bpm().get() + } + fn sync_value (&self) -> f64 { + self.sync().get() + } + fn format_beat (&self) -> String { + self.current().format_beat() + } + fn format_msu (&self) -> String { + self.current().usec.format_msu() + } +} diff --git a/crates/tek_tui/src/tui_sequencer_cmd.rs b/crates/tek_tui/src/tui_sequencer_cmd.rs new file mode 100644 index 00000000..e8d80e22 --- /dev/null +++ b/crates/tek_tui/src/tui_sequencer_cmd.rs @@ -0,0 +1,73 @@ +use crate::*; + +pub type SequencerCommand = AppViewCommand; + +#[derive(Clone, Debug, PartialEq)] +pub enum SequencerViewCommand { + Transport(TransportCommand), + Phrases(PhrasePoolViewCommand), + Editor(PhraseEditorCommand), +} + +impl InputToCommand> for SequencerCommand { + fn input_to_command (state: &SequencerApp, input: &TuiInput) -> Option { + use AppViewFocus::*; + use FocusCommand::*; + use SequencerViewCommand::*; + match input.event() { + key!(KeyCode::Tab) => Some(Self::Focus(Next)), + key!(Shift-KeyCode::Tab) => Some(Self::Focus(Prev)), + key!(KeyCode::BackTab) => Some(Self::Focus(Prev)), + key!(Shift-KeyCode::BackTab) => Some(Self::Focus(Prev)), + key!(KeyCode::Up) => Some(Self::Focus(Up)), + key!(KeyCode::Down) => Some(Self::Focus(Down)), + key!(KeyCode::Left) => Some(Self::Focus(Left)), + key!(KeyCode::Right) => Some(Self::Focus(Right)), + _ => Some(Self::App(match state.focused() { + Content(SequencerFocus::Transport) => + TransportCommand::input_to_command(&state, input) + .map(Transport), + Content(SequencerFocus::PhrasePool) => + PhrasePoolViewCommand::input_to_command(&state.phrases, input) + .map(Phrases), + Content(SequencerFocus::PhraseEditor) => + PhraseEditorCommand::input_to_command(&state.editor, input) + .map(Editor), + _ => return None, + })) + } + } +} + +impl Command> for SequencerCommand { + fn execute (self, state: &mut SequencerApp) -> Perhaps { + use AppViewCommand::*; + match self { + Focus(cmd) => delegate(cmd, Focus, state), + App(cmd) => delegate(cmd, App, state), + } + } +} + +impl Command> for SequencerViewCommand { + fn execute (self, state: &mut SequencerApp) -> Perhaps { + use SequencerViewCommand::*; + match self { + Phrases(cmd) => delegate(cmd, Phrases, &mut state.phrases), + Editor(cmd) => delegate(cmd, Editor, &mut state.editor), + Transport(cmd) => delegate(cmd, Transport, &mut state.transport) + } + } +} + +impl TransportControl for SequencerApp { + fn bpm (&self) -> &BeatsPerMinute { + self.app.bpm() + } + fn quant (&self) -> &Quantize { + self.app.quant() + } + fn sync (&self) -> &LaunchSync { + self.app.sync() + } +} diff --git a/crates/tek_tui/src/tui_transport.rs b/crates/tek_tui/src/tui_transport.rs index f0c6fbf4..3142c4d7 100644 --- a/crates/tek_tui/src/tui_transport.rs +++ b/crates/tek_tui/src/tui_transport.rs @@ -1,5 +1,13 @@ use crate::*; +/// Root type of application. +pub type TransportApp = AppView< + E, + TransportView, + AppViewCommand, + TransportStatusBar +>; + /// Create app state from JACK handle. impl TryFrom<&Arc>> for TransportApp { type Error = Box; @@ -15,121 +23,6 @@ impl TryFrom<&Arc>> for TransportApp { } } -/// 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 as Focus; - use TransportCommand::{Clock, Playhead}; - let timebase = app.app.timebase(); - Some(match input.event() { - key!(Char('.')) => match app.focused() { - Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() + 1.0)), - Content(Focus::Quant) => Clock(SetQuant(next_note_length(app.app.quant().get()as usize)as f64)), - Content(Focus::Sync) => Clock(SetSync(next_note_length(app.app.sync().get()as usize)as f64+1.)), - Content(Focus::PlayPause) => Playhead(todo!()), - Content(Focus::Clock) => Playhead(todo!()), - _ => {todo!()} - }, - key!(KeyCode::Char(',')) => match app.focused() { - Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() - 1.0)), - Content(Focus::Quant) => Clock(SetQuant(prev_note_length(app.app.quant().get()as usize)as f64)), - Content(Focus::Sync) => Clock(SetSync(prev_note_length(app.app.sync().get()as usize)as f64+1.)), - Content(Focus::PlayPause) => Playhead(todo!()), - Content(Focus::Clock) => Playhead(todo!()), - _ => {todo!()} - }, - key!(KeyCode::Char('>')) => match app.focused() { - Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() + 0.001)), - Content(Focus::Quant) => Clock(SetQuant(next_note_length(app.app.quant().get()as usize)as f64)), - Content(Focus::Sync) => Clock(SetSync(next_note_length(app.app.sync().get()as usize)as f64+1.)), - Content(Focus::PlayPause) => Playhead(todo!()), - Content(Focus::Clock) => Playhead(todo!()), - _ => {todo!()} - }, - key!(KeyCode::Char('<')) => match app.focused() { - Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() - 0.001)), - Content(Focus::Quant) => Clock(SetQuant(prev_note_length(app.app.quant().get()as usize)as f64)), - Content(Focus::Sync) => Clock(SetSync(prev_note_length(app.app.sync().get()as usize)as f64+1.)), - Content(Focus::PlayPause) => Playhead(todo!()), - Content(Focus::Clock) => Playhead(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 TransportCommand::{Clock, Playhead}; - use ClockCommand::{SetBpm, SetQuant, SetSync}; - Ok(Some(match self { - Clock(SetBpm(bpm)) => Clock(SetBpm(state.timebase().bpm.set(bpm))), - Clock(SetQuant(quant)) => Clock(SetQuant(state.quant().set(quant))), - Clock(SetSync(sync)) => Clock(SetSync(state.sync().set(sync))), - _ => return Ok(None) - })) - } -} - /// Stores and displays time-related info. pub struct TransportView { _engine: PhantomData, @@ -153,6 +46,15 @@ pub struct TransportView { size: Measure, } +impl std::fmt::Debug for TransportView { + 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() + } +} + /// Which item of the transport toolbar is focused #[derive(Clone, Copy, Debug, PartialEq)] pub enum TransportViewFocus { @@ -195,13 +97,60 @@ impl TransportViewFocus { #[derive(Copy, Clone)] pub struct TransportStatusBar; -impl Content for TransportView { +impl Widget for TransportView { + type Engine = Tui; + fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> { + TransportRef(&self, Default::default()).layout(to) + } + fn render (&self, to: &mut TuiOutput) -> Usually<()> { + TransportRef(&self, Default::default()).render(to) + } +} + +pub struct TransportRef<'a, T: TransportViewState>(pub &'a T); + +pub trait TransportViewState: Send + Sync { + fn focus (&self) -> TransportViewFocus; + fn focused (&self) -> bool; + fn transport_state (&self) -> Option; + fn bpm_value (&self) -> f64; + fn sync_value (&self) -> f64; + fn format_beat (&self) -> String; + fn format_msu (&self) -> String; +} + +impl TransportViewState for TransportView { + fn focus (&self) -> TransportViewFocus { + self.focus + } + fn focused (&self) -> bool { + self.focused + } + fn transport_state (&self) -> Option { + *self.playing().read().unwrap() + } + fn bpm_value (&self) -> f64 { + self.bpm().get() + } + fn sync_value (&self) -> f64 { + self.sync().get() + } + fn format_beat (&self) -> String { + self.current().format_beat() + } + fn format_msu (&self) -> String { + self.current().usec.format_msu() + } +} + +impl<'a, T: TransportViewState> Content for TransportRef<'a, T> { type Engine = Tui; fn content (&self) -> impl Widget { + let state = self.0; lay!( - self.focus.wrap(self.focused, TransportViewFocus::PlayPause, &Styled( + state.focus().wrap(state.focused(), TransportViewFocus::PlayPause, &Styled( None, - match *self.playing().read().unwrap() { + match state.transport_state() { Some(TransportState::Rolling) => "▶ PLAYING", Some(TransportState::Starting) => "READY ...", Some(TransportState::Stopped) => "⏹ STOPPED", @@ -210,21 +159,21 @@ impl Content for TransportView { ).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.timebase().bpm.get(); + state.focus().wrap(state.focused(), TransportViewFocus::Bpm, &Outset::X(1u16, { + let bpm = state.bpm_value(); 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) + //let quant = state.focus().wrap(state.focused(), TransportViewFocus::Quant, &Outset::X(1u16, row! { + //"QUANT ", ppq_to_name(state.0.quant as usize) //})), - self.focus.wrap(self.focused, TransportViewFocus::Sync, &Outset::X(1u16, row! { - "SYNC ", pulses_to_name(self.sync().get() as usize) + state.focus().wrap(state.focused(), TransportViewFocus::Sync, &Outset::X(1u16, row! { + "SYNC ", pulses_to_name(state.sync_value() as usize) })) ).align_w().fill_x(), - self.focus.wrap(self.focused, TransportViewFocus::Clock, &{ - let time1 = self.current().format_beat(); - let time2 = self.current().usec.format_msu(); + state.focus().wrap(state.focused(), TransportViewFocus::Clock, &{ + let time1 = state.format_beat(); + let time2 = state.format_msu(); row!("B" ,time1.as_str(), " T", time2.as_str()).outset_x(1) }).align_e().fill_x(), @@ -234,7 +183,7 @@ impl Content for TransportView { impl Audio for TransportView { fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - self.model.process(client, scope) + PlayheadAudio(self).process(client, scope) } } @@ -256,35 +205,35 @@ impl Content for TransportStatusBar { } } -impl FocusGrid for TransportApp { +impl HasFocus for TransportApp { type Item = AppViewFocus; - fn cursor (&self) -> (usize, usize) { - self.cursor - } - fn cursor_mut (&mut self) -> &mut (usize, usize) { - &mut self.cursor - } +} + +impl FocusEnter for TransportApp { fn focus_enter (&mut self) { - let focused = self.focused(); - if !self.entered { - self.entered = true; - // TODO - } + self.entered = true; } fn focus_exit (&mut self) { - if self.entered { - self.entered = false; - // TODO - } + self.entered = false; } - fn entered (&self) -> Option { + fn focus_entered (&self) -> Option { if self.entered { Some(self.focused()) } else { None } } - fn layout (&self) -> &[&[Self::Item]] { +} + +impl FocusGrid for TransportApp { + type Item = AppViewFocus; + 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 AppViewFocus::*; use TransportViewFocus::*; &[ @@ -298,7 +247,7 @@ impl FocusGrid for TransportApp { ], ] } - fn update_focus (&mut self) { + fn focus_update (&mut self) { // TODO } } @@ -335,14 +284,3 @@ impl PlayheadApi for TransportView { &self.started } } - -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() - } -} diff --git a/crates/tek_tui/src/tui_transport_cmd.rs b/crates/tek_tui/src/tui_transport_cmd.rs new file mode 100644 index 00000000..642eae46 --- /dev/null +++ b/crates/tek_tui/src/tui_transport_cmd.rs @@ -0,0 +1,136 @@ +use crate::*; + +/// Handle input. +impl Handle for TransportApp { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + AppViewCommand::::execute_with_state(self, from) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum TransportCommand { + Clock(ClockCommand), + Playhead(PlayheadCommand), +} + +impl InputToCommand> for AppViewCommand { + 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, input).map(App)? + }) + } +} + +pub trait TransportControl: FocusGrid> { + fn quant (&self) -> &Quantize; + fn bpm (&self) -> &BeatsPerMinute; + fn next_quant (&self) -> f64 { + next_note_length(self.quant().get() as usize) as f64 + } + fn prev_quant (&self) -> f64 { + prev_note_length(self.quant().get() as usize) as f64 + } + fn sync (&self) -> &LaunchSync; + fn next_sync (&self) -> f64 { + next_note_length(self.sync().get() as usize) as f64 + } + fn prev_sync (&self) -> f64 { + prev_note_length(self.sync().get() as usize) as f64 + } +} + +impl TransportControl for TransportApp { + fn bpm (&self) -> &BeatsPerMinute { + self.app.bpm() + } + fn quant (&self) -> &Quantize { + self.app.quant() + } + fn sync (&self) -> &LaunchSync { + self.app.sync() + } +} + +impl InputToCommand for TransportCommand { + fn input_to_command (state: &T, input: &TuiInput) -> Option { + use KeyCode::Char; + use AppViewFocus::Content; + use ClockCommand::{SetBpm, SetQuant, SetSync}; + use TransportViewFocus as Focus; + use TransportCommand::{Clock, Playhead}; + let focused = state.focused(); + Some(match input.event() { + key!(Char('.')) => match focused { + Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() + 1.0)), + Content(Focus::Quant) => Clock(SetQuant(state.next_quant())), + Content(Focus::Sync) => Clock(SetSync(state.next_sync())), + Content(Focus::PlayPause) => Playhead(todo!()), + Content(Focus::Clock) => Playhead(todo!()), + _ => {todo!()} + }, + key!(KeyCode::Char(',')) => match focused { + Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() - 1.0)), + Content(Focus::Quant) => Clock(SetQuant(state.prev_quant())), + Content(Focus::Sync) => Clock(SetSync(state.prev_sync())), + Content(Focus::PlayPause) => Playhead(todo!()), + Content(Focus::Clock) => Playhead(todo!()), + _ => {todo!()} + }, + key!(KeyCode::Char('>')) => match focused { + Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() + 0.001)), + Content(Focus::Quant) => Clock(SetQuant(state.next_quant())), + Content(Focus::Sync) => Clock(SetSync(state.next_sync())), + Content(Focus::PlayPause) => Playhead(todo!()), + Content(Focus::Clock) => Playhead(todo!()), + _ => {todo!()} + }, + key!(KeyCode::Char('<')) => match focused { + Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() - 0.001)), + Content(Focus::Quant) => Clock(SetQuant(state.prev_quant())), + Content(Focus::Sync) => Clock(SetSync(state.prev_sync())), + Content(Focus::PlayPause) => Playhead(todo!()), + Content(Focus::Clock) => Playhead(todo!()), + _ => {todo!()} + }, + _ => return None + }) + } +} + +impl Command> for AppViewCommand { + 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, &mut state.app)? { + 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 T) -> Perhaps { + use TransportCommand::{Clock, Playhead}; + use ClockCommand::{SetBpm, SetQuant, SetSync}; + Ok(Some(match self { + Clock(SetBpm(bpm)) => Clock(SetBpm(state.bpm().set(bpm))), + Clock(SetQuant(quant)) => Clock(SetQuant(state.quant().set(quant))), + Clock(SetSync(sync)) => Clock(SetSync(state.sync().set(sync))), + _ => return Ok(None) + })) + } +}