diff --git a/crates/tek_core/src/space.rs b/crates/tek_core/src/space.rs index 5f5fd627..31d68c8e 100644 --- a/crates/tek_core/src/space.rs +++ b/crates/tek_core/src/space.rs @@ -33,33 +33,49 @@ impl Coordinate for T where T: Send + Sync + Copy + Into {} +#[derive(Debug)] pub struct FixedAxis { pub start: T, pub point: Option, pub clamp: Option, } + +#[derive(Debug)] pub struct ScaledAxis { pub start: T, pub scale: T, pub point: Option, pub clamp: Option, } + macro_rules! impl_axis_common { ($A:ident $T:ty) => { impl $A<$T> { + #[inline] pub fn start_plus (&mut self, n: $T) -> $T { + (self.start + n).min(self.clamp.unwrap_or(<$T>::MAX)) + } #[inline] pub fn start_inc (&mut self, n: $T) -> $T { - self.start = (self.start + n).min(self.clamp.unwrap_or(<$T>::MAX)); + self.start = self.start_plus(n); self.start } + #[inline] pub fn start_minus (&mut self, n: $T) -> $T { + self.start.saturating_sub(n) + } #[inline] pub fn start_dec (&mut self, n: $T) -> $T { - self.start = self.start.saturating_sub(n); + self.start = self.start_minus(n); self.start } + #[inline] pub fn point_plus (&mut self, n: $T) -> Option<$T> { + self.point.map(|p|(p + n).min(self.clamp.unwrap_or(<$T>::MAX))) + } #[inline] pub fn point_inc (&mut self, n: $T) -> Option<$T> { - self.point = self.point.map(|p|(p + n).min(self.clamp.unwrap_or(<$T>::MAX))); + self.point = self.point_plus(n); self.point } + #[inline] pub fn point_minus (&mut self, n: $T) -> Option<$T> { + self.point.map(|p|p.saturating_sub(n)) + } #[inline] pub fn point_dec (&mut self, n: $T) -> Option<$T> { - self.point = self.point.map(|p|p.saturating_sub(n)); + self.point = self.point_minus(n); self.point } } @@ -871,9 +887,17 @@ impl, B: Widget> Widget for Split(PhantomData, AtomicUsize, AtomicUsize); +impl std::fmt::Debug for Measure { + fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("Measure") + .field("width", &self.0) + .field("height", &self.1) + .finish() + } +} + impl Measure { pub fn w (&self) -> usize { self.1.load(Ordering::Relaxed) } pub fn h (&self) -> usize { self.2.load(Ordering::Relaxed) } diff --git a/crates/tek_tui/src/lib.rs b/crates/tek_tui/src/lib.rs index 9632a96d..c94c97f2 100644 --- a/crates/tek_tui/src/lib.rs +++ b/crates/tek_tui/src/lib.rs @@ -12,14 +12,16 @@ pub(crate) use std::fs::read_dir; use std::fmt::Debug; submod! { - tui_apis + tui_apps tui_command tui_content tui_control + tui_debug tui_focus tui_handle tui_init tui_input + tui_impls tui_jack tui_menu tui_model diff --git a/crates/tek_tui/src/tui_apis.rs b/crates/tek_tui/src/tui_apis.rs deleted file mode 100644 index 9472eb80..00000000 --- a/crates/tek_tui/src/tui_apis.rs +++ /dev/null @@ -1,206 +0,0 @@ -use crate::*; - -impl PhrasesTui { - pub fn new (phrases: Vec>>) -> Self { - Self { - scroll: 0, - phrase: 0, - mode: None, - focused: false, - entered: false, - phrases, - } - } -} -impl PhraseTui { - pub fn new () -> Self { - Self { - phrase: None, - note_len: 24, - notes_in: Arc::new(RwLock::new([false;128])), - notes_out: Arc::new(RwLock::new([false;128])), - keys: keys_vert(), - buffer: Default::default(), - focused: false, - entered: false, - mode: false, - now: Arc::new(0.into()), - size: Measure::new(), - //width: 0.into(), - //height: 0.into(), - note_axis: RwLock::new(FixedAxis { - start: 12, - point: Some(36), - clamp: Some(127) - }), - time_axis: RwLock::new(ScaledAxis { - start: 00, - point: Some(00), - clamp: Some(000), - scale: 24 - }), - } - } -} - -//pub fn arranger_menu_bar () -> MenuBar { - //use ArrangerCommand as Cmd; - //use ArrangerCommand as Edit; - //use ArrangerSelection as Focus; - //use ArrangerTrackCommand as Track; - //use ArrangerClipCommand as Clip; - //use ArrangerSceneCommand as Scene; - //use TransportCommand as Transport; - //MenuBar::new() - //.add({ - //use ArrangerCommand::*; - //Menu::new("File") - //.cmd("n", "New project", ArrangerViewCommand::Arranger(New)) - //.cmd("l", "Load project", ArrangerViewCommand::Arranger(Load)) - //.cmd("s", "Save project", ArrangerViewCommand::Arranger(Save)) - //}) - //.add({ - //Menu::new("Transport") - //.cmd("p", "Play", TransportCommand::Transport(Play(None))) - //.cmd("P", "Play from start", TransportCommand::Transport(Play(Some(0)))) - //.cmd("s", "Pause", TransportCommand::Transport(Stop(None))) - //.cmd("S", "Stop and rewind", TransportCommand::Transport(Stop(Some(0)))) - //}) - //.add({ - //use ArrangerCommand::*; - //Menu::new("Track") - //.cmd("a", "Append new", ArrangerViewCommand::Arranger(AddTrack)) - //.cmd("i", "Insert new", ArrangerViewCommand::Arranger(AddTrack)) - //.cmd("n", "Rename", ArrangerViewCommand::Arranger(AddTrack)) - //.cmd("d", "Delete", ArrangerViewCommand::Arranger(AddTrack)) - //.cmd(">", "Move up", ArrangerViewCommand::Arranger(AddTrack)) - //.cmd("<", "Move down", ArrangerViewCommand::Arranger(AddTrack)) - //}) - //.add({ - //use ArrangerCommand::*; - //Menu::new("Scene") - //.cmd("a", "Append new", ArrangerViewCommand::Arranger(AddScene)) - //.cmd("i", "Insert new", ArrangerViewCommand::Arranger(AddTrack)) - //.cmd("n", "Rename", ArrangerViewCommand::Arranger(AddTrack)) - //.cmd("d", "Delete", ArrangerViewCommand::Arranger(AddTrack)) - //.cmd(">", "Move up", ArrangerViewCommand::Arranger(AddTrack)) - //.cmd("<", "Move down", ArrangerViewCommand::Arranger(AddTrack)) - //}) - //.add({ - //use PhraseRenameCommand as Rename; - //use PhraseLengthCommand as Length; - //Menu::new("Phrase") - //.cmd("a", "Append new", PhrasePoolCommand::Phrases(Append)) - //.cmd("i", "Insert new", PhrasePoolCommand::Phrases(Insert)) - //.cmd("n", "Rename", PhrasePoolCommand::Phrases(Rename(Rename::Begin))) - //.cmd("t", "Set length", PhrasePoolCommand::Phrases(Length(Length::Begin))) - //.cmd("d", "Delete", PhrasePoolCommand::Phrases(Delete)) - //.cmd("l", "Load from MIDI...", PhrasePoolCommand::Phrases(Import)) - //.cmd("s", "Save to MIDI...", PhrasePoolCommand::Phrases(Export)) - //.cmd(">", "Move up", PhrasePoolCommand::Phrases(MoveUp)) - //.cmd("<", "Move down", PhrasePoolCommand::Phrases(MoveDown)) - //}) -//} - - //pub fn phrase_next (&mut self) { - //if let ArrangerSelection::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 ArrangerSelection::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 ArrangerSelection::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.phrases.append_new(None, Some(self.next_color().into())); - //self.arrangement.phrase_put(); - //} - //self.show_phrase(); - //self.focus(ArrangerFocus::PhraseEditor); - //self.editor.entered = true; - //} - - //pub fn next_color (&self) -> ItemColor { - //if let ArrangerSelection::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 ArrangerSelection::Clip(track, scene) = self.selected { - //self.model.scenes[scene].clips[track] = self.selected_phrase().clone(); - //} - //} - //pub fn selected_scene (&self) -> Option<&ArrangerScene> { - //self.selected.scene().map(|s|self.model.scenes.get(s)).flatten() - //} - //pub fn selected_scene_mut (&mut self) -> Option<&mut ArrangerScene> { - //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() - //} - - - //pub fn is_first_row (&self) -> bool { - //let selected = self.selected; - //selected.is_mix() || selected.is_track() - //} - - //pub fn is_last_row (&self) -> bool { - //let selected = self.selected; - //(self.scenes().len() == 0 && (selected.is_mix() || selected.is_track())) || match selected { - //ArrangerSelection::Scene(s) => s == self.scenes().len() - 1, - //ArrangerSelection::Clip(_, s) => s == self.scenes().len() - 1, - //_ => false - //} - //} - //pub fn index_before (&self, index: usize) -> usize { - //index.overflowing_sub(1).0.min(self.len() - 1) - //} - //pub fn index_after (&self, index: usize) -> usize { - //(index + 1) % self.len() - //} diff --git a/crates/tek_tui/src/tui_apps.rs b/crates/tek_tui/src/tui_apps.rs new file mode 100644 index 00000000..97216b12 --- /dev/null +++ b/crates/tek_tui/src/tui_apps.rs @@ -0,0 +1,44 @@ +use crate::*; + +/// Stores and displays time-related info. +pub struct TransportTui { + pub(crate) jack: Arc>, + pub(crate) state: TransportModel, + pub(crate) size: Measure, + pub(crate) cursor: (usize, usize), +} + +/// Root view for standalone `tek_sequencer`. +pub struct SequencerTui { + pub(crate) jack: Arc>, + pub(crate) transport: TransportModel, + pub(crate) phrases: PhrasesModel, + pub(crate) player: PhrasePlayerModel, + pub(crate) editor: PhraseEditorModel, + pub(crate) size: Measure, + pub(crate) cursor: (usize, usize), + pub(crate) split: u16, + pub(crate) entered: bool, +} + +/// Root view for standalone `tek_arranger` +pub struct ArrangerTui { + pub(crate) jack: Arc>, + pub(crate) transport: TransportModel, + pub(crate) phrases: PhrasesModel, + pub(crate) tracks: Vec, + pub(crate) scenes: Vec, + pub(crate) name: Arc>, + pub(crate) splits: [u16;2], + pub(crate) selected: ArrangerSelection, + pub(crate) mode: ArrangerMode, + pub(crate) color: ItemColor, + pub(crate) entered: bool, + pub(crate) size: Measure, + pub(crate) note_buf: Vec, + pub(crate) midi_buf: Vec>>, + pub(crate) cursor: (usize, usize), + pub(crate) menu_bar: Option>, + pub(crate) status_bar: Option, + pub(crate) history: Vec, +} diff --git a/crates/tek_tui/src/tui_command.rs b/crates/tek_tui/src/tui_command.rs index 5b64429d..ee74df07 100644 --- a/crates/tek_tui/src/tui_command.rs +++ b/crates/tek_tui/src/tui_command.rs @@ -38,7 +38,7 @@ pub enum SequencerCommand { impl Command for SequencerCommand where - T: PhrasesControl + PhraseControl + ClockApi + PlayheadApi + T: PhrasesControl + PhraseControl + PlayheadApi + FocusGrid + FocusEnter { fn execute (self, state: &mut T) -> Perhaps { @@ -62,6 +62,7 @@ pub enum ArrangerCommand { Undo, Redo, Clear, + Color(ItemColor), Clock(ClockCommand), Playhead(PlayheadCommand), Scene(ArrangerSceneCommand), @@ -74,12 +75,8 @@ pub enum ArrangerCommand { EditPhrase(Option>>), } -impl Command for ArrangerCommand -where - T: ArrangerControl + HasPhrases + PhraseControl + ClockApi + PlayheadApi - + FocusGrid + FocusEnter -{ - fn execute (self, state: &mut T) -> Perhaps { +impl Command for ArrangerCommand { + fn execute (self, state: &mut ArrangerTui) -> Perhaps { use ArrangerCommand::*; Ok(match self { Focus(cmd) => cmd.execute(state)?.map(Focus), @@ -92,35 +89,33 @@ where Playhead(cmd) => cmd.execute(state)?.map(Playhead), Zoom(zoom) => { todo!(); }, Select(selected) => { - state.selected = selected; + *state.selected_mut() = selected; None }, EditPhrase(phrase) => { - state.editor.phrase = phrase.clone(); - state.focus(ArrangerFocus::PhraseEditor); - state.focus_enter(); + state.show_phrase(phrase); None } }) } } -impl Command for ArrangerSceneCommand { - fn execute (self, state: &mut T) -> Perhaps { +impl Command for ArrangerSceneCommand { + fn execute (self, state: &mut ArrangerTui) -> Perhaps { todo!(); Ok(None) } } -impl Command for ArrangerTrackCommand { - fn execute (self, state: &mut T) -> Perhaps { +impl Command for ArrangerTrackCommand { + fn execute (self, state: &mut ArrangerTui) -> Perhaps { todo!(); Ok(None) } } -impl Command for ArrangerClipCommand { - fn execute (self, state: &mut T) -> Perhaps { +impl Command for ArrangerClipCommand { + fn execute (self, state: &mut ArrangerTui) -> Perhaps { todo!(); Ok(None) } @@ -141,7 +136,7 @@ impl Command for PhrasesCommand { Self::Rename(command) => command.execute(state)?.map(Self::Rename), Self::Length(command) => command.execute(state)?.map(Self::Length), Self::Select(phrase) => { - *state.phrase_index_mut() = phrase; + state.set_phrase_index(phrase); None }, }) @@ -268,118 +263,34 @@ impl Command for PhraseCommand where T: PhraseControl + FocusEnter { - //fn translate (self, state: &PhraseTui) -> Self { - //use PhraseCommand::*; - //match self { - //GoUp => match state.entered { true => NoteCursorInc, false => NoteScrollInc, }, - //GoDown => match state.entered { true => NoteCursorDec, false => NoteScrollDec, }, - //GoLeft => match state.entered { true => TimeCursorDec, false => TimeScrollDec, }, - //GoRight => match state.entered { true => TimeCursorInc, false => TimeScrollInc, }, - //_ => self - //} - //} fn execute (self, state: &mut T) -> Perhaps { use PhraseCommand::*; Ok(match self { - ToggleDirection => { - state.phrase_mode_mut() = !state.mode; + ToggleDirection => { todo!() }, + EnterEditMode => { state.focus_enter(); None }, + ExitEditMode => { state.focus_exit(); None }, + NoteAppend => { + if state.phrase_entered() { + state.put_note(); + state.time_cursor_advance(); + } None }, - EnterEditMode => { - state.focus_enter(); - None - }, - ExitEditMode => { - state.focus_exit(); - None - }, - TimeZoomOut => { - let scale = state.time_axis().read().unwrap().scale; - state.time_axis().write().unwrap().scale = next_note_length(scale); - None - }, - TimeZoomIn => { - let scale = state.time_axis().read().unwrap().scale; - state.time_axis().write().unwrap().scale = prev_note_length(scale); - None - }, - TimeCursorDec => { - let scale = state.time_axis().read().unwrap().scale; - state.time_axis().write().unwrap().point_dec(scale); - None - }, - TimeCursorInc => { - let scale = state.time_axis().read().unwrap().scale; - state.time_axis().write().unwrap().point_inc(scale); - None - }, - TimeScrollDec => { - let scale = state.time_axis().read().unwrap().scale; - state.time_axis().write().unwrap().start_dec(scale); - None - }, - TimeScrollInc => { - let scale = state.time_axis().read().unwrap().scale; - state.time_axis().write().unwrap().start_inc(scale); - None - }, - NoteCursorDec => { - let mut axis = state.note_axis().write().unwrap(); - axis.point_inc(1); + NoteSet => { if state.phrase_entered() { state.put_note(); } None }, + TimeCursorSet(time) => { state.time_axis().write().unwrap().point_set(time); None }, + TimeScrollSet(time) => { state.time_axis().write().unwrap().start_set(time); None }, + TimeZoomSet(zoom) => { state.time_axis().write().unwrap().scale_set(zoom); None }, + NoteScrollSet(note) => { state.note_axis().write().unwrap().start_set(note); None }, + NoteLengthSet(time) => { *state.note_len_mut() = time; None }, + NoteCursorSet(note) => { + let axis = state.note_axis().write().unwrap(); + axis.point_set(note); if let Some(point) = axis.point { if point > 73 { axis.point = Some(73); } - } - None - }, - NoteCursorInc => { - let mut axis = state.note_axis().write().unwrap(); - axis.point_dec(1); - if let Some(point) = axis.point { if point < axis.start { axis.start = (point / 2) * 2; } } None }, - NoteScrollDec => { - state.note_axis().write().unwrap().start_inc(1); - None - }, - NoteScrollInc => { - state.note_axis().write().unwrap().start_dec(1); - None - }, - NoteLengthDec => { - *state.note_len_mut() = prev_note_length(state.note_len()); - None - }, - NoteLengthInc => { - *state.note_len_mut() = next_note_length(state.note_len()); - None - }, - NotePageUp => { - let mut axis = state.note_axis().write().unwrap(); - axis.start_dec(3); - axis.point_dec(3); - None - }, - NotePageDown => { - let mut axis = state.note_axis().write().unwrap(); - axis.start_inc(3); - axis.point_inc(3); - None - }, - NoteAppend => if state.focus_entered() { - state.put(); - state.time_cursor_advance(); - None - } else { - None - }, - NoteSet => if state.focus_entered() { - state.put(); - None - } else { - None - }, _ => unreachable!() }) } diff --git a/crates/tek_tui/src/tui_content.rs b/crates/tek_tui/src/tui_content.rs index 52d64017..74b13cc7 100644 --- a/crates/tek_tui/src/tui_content.rs +++ b/crates/tek_tui/src/tui_content.rs @@ -52,6 +52,29 @@ impl Content for SequencerTui { } } +/// Display mode of arranger +#[derive(Clone, PartialEq)] +pub enum ArrangerMode { + /// Tracks are rows + Horizontal, + /// Tracks are columns + Vertical(usize), +} + +/// Arranger display mode can be cycled +impl ArrangerMode { + /// 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), + } + } +} + /// Layout for standalone arranger app. impl Content for ArrangerTui { type Engine = Tui; @@ -59,7 +82,7 @@ impl Content for ArrangerTui { let arranger_focused = self.arranger_focused(); Split::up( 1, - widget(&TransportView(self)), + TransportView(self), Split::down( self.splits[0], lay!( @@ -87,8 +110,8 @@ impl Content for ArrangerTui { ), Split::right( self.splits[1], - widget(&PhrasesView(self)), - widget(&PhraseView(self)), + PhrasesView(self), + PhraseView(self), ) ) ) diff --git a/crates/tek_tui/src/tui_control.rs b/crates/tek_tui/src/tui_control.rs index 94eef920..07e19902 100644 --- a/crates/tek_tui/src/tui_control.rs +++ b/crates/tek_tui/src/tui_control.rs @@ -2,31 +2,95 @@ use crate::*; pub trait TransportControl: ClockApi {} +pub trait SequencerControl: TransportControl {} + +pub trait ArrangerControl: TransportControl { + fn selected (&self) -> ArrangerSelection; + fn selected_mut (&mut self) -> &mut ArrangerSelection; + fn show_phrase (&mut self, phrase: Option>>); + fn activate (&mut self); + fn selected_phrase (&self) -> Option>>; + fn toggle_loop (&mut self); + fn randomize_color (&mut self); +} + +pub trait PhrasesControl: HasPhrases { + fn phrase_index (&self) -> usize; + fn set_phrase_index (&self, index: usize); + fn phrases_mode (&self) -> &Option; + fn phrases_mode_mut (&mut self) -> &mut Option; + fn phrase_rename_begin (&mut self) { + let name = self.phrases()[self.phrase_index()].read().unwrap().name.clone(); + *self.phrases_mode_mut() = Some( + PhrasesMode::Rename(self.phrase_index(), name) + ) + } + fn phrase_length_begin (&mut self) { + let length = self.phrases()[self.phrase_index()].read().unwrap().length; + *self.phrases_mode_mut() = Some( + PhrasesMode::Length(self.phrase_index(), length, PhraseLengthFocus::Bar) + ) + } + fn new_phrase (name: Option<&str>, color: Option) -> Arc> { + Arc::new(RwLock::new(Phrase::new( + String::from(name.unwrap_or("(new)")), true, 4 * PPQ, None, color + ))) + } + fn index_of (&self, phrase: &Phrase) -> Option { + for i in 0..self.phrases().len() { + if *self.phrases()[i].read().unwrap() == *phrase { return Some(i) } + } + return None + } + fn insert_dup (&mut self) { + let mut phrase = self.phrases()[self.phrase_index()].read().unwrap().duplicate(); + phrase.color = ItemColorTriplet::random_near(phrase.color, 0.25); + let index = self.phrase_index() + 1; + self.phrases_mut().insert(index, Arc::new(RwLock::new(phrase))); + self.set_phrase_index(index); + } +} + +pub trait HasPhrasesModel: HasPhrases { + fn phrases_model (&self) -> &PhrasesModel; + fn phrases_model_mut (&mut self) -> &mut PhrasesModel; +} + +pub trait PhraseControl { + fn phrase_entered (&self) -> bool; + fn time_axis (&self) -> &RwLock>; + fn note_axis (&self) -> &RwLock>; + fn note_len (&self) -> usize; + fn note_len_mut (&mut self) -> &mut usize; + fn put_note (&mut self); + fn time_cursor_advance (&self) { + let point = self.time_axis().read().unwrap().point; + let length = self.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); + let forward = |time|(time + self.note_len()) % length; + self.time_axis().write().unwrap().point = point.map(forward); + } +} + impl TransportControl for TransportTui {} impl TransportControl for SequencerTui {} impl TransportControl for ArrangerTui {} -pub trait SequencerControl {} - impl SequencerControl for SequencerTui {} -pub trait ArrangerControl { - fn selected (&self) -> ArrangerSelection; - fn show_phrase (&mut self); - fn activate (&mut self); - fn selected_phrase (&self) -> Option>>; - fn toggle_loop (&mut self); - fn randomize_color (&mut self); -} - impl ArrangerControl for ArrangerTui { fn selected (&self) -> ArrangerSelection { self.selected } - fn show_phrase (&mut self) { + fn selected_mut (&mut self) -> &mut ArrangerSelection { + &mut self.selected + } + fn show_phrase (&mut self, phrase: Option>>) { self.editor.show(self.selected_phrase().as_ref()); + //state.editor.phrase = phrase.clone(); + //state.focus(ArrangerFocus::PhraseEditor); + //state.focus_enter(); } fn activate (&mut self) { if let ArrangerSelection::Scene(s) = self.selected { @@ -73,99 +137,36 @@ impl ArrangerControl for ArrangerTui { } } -pub trait PhrasesControl: HasPhrases { - fn phrase_index (&self) -> usize; - fn phrase_index_mut (&mut self) -> &mut usize; - fn phrases_mode (&self) -> &Option; - fn phrases_mode_mut (&mut self) -> &mut Option; - fn phrase_rename_begin (&mut self) { - let name = self.phrases()[self.phrase_index()].read().unwrap().name.clone(); - *self.phrases_mode_mut() = Some( - PhrasesMode::Rename(self.phrase_index(), name) - ) +impl HasPhrasesModel for ArrangerTui { + fn phrases_model (&self) -> &PhrasesModel { + &self.phrases } - fn phrase_length_begin (&mut self) { - let length = self.phrases()[self.phrase_index()].read().unwrap().length; - *self.phrases_mode_mut() = Some( - PhrasesMode::Length(self.phrase_index(), length, PhraseLengthFocus::Bar) - ) - } - fn new_phrase (name: Option<&str>, color: Option) -> Arc> { - Arc::new(RwLock::new(Phrase::new( - String::from(name.unwrap_or("(new)")), true, 4 * PPQ, None, color - ))) - } - fn index_of (&self, phrase: &Phrase) -> Option { - for i in 0..self.phrases().len() { - if *self.phrases()[i].read().unwrap() == *phrase { return Some(i) } - } - return None - } - fn insert_dup (&mut self) { - let mut phrase = self.phrases()[self.phrase_index()].read().unwrap().duplicate(); - phrase.color = ItemColorTriplet::random_near(phrase.color, 0.25); - let index = self.phrase_index() + 1; - self.phrases_mut().insert(index, Arc::new(RwLock::new(phrase))); - *self.phrase_index_mut() += 1; + fn phrases_model_mut (&mut self) -> &mut PhrasesModel { + &mut self.phrases } } -impl PhrasesControl for SequencerTui { - fn phrase_index (&self) -> usize { - self.view_phrase +impl HasPhrasesModel for SequencerTui { + fn phrases_model (&self) -> &PhrasesModel { + &self.phrases } - fn phrase_index_mut (&mut self) -> &mut usize { - &mut self.view_phrase + fn phrases_model_mut (&mut self) -> &mut PhrasesModel { + &mut self.phrases + } +} + +impl PhrasesControl for T { + fn phrase_index (&self) -> usize { + self.phrases_model().phrase.load(Ordering::Relaxed) + } + fn set_phrase_index (&self, value: usize) { + self.phrases_model().phrase.store(value, Ordering::Relaxed); } fn phrases_mode (&self) -> &Option { - &self.phrases_mode + &self.phrases_model().mode } fn phrases_mode_mut (&mut self) -> &mut Option { - &mut self.phrases_mode - } -} - -impl PhrasesControl for ArrangerTui { - fn phrase_index (&self) -> usize { - self.phrase - } - fn phrase_index_mut (&mut self) -> &mut usize { - &mut self.phrase - } - fn phrases_mode (&self) -> &Option { - &self.phrases_mode - } - fn phrases_mode_mut (&mut self) -> &mut Option { - &mut self.phrases_mode - } -} - -impl PhrasesControl for PhrasesTui { - fn phrase_index (&self) -> usize { - self.phrase - } - fn phrase_index_mut (&mut self) -> &mut usize { - &mut self.phrase - } - fn phrases_mode (&self) -> &Option { - &self.mode - } - fn phrases_mode_mut (&mut self) -> &mut Option { - &mut self.mode - } -} - -pub trait PhraseControl { - fn phrase_entered (&self) -> bool; - fn time_axis (&self) -> &RwLock>; - fn note_axis (&self) -> &RwLock>; - fn note_len (&self) -> usize; - fn note_len_mut (&mut self) -> &mut usize; - fn time_cursor_advance (&self) { - let point = self.time_axis().read().unwrap().point; - let length = self.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); - let forward = |time|(time + self.note_len()) % length; - self.time_axis().write().unwrap().point = point.map(forward); + &mut self.phrases_model_mut().mode } } @@ -185,6 +186,9 @@ impl PhraseControl for SequencerTui { fn note_len_mut (&mut self) -> &mut usize { todo!() } + fn put_note (&mut self) { + todo!() + } } impl PhraseControl for ArrangerTui { @@ -203,4 +207,7 @@ impl PhraseControl for ArrangerTui { fn note_len_mut (&mut self) -> &mut usize { todo!() } + fn put_note (&mut self) { + todo!() + } } diff --git a/crates/tek_tui/src/tui_debug.rs b/crates/tek_tui/src/tui_debug.rs new file mode 100644 index 00000000..ced0afcd --- /dev/null +++ b/crates/tek_tui/src/tui_debug.rs @@ -0,0 +1,33 @@ +use crate::*; + +impl std::fmt::Debug for TransportModel { + fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("TransportModel") + .field("playing", &self.playing) + .field("started", &self.started) + .field("current", &self.current) + .field("quant", &self.quant) + .field("sync", &self.sync) + .finish() + } +} + +impl std::fmt::Debug for TransportTui { + fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("Measure") + .field("jack", &self.jack) + .field("state", &self.state) + .field("size", &self.size) + .field("cursor", &self.cursor) + .finish() + } +} + +impl std::fmt::Debug for PhraseEditorModel { + fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("editor") + .field("note_axis", &self.time_axis) + .field("time_axis", &self.note_axis) + .finish() + } +} diff --git a/crates/tek_tui/src/tui_focus.rs b/crates/tek_tui/src/tui_focus.rs index 9db3f17d..5c134da5 100644 --- a/crates/tek_tui/src/tui_focus.rs +++ b/crates/tek_tui/src/tui_focus.rs @@ -217,7 +217,7 @@ impl FocusGrid for ArrangerTui { } /// Focused field of `PhraseLength` -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub enum PhraseLengthFocus { /// Editing the number of bars Bar, diff --git a/crates/tek_tui/src/tui_handle.rs b/crates/tek_tui/src/tui_handle.rs index 013d7171..3befacc4 100644 --- a/crates/tek_tui/src/tui_handle.rs +++ b/crates/tek_tui/src/tui_handle.rs @@ -15,12 +15,12 @@ impl Handle for ArrangerTui { ArrangerCommand::execute_with_state(self, i) } } -impl Handle for PhrasesTui { +impl Handle for PhrasesModel { fn handle (&mut self, from: &TuiInput) -> Perhaps { PhrasesCommand::execute_with_state(self, from) } } -impl Handle for PhraseTui { +impl Handle for PhraseEditorModel { fn handle (&mut self, from: &TuiInput) -> Perhaps { PhraseCommand::execute_with_state(self, from) } diff --git a/crates/tek_tui/src/tui_impls.rs b/crates/tek_tui/src/tui_impls.rs new file mode 100644 index 00000000..9560c6e4 --- /dev/null +++ b/crates/tek_tui/src/tui_impls.rs @@ -0,0 +1,190 @@ +use crate::*; + +macro_rules! impl_jack_api { + ($Struct:ident $(:: $field:ident)*) => { + impl JackApi for $Struct { + fn jack (&self) -> &Arc> { + &self$(.$field)* + } + } + } +} + +macro_rules! impl_clock_api { + ($Struct:ident $(:: $field:ident)*) => { + impl ClockApi for $Struct { + fn timebase (&self) -> &Arc { + &self$(.$field)*.current.timebase + } + fn quant (&self) -> &Quantize { + &self$(.$field)*.quant + } + fn sync (&self) -> &LaunchSync { + &self$(.$field)*.sync + } + } + } +} + +macro_rules! impl_playhead_api { + ($Struct:ident $(:: $field:ident)*) => { + impl PlayheadApi for $Struct { + fn current (&self) -> &Instant { + &self$(.$field)*.current + } + fn transport (&self) -> &jack::Transport { + &self$(.$field)*.transport + } + fn playing (&self) -> &RwLock> { + &self$(.$field)*.playing + } + fn started (&self) -> &RwLock> { + &self$(.$field)*.started + } + } + } +} + +macro_rules! impl_has_phrases { + ($Struct:ident $(:: $field:ident)*) => { + impl HasPhrases for $Struct { + fn phrases (&self) -> &Vec>> { + &self$(.$field)* + } + fn phrases_mut (&mut self) -> &mut Vec>> { + &mut self$(.$field)* + } + } + } +} + +macro_rules! impl_has_phrase { + ($Struct:ident $(:: $field:ident)*) => { + impl HasPhrase for $Struct { + fn reset (&self) -> bool { + self$(.$field)*.reset + } + fn reset_mut (&mut self) -> &mut bool { + &mut self$(.$field)*.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!() + } + } + } +} + +macro_rules! impl_midi_player { + ($Struct:ident $(:: $field:ident)*) => { + impl MidiInputApi for $Struct { + 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 $Struct { + 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 MidiPlayerApi for $Struct {} + } +} + +impl_jack_api!(TransportTui::jack); +impl_jack_api!(SequencerTui::jack); +impl_jack_api!(ArrangerTui::jack); +impl_clock_api!(TransportTui::state); +impl_clock_api!(SequencerTui::transport); +impl_clock_api!(ArrangerTui::transport); +impl_clock_api!(PhrasePlayerModel::transport); +impl_clock_api!(ArrangerTrack); +impl_playhead_api!(TransportTui::state); +impl_playhead_api!(SequencerTui::transport); +impl_playhead_api!(ArrangerTui::transport); +impl_playhead_api!(PhrasePlayerModel::transport); +impl_playhead_api!(ArrangerTrack); +impl_has_phrases!(PhrasesModel::phrases); +impl_has_phrases!(SequencerTui::phrases); +impl_has_phrases!(ArrangerTui::phrases); +impl_has_phrase!(SequencerTui::player); +impl_has_phrase!(ArrangerTrack::player); +impl_has_phrase!(PhrasePlayerModel); + +impl HasScenes for ArrangerTui { + fn scenes (&self) -> &Vec { + &self.scenes + } + fn scenes_mut (&mut self) -> &mut Vec { + &mut self.scenes + } + fn scene_add (&mut self, name: Option<&str>, color: Option) + -> Usually<&mut ArrangerScene> + { + let name = name.map_or_else(||self.scene_default_name(), |x|x.to_string()); + let scene = ArrangerScene { + name: Arc::new(name.into()), + clips: vec![None;self.tracks().len()], + color: color.unwrap_or_else(||ItemColor::random()), + }; + self.scenes_mut().push(scene); + let index = self.scenes().len() - 1; + Ok(&mut self.scenes_mut()[index]) + } + fn selected_scene (&self) -> Option<&ArrangerScene> { + self.selected.scene().map(|s|self.scenes().get(s)).flatten() + } + fn selected_scene_mut (&mut self) -> Option<&mut ArrangerScene> { + self.selected.scene().map(|s|self.scenes_mut().get_mut(s)).flatten() + } +} + +impl HasTracks for ArrangerTui { + fn tracks (&self) -> &Vec { + &self.tracks + } + fn tracks_mut (&mut self) -> &mut Vec { + &mut self.tracks + } +} diff --git a/crates/tek_tui/src/tui_init.rs b/crates/tek_tui/src/tui_init.rs index 6f3e91c2..0aef8f5d 100644 --- a/crates/tek_tui/src/tui_init.rs +++ b/crates/tek_tui/src/tui_init.rs @@ -5,18 +5,10 @@ impl TryFrom<&Arc>> for TransportTui { type Error = Box; fn try_from (jack: &Arc>) -> Usually { Ok(Self { - current: Instant::default(), - cursor: (0, 0), - focus: TransportFocus::PlayPause, - focused: false, - jack: jack.clone(), - metronome: false, - playing: RwLock::new(None), - quant: Quantize::default(), - size: Measure::new(), - started: RwLock::new(None), - sync: LaunchSync::default(), - transport: jack.read().unwrap().transport(), + cursor: (0, 0), + state: TransportModel::from(jack.read().unwrap().transport()), + jack: jack.clone(), + size: Measure::new(), }) } } @@ -25,33 +17,17 @@ impl TryFrom<&Arc>> for SequencerTui { type Error = Box; fn try_from (jack: &Arc>) -> Usually { Ok(Self { - current: Instant::default(), cursor: (0, 0), entered: false, jack: jack.clone(), - metronome: false, - midi_buf: vec![], - midi_inputs: vec![], - midi_outputs: vec![], - monitoring: true, - next_phrase: None, - note_buf: vec![], - notes_in: RwLock::new([false;128]).into(), - notes_out: RwLock::new([false;128]).into(), - overdub: true, phrases: vec![], phrases_mode: None, - play_phrase: None, - playing: RwLock::new(None), - quant: Quantize::default(), - recording: true, - reset: false, size: Measure::new(), split: 20, - started: RwLock::new(None), - sync: LaunchSync::default(), - transport: jack.read().unwrap().transport(), view_phrase: 0, + transport: TransportModel::from(jack.read().unwrap().transport()), + player: PhrasePlayerModel::default(), + editor: PhraseEditorModel::default(), }) } } @@ -61,13 +37,11 @@ impl TryFrom<&Arc>> for ArrangerTui { fn try_from (jack: &Arc>) -> Usually { Ok(Self { color: Color::Rgb(28, 35, 25).into(), - current: Instant::default(), cursor: (0, 0), entered: false, history: vec![], jack: jack.clone(), menu_bar: None, - metronome: false, midi_buf: vec![], mode: ArrangerMode::Vertical(2), name: Arc::new(RwLock::new(String::new())), @@ -75,17 +49,13 @@ impl TryFrom<&Arc>> for ArrangerTui { phrase: 0, phrases: vec![], phrases_mode: None, - playing: None.into(), - quant: Default::default(), scenes: vec![], selected: ArrangerSelection::Clip(0, 0), size: Measure::new(), splits: [20, 20], - started: None.into(), status_bar: None, - sync: Default::default(), tracks: vec![], - transport: jack.read().unwrap().transport(), + transport: TransportModel::from(jack.read().unwrap().transport()), }) } } diff --git a/crates/tek_tui/src/tui_input.rs b/crates/tek_tui/src/tui_input.rs index f4dfcdc7..23000b7d 100644 --- a/crates/tek_tui/src/tui_input.rs +++ b/crates/tek_tui/src/tui_input.rs @@ -2,7 +2,9 @@ use crate::*; impl InputToCommand for TransportCommand where - T: TransportControl + HasFocus + T: TransportControl + + HasFocus + + FocusEnter { fn input_to_command (state: &T, input: &TuiInput) -> Option { use KeyCode::Char; @@ -53,7 +55,9 @@ where impl InputToCommand for SequencerCommand where T: SequencerControl + TransportControl + PhrasesControl + PhraseControl + PlayheadApi - + HasFocus + FocusGrid + + HasFocus + + FocusGrid + + FocusEnter { fn input_to_command (state: &T, input: &TuiInput) -> Option { use FocusCommand::*; @@ -91,12 +95,8 @@ where } } -impl InputToCommand for ArrangerCommand -where - T: ArrangerControl + TransportControl + PhrasesControl + PhraseControl + PlayheadApi - + HasFocus + FocusGrid -{ - fn input_to_command (state: &T, input: &TuiInput) -> Option { +impl InputToCommand for ArrangerCommand { + fn input_to_command (state: &ArrangerCommand, input: &TuiInput) -> Option { use FocusCommand::*; use ArrangerCommand::*; Some(match input.event() { @@ -240,7 +240,12 @@ where Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, - key!(KeyCode::Char('c')) => Clip(Clip::RandomColor), + key!(KeyCode::Char('c')) => match state.selected() { + Select::Mix => Color(ItemColor::random()), + Select::Track(t) => Track(Track::Delete(t)), + Select::Scene(s) => Scene(Scene::Delete(s)), + Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), + }, key!(KeyCode::Char('s')) => match state.selected() { Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), @@ -279,7 +284,7 @@ impl InputToCommand for PhrasesCommand { key!(KeyCode::Down) => Some(Self::Select(0)), key!(KeyCode::Char(',')) => { if index > 1 { - *state.phrase_index_mut() -= 1; + state.set_phrase_index(state.phrase_index().saturating_sub(1)); Some(Self::Phrase(Phrase::Swap(index - 1, index))) } else { None @@ -287,7 +292,7 @@ impl InputToCommand for PhrasesCommand { }, key!(KeyCode::Char('.')) => { if index < count.saturating_sub(1) { - *state.phrase_index_mut() += 1; + state.set_phrase_index(state.phrase_index() + 1); Some(Self::Phrase(Phrase::Swap(index + 1, index))) } else { None @@ -295,7 +300,7 @@ impl InputToCommand for PhrasesCommand { }, key!(KeyCode::Delete) => { if index > 0 { - *state.phrase_index_mut() = index.min(count.saturating_sub(1)); + state.set_phrase_index(index.min(count.saturating_sub(1))); Some(Self::Phrase(Phrase::Delete(index))) } else { None @@ -362,39 +367,47 @@ impl InputToCommand for PhraseRenameCommand { } } -impl InputToCommand for PhraseCommand { +impl InputToCommand for PhraseCommand +where + T: PhraseControl + FocusEnter +{ fn input_to_command (state: &T, from: &TuiInput) -> Option { use PhraseCommand::*; Some(match from.event() { key!(KeyCode::Char('`')) => ToggleDirection, key!(KeyCode::Enter) => EnterEditMode, key!(KeyCode::Esc) => ExitEditMode, - key!(KeyCode::Char('[')) => NoteLengthSet(0), - key!(KeyCode::Char(']')) => NoteLengthSet(0), key!(KeyCode::Char('a')) => NoteAppend, key!(KeyCode::Char('s')) => NoteSet, - key!(KeyCode::Char('-')) => TimeZoomSet(0), - key!(KeyCode::Char('_')) => TimeZoomSet(0), - key!(KeyCode::Char('=')) => TimeZoomSet(0), - key!(KeyCode::Char('+')) => TimeZoomSet(0), - key!(KeyCode::PageUp) => NoteScrollSet(0), - key!(KeyCode::PageDown) => NoteScrollSet(0), - - key!(KeyCode::Up) => match state.phrase_entered() { - true => NoteCursorSet(0), - false => NoteScrollSet(0), + key!(KeyCode::Char('[')) => NoteLengthSet(prev_note_length(state.note_len())), + key!(KeyCode::Char(']')) => NoteLengthSet(next_note_length(state.note_len())), + key!(KeyCode::Char('-')) => TimeZoomSet(next_note_length(state.time_axis().read().unwrap().scale)), + key!(KeyCode::Char('_')) => TimeZoomSet(next_note_length(state.time_axis().read().unwrap().scale)), + key!(KeyCode::Char('=')) => TimeZoomSet(prev_note_length(state.time_axis().read().unwrap().scale)), + key!(KeyCode::Char('+')) => TimeZoomSet(prev_note_length(state.time_axis().read().unwrap().scale)), + key!(KeyCode::Up) => match state.phrase_entered() { + true => NoteCursorSet(state.note_axis().write().unwrap().point_plus(1)), + false => NoteScrollSet(state.note_axis().write().unwrap().start_plus(1)), }, - key!(KeyCode::Down) => match state.phrase_entered() { - true => NoteCursorSet(0), - false => NoteScrollSet(0), + key!(KeyCode::Down) => match state.phrase_entered() { + true => NoteCursorSet(state.note_axis().write().unwrap().point_minus(1)), + false => NoteScrollSet(state.note_axis().write().unwrap().start_minus(1)), }, - key!(KeyCode::Left) => match state.phrase_entered() { - true => TimeCursorSet(0), - false => TimeScrollSet(0), + key!(KeyCode::PageUp) => match state.phrase_entered() { + true => NoteCursorSet(state.note_axis().write().unwrap().point_plus(3)), + false => NoteScrollSet(state.note_axis().write().unwrap().start_plus(3)), }, - key!(KeyCode::Right) => match state.phrase_entered() { - true => TimeCursorSet(0), - false => TimeScrollSet(0), + key!(KeyCode::PageDown) => match state.phrase_entered() { + true => NoteCursorSet(state.note_axis().write().unwrap().point_minus(3)), + false => NoteScrollSet(state.note_axis().write().unwrap().start_minus(3)), + }, + key!(KeyCode::Left) => match state.phrase_entered() { + true => TimeCursorSet(state.note_axis().write().unwrap().point_minus(1)), + false => TimeScrollSet(state.note_axis().write().unwrap().start_minus(1)), + }, + key!(KeyCode::Right) => match state.phrase_entered() { + true => TimeCursorSet(state.note_axis().write().unwrap().point_plus(1)), + false => TimeScrollSet(state.note_axis().write().unwrap().start_plus(1)), }, _ => return None }) diff --git a/crates/tek_tui/src/tui_jack.rs b/crates/tek_tui/src/tui_jack.rs index 91b6cc71..c515751a 100644 --- a/crates/tek_tui/src/tui_jack.rs +++ b/crates/tek_tui/src/tui_jack.rs @@ -1,26 +1,23 @@ use crate::*; -impl JackApi for TransportTui { - fn jack (&self) -> &Arc> { - &self.jack - } -} -impl JackApi for SequencerTui { - fn jack (&self) -> &Arc> { - &self.jack - } -} -impl JackApi for ArrangerTui { - fn jack (&self) -> &Arc> { - &self.jack +impl Audio for TransportTui { + fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { + PlayheadAudio(self).process(client, scope) } } impl Audio for SequencerTui { fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - self.model.process(client, scope) + if PlayheadAudio(self).process(client, scope) == Control::Quit { + return Control::Quit + } + if PlayerAudio(self.player).process(client, scope) == Control::Quit { + return Control::Quit + } + Control::Continue } } + impl Audio for ArrangerTui { #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { if TracksAudio( @@ -36,156 +33,20 @@ impl Audio for ArrangerTui { let phrase = self.scenes().get(s).map(|scene|scene.clips.get(t)); if let Some(Some(Some(phrase))) = phrase { if let Some(track) = self.tracks().get(t) { - if let Some((ref started_at, Some(ref playing))) = track.play_phrase { + if let Some((ref started_at, Some(ref playing))) = track.player.play_phrase { let phrase = phrase.read().unwrap(); if *playing.read().unwrap() == *phrase { let pulse = self.current().pulse.get(); let start = started_at.pulse.get(); let now = (pulse - start) % phrase.length as f64; - self.editor.now.set(now); + self.now.set(now); return Control::Continue } } } } } - self.editor.now.set(0.); + self.now.set(0.); return Control::Continue } } - -impl ClockApi for ArrangerTui { - fn timebase (&self) -> &Arc { - &self.current.timebase - } - fn quant (&self) -> &Quantize { - &self.quant - } - fn sync (&self) -> &LaunchSync { - &self.sync - } -} - -impl PlayheadApi for ArrangerTui { - fn current (&self) -> &Instant { - &self.current - } - fn transport (&self) -> &jack::Transport { - &self.transport - } - fn playing (&self) -> &RwLock> { - &self.playing - } - fn started (&self) -> &RwLock> { - &self.started - } -} - -impl Audio for TransportTui { - fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - PlayheadAudio(self).process(client, scope) - } -} - - -impl ClockApi for TransportTui { - fn timebase (&self) -> &Arc { - &self.current.timebase - } - fn quant (&self) -> &Quantize { - &self.quant - } - fn sync (&self) -> &LaunchSync { - &self.sync - } -} - -impl PlayheadApi for TransportTui { - fn current (&self) -> &Instant { - &self.current - } - fn transport (&self) -> &jack::Transport { - &self.transport - } - fn playing (&self) -> &RwLock> { - &self.playing - } - fn started (&self) -> &RwLock> { - &self.started - } -} - - -impl MidiInputApi for SequencerTui { - fn midi_ins(&self) -> &Vec> { - todo!() - } - fn midi_ins_mut(&self) -> &mut Vec> { - todo!() - } - fn recording(&self) -> bool { - todo!() - } - fn recording_mut(&mut self) -> &mut bool { - todo!() - } - fn monitoring(&self) -> bool { - todo!() - } - fn monitoring_mut(&mut self) -> &mut bool { - todo!() - } - fn overdub(&self) -> bool { - todo!() - } - fn overdub_mut(&mut self) -> &mut bool { - todo!() - } - fn notes_in(&self) -> &Arc> { - todo!() - } -} - -impl MidiOutputApi for SequencerTui { - fn midi_outs (&self) -> &Vec> { - todo!() - } - fn midi_outs_mut (&mut self) -> &mut Vec> { - todo!() - } - fn midi_note (&mut self) -> &mut Vec { - todo!() - } - fn notes_out (&self) -> &Arc> { - todo!() - } -} - -impl ClockApi for SequencerTui { - fn timebase (&self) -> &Arc { - todo!() - } - fn quant (&self) -> &Quantize { - todo!() - } - fn sync (&self) -> &LaunchSync { - todo!() - } -} - -impl PlayheadApi for SequencerTui { - fn current(&self) -> &Instant { - todo!() - } - fn transport(&self) -> &Transport { - todo!() - } - fn playing(&self) -> &RwLock> { - todo!() - } - fn started(&self) -> &RwLock> { - todo!() - } -} - -impl PlayerApi for SequencerTui {} diff --git a/crates/tek_tui/src/tui_model.rs b/crates/tek_tui/src/tui_model.rs index aa64fe1e..edaa7a6c 100644 --- a/crates/tek_tui/src/tui_model.rs +++ b/crates/tek_tui/src/tui_model.rs @@ -1,204 +1,165 @@ use crate::*; -/// Stores and displays time-related info. -pub struct TransportTui { - pub(crate) jack: Arc>, +pub struct TransportModel { /// Playback state - pub(crate) playing: RwLock>, - /// Global sample and usec at which playback started - pub(crate) started: RwLock>, - /// Current moment in time - pub(crate) current: Instant, - /// Note quantization factor - pub(crate) quant: Quantize, - /// Launch quantization factor - pub(crate) sync: LaunchSync, - /// JACK transport handle. - pub(crate) transport: jack::Transport, - /// Enable metronome? - pub(crate) metronome: bool, - pub(crate) focus: TransportFocus, - pub(crate) focused: bool, - pub(crate) size: Measure, - pub(crate) cursor: (usize, usize), -} - -impl std::fmt::Debug for TransportTui { - fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - f.debug_struct("transport") - .field("jack", &self.jack) - .field("metronome", &self.metronome) - .finish() - } -} - -/// Root view for standalone `tek_sequencer`. -pub struct SequencerTui { - pub(crate) jack: Arc>, - pub(crate) playing: RwLock>, - pub(crate) started: RwLock>, - pub(crate) current: Instant, - pub(crate) quant: Quantize, - pub(crate) sync: LaunchSync, - pub(crate) transport: jack::Transport, - pub(crate) metronome: bool, - pub(crate) phrases: Vec>>, - pub(crate) view_phrase: usize, - pub(crate) split: u16, - /// Start time and phrase being played - pub(crate) play_phrase: Option<(Instant, Option>>)>, - /// Start time and next phrase - pub(crate) next_phrase: Option<(Instant, Option>>)>, - /// Play input through output. - pub(crate) monitoring: bool, - /// Write input to sequence. - pub(crate) recording: bool, - /// Overdub input to sequence. - pub(crate) overdub: bool, - /// Send all notes off - pub(crate) reset: bool, // TODO?: after Some(nframes) - /// Record from MIDI ports to current sequence. - pub(crate) midi_inputs: Vec>, - /// Play from current sequence to MIDI ports - pub(crate) midi_outputs: Vec>, - /// MIDI output buffer - pub(crate) note_buf: Vec, - /// MIDI output buffer - pub(crate) midi_buf: Vec>>, - /// Notes currently held at input - pub(crate) notes_in: Arc>, - /// Notes currently held at output - pub(crate) notes_out: Arc>, - - pub(crate) entered: bool, - pub(crate) cursor: (usize, usize), - pub(crate) size: Measure, - - /// Mode switch for phrase pool - pub(crate) phrases_mode: Option, -} - -impl HasPhrases for SequencerTui { - fn phrases (&self) -> &Vec>> { - &self.phrases - } - fn phrases_mut (&mut self) -> &mut Vec>> { - &mut self.phrases - } -} - -impl HasPhrase for SequencerTui { - 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!() - } -} - -/// Root view for standalone `tek_arranger` -pub struct ArrangerTui { - pub(crate) jack: Arc>, - pub(crate) transport: jack::Transport, pub(crate) playing: RwLock>, + /// Global sample and usec at which playback started pub(crate) started: RwLock>, + /// Current moment in time pub(crate) current: Instant, + /// Note quantization factor pub(crate) quant: Quantize, + /// Launch quantization factor pub(crate) sync: LaunchSync, + /// JACK transport handle. + pub(crate) transport: jack::Transport, + /// Enable metronome? pub(crate) metronome: bool, - pub(crate) phrases: Vec>>, - pub(crate) phrase: usize, - pub(crate) tracks: Vec, - pub(crate) scenes: Vec, - pub(crate) name: Arc>, - pub(crate) splits: [u16;2], - pub(crate) selected: ArrangerSelection, - pub(crate) mode: ArrangerMode, - pub(crate) color: ItemColor, - pub(crate) entered: bool, - pub(crate) size: Measure, - pub(crate) note_buf: Vec, - pub(crate) midi_buf: Vec>>, - pub(crate) cursor: (usize, usize), - pub(crate) menu_bar: Option>, - pub(crate) status_bar: Option, - pub(crate) history: Vec, - /// Mode switch for phrase pool - pub(crate) phrases_mode: Option, + /// Selected transport component + pub(crate) focus: TransportFocus, + /// Whether the transport is focused + pub(crate) is_focused: bool, } -impl HasPhrases for ArrangerTui { - fn phrases (&self) -> &Vec>> { - &self.phrases - } - fn phrases_mut (&mut self) -> &mut Vec>> { - &mut self.phrases - } -} - -impl HasScenes for ArrangerTui { - fn scenes (&self) -> &Vec { - &self.scenes - } - fn scenes_mut (&mut self) -> &mut Vec { - &mut self.scenes - } - fn scene_add (&mut self, name: Option<&str>, color: Option) - -> Usually<&mut ArrangerScene> - { - let name = name.map_or_else(||self.scene_default_name(), |x|x.to_string()); - let scene = ArrangerScene { - name: Arc::new(name.into()), - clips: vec![None;self.tracks().len()], - color: color.unwrap_or_else(||ItemColor::random()), - }; - self.scenes_mut().push(scene); - let index = self.scenes().len() - 1; - Ok(&mut self.scenes_mut()[index]) - } - fn selected_scene (&self) -> Option<&ArrangerScene> { - self.selected.scene().map(|s|self.scenes().get(s)).flatten() - } - fn selected_scene_mut (&mut self) -> Option<&mut ArrangerScene> { - self.selected.scene().map(|s|self.scenes_mut().get_mut(s)).flatten() - } -} - -/// Display mode of arranger -#[derive(Clone, PartialEq)] -pub enum ArrangerMode { - /// Tracks are rows - Horizontal, - /// Tracks are columns - Vertical(usize), -} - -/// Arranger display mode can be cycled -impl ArrangerMode { - /// 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 From for TransportModel { + fn from (transport: jack::Transport) -> Self { + Self { + current: Instant::default(), + metronome: false, + playing: RwLock::new(None), + quant: Quantize::default(), + started: RwLock::new(None), + sync: LaunchSync::default(), + focus: TransportFocus::PlayPause, + is_focused: true, + transport, } } } +/// Contains state for playing a phrase +#[derive(Debug)] +pub struct PhrasePlayerModel { + /// Start time and phrase being played + pub(crate) play_phrase: Option<(Instant, Option>>)>, + /// Start time and next phrase + pub(crate) next_phrase: Option<(Instant, Option>>)>, + /// Play input through output. + pub(crate) monitoring: bool, + /// Write input to sequence. + pub(crate) recording: bool, + /// Overdub input to sequence. + pub(crate) overdub: bool, + /// Send all notes off + pub(crate) reset: bool, // TODO?: after Some(nframes) + /// Record from MIDI ports to current sequence. + pub(crate) midi_ins: Vec>, + /// Play from current sequence to MIDI ports + pub(crate) midi_outs: Vec>, + /// MIDI output buffer + pub(crate) note_buf: Vec, + /// MIDI output buffer + pub(crate) midi_buf: Vec>>, + /// Notes currently held at input + pub(crate) notes_in: Arc>, + /// Notes currently held at output + pub(crate) notes_out: Arc>, +} + +impl Default for PhrasePlayerModel { + fn default () -> Self { + Self { + 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(), + note_buf: vec![], + midi_buf: vec![], + } + } +} + +/// Contains state for viewing and editing a phrase +pub struct PhraseEditorModel { + /// Phrase being played + pub(crate) phrase: Option>>, + /// Length of note that will be inserted, in pulses + pub(crate) note_len: usize, + /// The full piano keys are rendered to this buffer + pub(crate) keys: Buffer, + /// The full piano roll is rendered to this buffer + pub(crate) buffer: BigBuffer, + /// Cursor/scroll/zoom in pitch axis + pub(crate) note_axis: RwLock>, + /// Cursor/scroll/zoom in time axis + pub(crate) time_axis: RwLock>, + /// Whether this widget is focused + pub(crate) focused: bool, + /// Whether note enter mode is enabled + pub(crate) entered: bool, + /// Display mode + pub(crate) mode: bool, + /// Notes currently held at input + pub(crate) notes_in: Arc>, + /// Notes currently held at output + pub(crate) notes_out: Arc>, + /// Current position of global playhead + pub(crate) now: Arc, + /// Width and height of notes area at last render + pub(crate) size: Measure +} + +impl Default for PhraseEditorModel { + fn default () -> Self { + Self { + phrase: None, + note_len: 24, + keys: keys_vert(), + buffer: Default::default(), + focused: false, + entered: false, + mode: false, + notes_in: RwLock::new([false;128]).into(), + notes_out: RwLock::new([false;128]).into(), + now: Instant::default().into(), + size: Measure::new(), + note_axis: RwLock::new(FixedAxis { + start: 12, + point: Some(36), + clamp: Some(127) + }), + time_axis: RwLock::new(ScaledAxis { + start: 00, + point: Some(00), + clamp: Some(000), + scale: 24 + }), + } + } +} + +#[derive(Debug)] +pub struct PhrasesModel { + /// Collection of phrases + pub(crate) phrases: Vec>>, + /// Selected phrase + pub(crate) phrase: AtomicUsize, + /// Scroll offset + pub(crate) scroll: usize, + /// Mode switch + pub(crate) mode: Option, + /// Whether this widget is focused + pub(crate) focused: bool, + /// Whether this widget is entered + pub(crate) entered: bool, +} + #[derive(Default, Debug, Clone)] pub struct ArrangerScene { /// Name of scene @@ -221,34 +182,17 @@ impl ArrangerSceneApi for ArrangerScene { } } -impl HasTracks for ArrangerTui { - fn tracks (&self) -> &Vec { - &self.tracks - } - fn tracks_mut (&mut self) -> &mut Vec { - &mut self.tracks - } -} - impl ArrangerTracksApi for ArrangerTui { 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 { - 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(), + width: name.len() + 2, + name: Arc::new(name.into()), + color: color.unwrap_or_else(||ItemColor::random()), + player: PhrasePlayerModel::default(), + editor: PhraseEditorModel::default(), }; self.tracks_mut().push(track); let index = self.tracks().len() - 1; @@ -265,41 +209,15 @@ impl ArrangerTracksApi for ArrangerTui { #[derive(Debug)] pub struct ArrangerTrack { /// Name of track - pub(crate) name: Arc>, + pub(crate) name: Arc>, /// Preferred width of track column - pub(crate) width: usize, + pub(crate) width: usize, /// Identifying color of track - pub(crate) color: ItemColor, - /// Start time and phrase being played - pub(crate) play_phrase: Option<(Instant, Option>>)>, - /// Start time and next phrase - pub(crate) next_phrase: Option<(Instant, Option>>)>, - /// Play input through output. - pub(crate) monitoring: bool, - /// Write input to sequence. - pub(crate) recording: bool, - /// Overdub input to sequence. - pub(crate) overdub: bool, - /// Send all notes off - pub(crate) reset: bool, // TODO?: after Some(nframes) - /// Record from MIDI ports to current sequence. - pub(crate) midi_ins: Vec>, - /// Play from current sequence to MIDI ports - pub(crate) midi_outs: Vec>, - /// Notes currently held at input - pub(crate) notes_in: Arc>, - /// Notes currently held at output - pub(crate) notes_out: Arc>, - ///// MIDI output buffer - //midi_note: Vec, - ///// MIDI output buffer - //midi_chunk: Vec>>, - /// Whether this widget is focused - pub(crate) focused: bool, - /// Width and height of notes area at last render - pub(crate) size: Measure, - /// Mode switch - pub(crate) phrases_mode: Option, + pub(crate) color: ItemColor, + /// MIDI player state + pub(crate) player: PhrasePlayerModel, + /// MIDI editor state + pub(crate) editor: PhraseEditorModel, } impl ArrangerTrackApi for ArrangerTrack { @@ -321,126 +239,8 @@ impl ArrangerTrackApi for ArrangerTrack { } } -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 {} - -pub struct PhrasesTui { - /// Collection of phrases - pub(crate) phrases: Vec>>, - /// Selected phrase - pub(crate) phrase: usize, - /// Scroll offset - pub(crate) scroll: usize, - /// Mode switch - pub(crate) mode: Option, - /// Whether this widget is focused - pub(crate) focused: bool, - /// Whether this widget is entered - pub(crate) entered: bool, -} - -impl HasPhrases for PhrasesTui { - fn phrases (&self) -> &Vec>> { - &self.phrases - } - fn phrases_mut (&mut self) -> &mut Vec>> { - &mut self.phrases - } -} - /// Modes for phrase pool +#[derive(Debug, Clone)] pub enum PhrasesMode { /// Renaming a pattern Rename(usize, String), @@ -484,32 +284,15 @@ impl PhraseLength { } } -/// Contains state for viewing and editing a phrase -pub struct PhraseTui { - /// Phrase being played - pub(crate) phrase: Option>>, - /// Length of note that will be inserted, in pulses - pub(crate) note_len: usize, - /// The full piano keys are rendered to this buffer - pub(crate) keys: Buffer, - /// The full piano roll is rendered to this buffer - pub(crate) buffer: BigBuffer, - /// Cursor/scroll/zoom in pitch axis - pub(crate) note_axis: RwLock>, - /// Cursor/scroll/zoom in time axis - pub(crate) time_axis: RwLock>, - /// Whether this widget is focused - pub(crate) focused: bool, - /// Whether note enter mode is enabled - pub(crate) entered: bool, - /// Display mode - pub(crate) mode: bool, - /// Notes currently held at input - pub(crate) notes_in: Arc>, - /// Notes currently held at output - pub(crate) notes_out: Arc>, - /// Current position of global playhead - pub(crate) now: Arc, - /// Width and height of notes area at last render - pub(crate) size: Measure +impl PhrasesModel { + pub fn new (phrases: Vec>>) -> Self { + Self { + scroll: 0, + phrase: 0, + mode: None, + focused: false, + entered: false, + phrases, + } + } } diff --git a/crates/tek_tui/src/tui_view.rs b/crates/tek_tui/src/tui_view.rs index d7767254..c45988cb 100644 --- a/crates/tek_tui/src/tui_view.rs +++ b/crates/tek_tui/src/tui_view.rs @@ -2,6 +2,10 @@ use crate::*; pub struct TransportView<'a, T: TransportViewState>(pub &'a T); +pub struct PhrasesView<'a, T: PhrasesViewState>(pub &'a T); + +pub struct PhraseView<'a, T: PhraseViewState>(pub &'a T); + pub trait TransportViewState: ClockApi + PlayheadApi + Send + Sync { fn transport_selected (&self) -> TransportFocus; fn transport_focused (&self) -> bool; @@ -20,9 +24,35 @@ pub trait TransportViewState: ClockApi + PlayheadApi + Send + Sync { } } +pub trait ArrangerViewState { + fn arranger_focused (&self) -> bool; +} + +pub trait PhrasesViewState: Send + Sync { + fn phrases_focused (&self) -> bool; + fn entered (&self) -> bool; + fn phrases (&self) -> Vec>>; + fn phrase (&self) -> usize; + fn mode (&self) -> &Option; +} + +pub trait PhraseViewState: Send + Sync { + fn phrase (&self) -> &Option>>; + fn phrase_focused (&self) -> bool; + fn phrase_editor_size (&self) -> &Measure; + fn entered (&self) -> bool; + fn keys (&self) -> &Buffer; + fn buffer (&self) -> &BigBuffer; + fn note_len (&self) -> usize; + fn note_axis (&self) -> &RwLock>; + fn time_axis (&self) -> &RwLock>; + fn now (&self) -> &Arc; + fn size (&self) -> &Measure; +} + impl TransportViewState for TransportTui { fn transport_selected (&self) -> TransportFocus { - self.focus + self.state.focus } fn transport_focused (&self) -> bool { true @@ -34,7 +64,7 @@ impl TransportViewState for TransportTui { impl TransportViewState for SequencerTui { fn transport_selected (&self) -> TransportFocus { - self.focus + self.transport.focus } fn transport_focused (&self) -> bool { self.focused() == SequencerFocus::Transport @@ -46,7 +76,7 @@ impl TransportViewState for SequencerTui { impl TransportViewState for ArrangerTui { fn transport_selected (&self) -> TransportFocus { - self.focus + self.transport.focus } fn transport_focused (&self) -> bool { self.focused() == ArrangerFocus::Transport @@ -56,27 +86,13 @@ impl TransportViewState for ArrangerTui { } } -pub trait ArrangerViewState { - fn arranger_focused (&self) -> bool; -} - impl ArrangerViewState for ArrangerTui { fn arranger_focused (&self) -> bool { self.focused() == ArrangerFocus::Arranger } } -pub struct PhrasesView<'a, T: PhrasesViewState>(pub &'a T); - -pub trait PhrasesViewState: Send + Sync { - fn phrases_focused (&self) -> bool; - fn entered (&self) -> bool; - fn phrases (&self) -> Vec>>; - fn phrase (&self) -> usize; - fn mode (&self) -> &Option; -} - -impl PhrasesViewState for PhrasesTui { +impl PhrasesViewState for PhrasesModel { fn phrases_focused (&self) -> bool { todo!() } @@ -130,22 +146,7 @@ impl PhrasesViewState for ArrangerTui { } } -pub struct PhraseView<'a, T: PhraseViewState>(pub &'a T); - -pub trait PhraseViewState: Send + Sync { - fn phrase (&self) -> &Option>>; - fn phrase_focused (&self) -> bool; - fn phrase_editor_size (&self) -> &Measure; - fn entered (&self) -> bool; - fn keys (&self) -> &Buffer; - fn buffer (&self) -> &BigBuffer; - fn note_len (&self) -> usize; - fn note_axis (&self) -> &RwLock>; - fn time_axis (&self) -> &RwLock>; - fn now (&self) -> &Arc; -} - -impl PhraseViewState for PhraseTui { +impl PhraseViewState for PhraseEditorModel { fn phrase (&self) -> &Option>> { &self.phrase } @@ -176,6 +177,9 @@ impl PhraseViewState for PhraseTui { fn now (&self) -> &Arc { &self.now } + fn size (&self) -> &Measure { + &self.size + } } impl PhraseViewState for SequencerTui { @@ -209,6 +213,9 @@ impl PhraseViewState for SequencerTui { fn now (&self) -> &Arc { todo!() } + fn size (&self) -> &Measure { + &self.size + } } impl PhraseViewState for ArrangerTui { @@ -242,6 +249,9 @@ impl PhraseViewState for ArrangerTui { fn now (&self) -> &Arc { todo!() } + fn size (&self) -> &Measure { + &self.size + } } fn track_widths (tracks: &[ArrangerTrack]) -> Vec<(usize, usize)> { @@ -681,7 +691,7 @@ const NTH_OCTAVE: [&'static str; 11] = [ "-2", "-1", "0", "1", "2", "3", "4", "5", "6", "7", "8", ]; -impl PhraseTui { +impl PhraseEditorModel { pub fn put (&mut self) { if let (Some(phrase), Some(time), Some(note)) = ( &self.phrase, diff --git a/crates/tek_tui/src/tui_widget.rs b/crates/tek_tui/src/tui_widget.rs index 1d62f554..dbc98113 100644 --- a/crates/tek_tui/src/tui_widget.rs +++ b/crates/tek_tui/src/tui_widget.rs @@ -10,7 +10,7 @@ impl Widget for TransportTui { } } -impl Widget for PhrasesTui { +impl Widget for PhrasesModel { type Engine = Tui; fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> { PhrasesView(self).layout(to) @@ -20,7 +20,7 @@ impl Widget for PhrasesTui { } } -impl Widget for PhraseTui { +impl Widget for PhraseEditorModel { type Engine = Tui; fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> { PhraseView(self).layout(to)