diff --git a/crates/tek_core/src/focus.rs b/crates/tek_core/src/focus.rs index f18dc26e..495e3271 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 { @@ -30,24 +30,25 @@ impl Command for FocusCommand { } } +/// Trait for things that have ordered focusable subparts. pub trait HasFocus { + /// Type that identifies of focused item. type Item: Copy + PartialEq + Debug; + /// Get the currently focused item. fn focused (&self) -> Self::Item; - fn focus (&mut self, target: Self::Item); -} - -pub trait FocusOrder { + /// Focus the next item. fn focus_next (&mut self); + /// Focus the previous item. fn focus_prev (&mut self); + /// Loop forward until a specific item is focused. + fn focus (&mut self, target: Self::Item) { + while self.focused() != target { + self.focus_next() + } + } } -pub trait FocusEnter { - type Item: Copy + PartialEq + Debug; - fn focus_enter (&mut self) {} - fn focus_exit (&mut self) {} - fn focus_entered (&self) -> Option; -} - +/// Trait for things that implement directional focus. pub trait FocusGrid { type Item: Copy + PartialEq + Debug; fn focus_layout (&self) -> &[&[Self::Item]]; @@ -93,25 +94,13 @@ pub trait FocusGrid { impl HasFocus for U where T: Copy + PartialEq + Debug, - U: FocusGrid + FocusOrder, + U: FocusGrid + FocusEnter, { type Item = T; fn focused (&self) -> Self::Item { 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() - } - } -} - -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.focus_cursor(); @@ -145,3 +134,11 @@ where self.focus_update(); } } + +/// Trait for things that can be focused into. +pub trait FocusEnter { + type Item: Copy + PartialEq + Debug; + fn focus_enter (&mut self) {} + fn focus_exit (&mut self) {} + fn focus_entered (&self) -> Option; +} diff --git a/crates/tek_tui/src/lib.rs b/crates/tek_tui/src/lib.rs index a5dbdb7d..2fa90752 100644 --- a/crates/tek_tui/src/lib.rs +++ b/crates/tek_tui/src/lib.rs @@ -12,58 +12,49 @@ pub(crate) use std::fs::read_dir; use std::fmt::Debug; submod! { - tui_apis - tui_cmd + tui_command + tui_control tui_focus tui_handle + tui_init + tui_input tui_jack tui_menu tui_model + tui_select tui_status tui_theme - tui_select - - tui_phrase - tui_sequencer - - //tui_mixer // TODO - //tui_plugin // TODO - //tui_plugin_lv2 - //tui_plugin_lv2_gui - //tui_plugin_vst2 - //tui_plugin_vst3 - //tui_sampler // TODO - //tui_sampler_cmd + tui_view } -// TODO -impl Content for AppView -where - A: Widget + Audio, - C: Command, - S: StatusBar, -{ - type Engine = Tui; - fn content (&self) -> impl Widget { - let menus = self.menu_bar.as_ref().map_or_else( - ||&[] as &[Menu<_, _, _>], - |m|m.menus.as_slice() - ); +fn content_with_menu_and_status <'a, S: Send + Sync + 'a, C: Command> ( + content: &'a impl Widget, + menu_bar: Option<&'a MenuBar>, + status_bar: Option> +) -> impl Widget + 'a { + let menus = menu_bar.as_ref().map_or_else( + ||&[] as &[Menu<_, _, _>], + |m|m.menus.as_slice() + ); + let content = Either( + status_bar.is_none(), + widget(&content), + Split::up( + 1, + widget(status_bar.as_ref().unwrap()), + widget(&content) + ), + ); + Either( + menu_bar.is_none(), + widget(&content), Split::down( - if self.menu_bar.is_some() { 1 } else { 0 }, + 1, row!(menu in menus.iter() => { row!(" ", menu.title.as_str(), " ") }), - Either( - self.status_bar.is_some(), - Split::up( - 1, - widget(self.status_bar.as_ref().unwrap()), - widget(&self.app) - ), - widget(&self.app) - ) + widget(&content) ) - } + ) } diff --git a/crates/tek_tui/src/tui_apis.rs b/crates/tek_tui/src/tui_apis.rs index f4c1f884..7f4bfb28 100644 --- a/crates/tek_tui/src/tui_apis.rs +++ b/crates/tek_tui/src/tui_apis.rs @@ -18,6 +18,15 @@ impl HasPhrases for SequencerTui { } } +impl HasPhrases for PhrasesTui { + 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 @@ -564,3 +573,143 @@ impl PhrasesTui { //pub fn selected_phrase (&self) -> Option>> { //self.selected_scene()?.clips.get(self.selected.track()?)?.clone() //} + +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()), + 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 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); + } + + pub fn put (&mut self) { + if let (Some(phrase), Some(time), Some(note)) = ( + &self.phrase, + self.time_axis.read().unwrap().point, + self.note_axis.read().unwrap().point, + ) { + let mut phrase = phrase.write().unwrap(); + let key: u7 = u7::from((127 - note) as u8); + let vel: u7 = 100.into(); + let start = time; + let end = (start + self.note_len) % phrase.length; + phrase.notes[time].push(MidiMessage::NoteOn { key, vel }); + phrase.notes[end].push(MidiMessage::NoteOff { key, vel }); + self.buffer = Self::redraw(&phrase); + } + } + /// Select which pattern to display. This pre-renders it to the buffer at full resolution. + pub fn show (&mut self, phrase: Option<&Arc>>) { + if let Some(phrase) = phrase { + self.phrase = Some(phrase.clone()); + self.time_axis.write().unwrap().clamp = Some(phrase.read().unwrap().length); + self.buffer = Self::redraw(&*phrase.read().unwrap()); + } else { + self.phrase = None; + self.time_axis.write().unwrap().clamp = Some(0); + self.buffer = Default::default(); + } + } + fn redraw (phrase: &Phrase) -> BigBuffer { + let mut buf = BigBuffer::new(usize::MAX.min(phrase.length), 65); + Self::fill_seq_bg(&mut buf, phrase.length, phrase.ppq); + Self::fill_seq_fg(&mut buf, &phrase); + buf + } + fn fill_seq_bg (buf: &mut BigBuffer, length: usize, ppq: usize) { + for x in 0..buf.width { + // Only fill as far as phrase length + if x as usize >= length { break } + // Fill each row with background characters + for y in 0 .. buf.height { + buf.get_mut(x, y).map(|cell|{ + cell.set_char(if ppq == 0 { + '·' + } else if x % (4 * ppq) == 0 { + '│' + } else if x % ppq == 0 { + '╎' + } else { + '·' + }); + cell.set_fg(Color::Rgb(48, 64, 56)); + cell.modifier = Modifier::DIM; + }); + } + } + } + fn fill_seq_fg (buf: &mut BigBuffer, phrase: &Phrase) { + let mut notes_on = [false;128]; + for x in 0..buf.width { + if x as usize >= phrase.length { + break + } + if let Some(notes) = phrase.notes.get(x as usize) { + if phrase.percussive { + for note in notes { + match note { + MidiMessage::NoteOn { key, .. } => + notes_on[key.as_int() as usize] = true, + _ => {} + } + } + } else { + for note in notes { + match note { + MidiMessage::NoteOn { key, .. } => + notes_on[key.as_int() as usize] = true, + MidiMessage::NoteOff { key, .. } => + notes_on[key.as_int() as usize] = false, + _ => {} + } + } + } + for y in 0..buf.height { + if y >= 64 { + break + } + if let Some(block) = half_block( + notes_on[y as usize * 2], + notes_on[y as usize * 2 + 1], + ) { + buf.get_mut(x, y).map(|cell|{ + cell.set_char(block); + cell.set_fg(Color::White); + }); + } + } + if phrase.percussive { + notes_on.fill(false); + } + } + } + } +} diff --git a/crates/tek_tui/src/tui_arranger.rs b/crates/tek_tui/src/tui_arranger.rs deleted file mode 100644 index c7b7e813..00000000 --- a/crates/tek_tui/src/tui_arranger.rs +++ /dev/null @@ -1 +0,0 @@ -use crate::*; diff --git a/crates/tek_tui/src/tui_command.rs b/crates/tek_tui/src/tui_command.rs new file mode 100644 index 00000000..1fb89811 --- /dev/null +++ b/crates/tek_tui/src/tui_command.rs @@ -0,0 +1,348 @@ +use crate::*; + +#[derive(Clone, Debug, PartialEq)] +pub enum TransportCommand { + Focus(FocusCommand), + Clock(ClockCommand), + Playhead(PlayheadCommand), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum SequencerCommand { + Focus(FocusCommand), + Undo, + Redo, + Clear, + Clock(ClockCommand), + Playhead(PlayheadCommand), + Phrases(PhrasesCommand), + Editor(PhraseCommand), +} + +#[derive(Clone, Debug)] +pub enum ArrangerCommand { + Focus(FocusCommand), + Undo, + Redo, + Clear, + Clock(ClockCommand), + Playhead(PlayheadCommand), + Scene(ArrangerSceneCommand), + Track(ArrangerTrackCommand), + Clip(ArrangerClipCommand), + Select(ArrangerSelection), + Zoom(usize), + Phrases(PhrasePoolCommand), + Editor(PhraseCommand), + EditPhrase(Option>>), +} + +#[derive(Clone, PartialEq, Debug)] +pub enum PhrasesCommand { + Select(usize), + Edit(PhrasePoolCommand), + Rename(PhraseRenameCommand), + Length(PhraseLengthCommand), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum PhraseRenameCommand { + Begin, + Set(String), + Confirm, + Cancel, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum PhraseLengthCommand { + Begin, + Next, + Prev, + Inc, + Dec, + Set(usize), + Cancel, +} + +#[derive(Clone, PartialEq, Debug)] +pub enum PhraseCommand { + // TODO: 1-9 seek markers that by default start every 8th of the phrase + ToggleDirection, + EnterEditMode, + ExitEditMode, + NoteAppend, + NoteSet, + NoteCursorSet(usize), + NoteLengthSet(usize), + NoteScrollSet(usize), + TimeCursorSet(usize), + TimeScrollSet(usize), + TimeZoomSet(usize), +} + +impl Command for TransportCommand { + fn execute (self, state: &mut T) -> Perhaps { + use TransportCommand::{Focus, Clock, Playhead}; + use ClockCommand::{SetBpm, SetQuant, SetSync}; + Ok(Some(match self { + Focus(Next) => { todo!() } + Focus(Prev) => { todo!() }, + Focus(_) => { unimplemented!() }, + 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) + })) + } +} + +impl Command for SequencerCommand { + fn execute (self, state: &mut SequencerTui) -> Perhaps { + use SequencerCommand::*; + match self { + Focus(cmd) => delegate(cmd, Focus, &mut state), + Phrases(cmd) => delegate(cmd, Phrases, &mut state.phrases), + Editor(cmd) => delegate(cmd, Editor, &mut state.editor), + Clock(cmd) => delegate(cmd, Clock, &mut state.transport), + Playhead(cmd) => delegate(cmd, Playhead, &mut state.transport) + } + } +} + +impl Command for ArrangerCommand { + fn execute (self, state: &mut T) -> Perhaps { + use ArrangerCommand::*; + match self { + Focus(cmd) => { delegate(cmd, Focus, &mut state) }, + Scene(cmd) => { delegate(cmd, Scene, &mut state) }, + Track(cmd) => { delegate(cmd, Track, &mut state) }, + Clip(cmd) => { delegate(cmd, Clip, &mut state) }, + Phrases(cmd) => { delegate(cmd, Phrases, &mut state) }, + Editor(cmd) => { delegate(cmd, Editor, &mut state) }, + Clock(cmd) => { delegate(cmd, Clock, &mut state) }, + Playhead(cmd) => { delegate(cmd, Playhead, &mut state) }, + Zoom(zoom) => { todo!(); }, + Select(selected) => { state.selected = selected; Ok(None) }, + EditPhrase(phrase) => { + state.editor.phrase = phrase.clone(); + state.focus(ArrangerFocus::PhraseEditor); + state.focus_enter(); + Ok(None) + } + } + } +} + +impl Command for PhrasesCommand { + fn execute (self, view: &mut PhrasesTui) -> Perhaps { + use PhraseRenameCommand as Rename; + use PhraseLengthCommand as Length; + match self { + Self::Select(phrase) => { + view.phrase = phrase + }, + Self::Edit(command) => { + return Ok(command.execute(&mut view)?.map(Self::Edit)) + } + Self::Rename(command) => match command { + Rename::Begin => { + view.mode = Some(PhrasesMode::Rename( + view.phrase, + view.phrases[view.phrase].read().unwrap().name.clone() + )) + }, + _ => { + return Ok(command.execute(view)?.map(Self::Rename)) + } + }, + Self::Length(command) => match command { + Length::Begin => { + view.mode = Some(PhrasesMode::Length( + view.phrase, + view.phrases[view.phrase].read().unwrap().length, + PhraseLengthFocus::Bar + )) + }, + _ => { + return Ok(command.execute(view)?.map(Self::Length)) + } + }, + } + Ok(None) + } +} + +impl Command for PhraseLengthCommand { + fn execute (self, view: &mut PhrasesTui) -> Perhaps { + use PhraseLengthFocus::*; + use PhraseLengthCommand::*; + if let Some(PhrasesMode::Length(phrase, ref mut length, ref mut focus)) = view.mode { + match self { + Self::Cancel => { + view.mode = None; + }, + Self::Prev => { + focus.prev() + }, + Self::Next => { + focus.next() + }, + Self::Inc => match focus { + Bar => { *length += 4 * PPQ }, + Beat => { *length += PPQ }, + Tick => { *length += 1 }, + }, + Self::Dec => match focus { + Bar => { *length = length.saturating_sub(4 * PPQ) }, + Beat => { *length = length.saturating_sub(PPQ) }, + Tick => { *length = length.saturating_sub(1) }, + }, + Self::Set(length) => { + let mut phrase = view.phrases[phrase].write().unwrap(); + let old_length = phrase.length; + phrase.length = length; + view.mode = None; + return Ok(Some(Self::Set(old_length))) + }, + _ => unreachable!() + } + Ok(None) + } else if self == Begin { + view.mode = Some(PhrasesMode::Length( + view.phrase, + view.phrases[view.phrase].read().unwrap().length, + PhraseLengthFocus::Bar + )); + Ok(None) + } else { + unreachable!() + } + } +} + +impl Command for PhraseRenameCommand { + fn execute (self, view: &mut PhrasesTui) -> Perhaps { + use PhraseRenameCommand::*; + if let Some(PhrasesMode::Rename(phrase, ref mut old_name)) = view.mode { + match self { + Set(s) => { + view.phrases[phrase].write().unwrap().name = s.into(); + return Ok(Some(Self::Set(old_name.clone()))) + }, + Confirm => { + let old_name = old_name.clone(); + view.mode = None; + return Ok(Some(Self::Set(old_name))) + }, + Cancel => { + let mut phrase = view.phrases[phrase].write().unwrap(); + phrase.name = old_name.clone(); + }, + _ => unreachable!() + }; + Ok(None) + } else if self == Begin { + view.mode = Some(PhrasesMode::Rename( + view.phrase, + view.phrases[view.phrase].read().unwrap().name.clone() + )); + Ok(None) + } else { + unreachable!() + } + } +} + +impl Command for PhraseCommand { + //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 PhraseTui) -> Perhaps { + use PhraseCommand::*; + match self.translate(state) { + ToggleDirection => { + state.mode = !state.mode; + }, + EnterEditMode => { + state.entered = true; + }, + ExitEditMode => { + state.entered = false; + }, + TimeZoomOut => { + let scale = state.time_axis.read().unwrap().scale; + state.time_axis.write().unwrap().scale = next_note_length(scale) + }, + TimeZoomIn => { + let scale = state.time_axis.read().unwrap().scale; + state.time_axis.write().unwrap().scale = prev_note_length(scale) + }, + TimeCursorDec => { + let scale = state.time_axis.read().unwrap().scale; + state.time_axis.write().unwrap().point_dec(scale); + }, + TimeCursorInc => { + let scale = state.time_axis.read().unwrap().scale; + state.time_axis.write().unwrap().point_inc(scale); + }, + TimeScrollDec => { + let scale = state.time_axis.read().unwrap().scale; + state.time_axis.write().unwrap().start_dec(scale); + }, + TimeScrollInc => { + let scale = state.time_axis.read().unwrap().scale; + state.time_axis.write().unwrap().start_inc(scale); + }, + NoteCursorDec => { + let mut axis = state.note_axis.write().unwrap(); + axis.point_inc(1); + if let Some(point) = axis.point { if point > 73 { axis.point = Some(73); } } + }, + 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; } } + }, + NoteScrollDec => { + state.note_axis.write().unwrap().start_inc(1); + }, + NoteScrollInc => { + state.note_axis.write().unwrap().start_dec(1); + }, + NoteLengthDec => { + state.note_len = prev_note_length(state.note_len) + }, + NoteLengthInc => { + state.note_len = next_note_length(state.note_len) + }, + NotePageUp => { + let mut axis = state.note_axis.write().unwrap(); + axis.start_dec(3); + axis.point_dec(3); + }, + NotePageDown => { + let mut axis = state.note_axis.write().unwrap(); + axis.start_inc(3); + axis.point_inc(3); + }, + NoteAppend => { + if state.entered { + state.put(); + state.time_cursor_advance(); + } + }, + NoteSet => { + if state.entered { state.put(); } + }, + _ => unreachable!() + } + Ok(None) + } +} diff --git a/crates/tek_tui/src/tui_control.rs b/crates/tek_tui/src/tui_control.rs new file mode 100644 index 00000000..8ea8b31b --- /dev/null +++ b/crates/tek_tui/src/tui_control.rs @@ -0,0 +1,53 @@ +use crate::*; + +pub trait TransportControl { + 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 TransportTui { + fn bpm (&self) -> &BeatsPerMinute { + self.bpm() + } + fn quant (&self) -> &Quantize { + self.quant() + } + fn sync (&self) -> &LaunchSync { + self.sync() + } +} + +impl TransportControl for SequencerTui { + fn bpm (&self) -> &BeatsPerMinute { + self.bpm() + } + fn quant (&self) -> &Quantize { + self.quant() + } + fn sync (&self) -> &LaunchSync { + self.sync() + } +} + +pub trait ArrangerControl { + fn selected (&self) -> ArrangerSelection; +} + +impl ArrangerControl for ArrangerTui { + fn selected (&self) -> ArrangerSelection { + self.selected + } +} diff --git a/crates/tek_tui/src/tui_focus.rs b/crates/tek_tui/src/tui_focus.rs index e05b7dd7..27883611 100644 --- a/crates/tek_tui/src/tui_focus.rs +++ b/crates/tek_tui/src/tui_focus.rs @@ -45,6 +45,7 @@ impl TransportFocus { Self::Quant => Self::Sync, Self::Sync => Self::Clock, Self::Clock => Self::PlayPause, + Self::Menu => { todo!() }, } } pub fn prev (&mut self) { @@ -54,6 +55,7 @@ impl TransportFocus { Self::Quant => Self::Bpm, Self::Sync => Self::Quant, Self::Clock => Self::Sync, + Self::Menu => { todo!() }, } } pub fn wrap <'a, W: Widget> ( @@ -66,9 +68,9 @@ impl TransportFocus { } } -impl HasFocus for TransportTui { - type Item = TransportFocus; -} +//impl HasFocus for TransportTui { + //type Item = TransportFocus; +//} impl FocusEnter for TransportTui { type Item = TransportFocus; @@ -107,9 +109,9 @@ impl FocusGrid for TransportTui { } } -impl HasFocus for SequencerTui { - type Item = SequencerFocus; -} +//impl HasFocus for SequencerTui { + //type Item = SequencerFocus; +//} impl FocusEnter for SequencerTui { type Item = SequencerFocus; @@ -163,15 +165,15 @@ impl FocusEnter for ArrangerTui { let focused = self.focused(); if !self.entered { self.entered = focused == Arranger; - self.app.editor.entered = focused == PhraseEditor; - self.app.phrases.entered = focused == PhrasePool; + self.editor.entered = focused == PhraseEditor; + self.phrases.entered = focused == PhrasePool; } } fn focus_exit (&mut self) { if self.entered { self.entered = false; - self.app.editor.entered = false; - self.app.phrases.entered = false; + self.editor.entered = false; + self.phrases.entered = false; } } fn focus_entered (&self) -> Option { @@ -207,7 +209,7 @@ impl FocusGrid for ArrangerTui { if let Some(mut status_bar) = self.status_bar { status_bar.update(&( self.focused(), - self.app.selected, + self.selected, focused == PhraseEditor && self.entered )) } diff --git a/crates/tek_tui/src/tui_init.rs b/crates/tek_tui/src/tui_init.rs index 5292208e..11a552db 100644 --- a/crates/tek_tui/src/tui_init.rs +++ b/crates/tek_tui/src/tui_init.rs @@ -26,7 +26,6 @@ impl TryFrom<&Arc>> for SequencerTui { focused: false, focus: TransportFocus::PlayPause, size: Measure::new(), - phrases: vec![], }.into(), None, None)) } } diff --git a/crates/tek_tui/src/tui_cmd.rs b/crates/tek_tui/src/tui_input.rs similarity index 54% rename from crates/tek_tui/src/tui_cmd.rs rename to crates/tek_tui/src/tui_input.rs index 4c706db8..736bee90 100644 --- a/crates/tek_tui/src/tui_cmd.rs +++ b/crates/tek_tui/src/tui_input.rs @@ -1,12 +1,5 @@ use crate::*; -#[derive(Clone, Debug, PartialEq)] -pub enum TransportCommand { - Focus(FocusCommand), - Clock(ClockCommand), - Playhead(PlayheadCommand), -} - impl InputToCommand for TransportCommand { fn input_to_command (state: &T, input: &TuiInput) -> Option { use KeyCode::Char; @@ -54,64 +47,6 @@ impl InputToCommand for TransportCommand { } } -pub trait TransportControl { - 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 TransportTui { - fn bpm (&self) -> &BeatsPerMinute { - self.bpm() - } - fn quant (&self) -> &Quantize { - self.quant() - } - fn sync (&self) -> &LaunchSync { - self.sync() - } -} - -impl Command for TransportCommand { - fn execute (self, state: &mut T) -> Perhaps { - use TransportCommand::{Focus, Clock, Playhead}; - use ClockCommand::{SetBpm, SetQuant, SetSync}; - Ok(Some(match self { - Focus(Next) => { todo!() } - Focus(Prev) => { todo!() }, - Focus(_) => { unimplemented!() }, - 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) - })) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum SequencerCommand { - Focus(FocusCommand), - Undo, - Redo, - Clear, - Clock(ClockCommand), - Playhead(PlayheadCommand), - Phrases(PhrasesCommand), - Editor(PhraseCommand), -} - impl InputToCommand for SequencerCommand { fn input_to_command (state: &SequencerTui, input: &TuiInput) -> Option { use FocusCommand::*; @@ -138,55 +73,6 @@ impl InputToCommand for SequencerCommand { } } -impl Command for SequencerCommand { - fn execute (self, state: &mut SequencerTui) -> Perhaps { - use SequencerCommand::*; - match self { - Focus(cmd) => delegate(cmd, Focus, state), - Phrases(cmd) => delegate(cmd, Phrases, &mut state.phrases), - Editor(cmd) => delegate(cmd, Editor, &mut state.editor), - Clock(cmd) => delegate(cmd, Clock, &mut state.transport), - Playhead(cmd) => delegate(cmd, Playhead, &mut state.transport) - } - } -} - -impl TransportControl for SequencerTui { - fn bpm (&self) -> &BeatsPerMinute { - self.app.bpm() - } - fn quant (&self) -> &Quantize { - self.app.quant() - } - fn sync (&self) -> &LaunchSync { - self.app.sync() - } -} - -#[derive(Clone, Debug)] -pub enum ArrangerCommand { - Focus(FocusCommand), - Undo, - Redo, - Clear, - Clock(ClockCommand), - Playhead(PlayheadCommand), - Scene(ArrangerSceneCommand), - Track(ArrangerTrackCommand), - Clip(ArrangerClipCommand), - Select(ArrangerSelection), - Zoom(usize), - Phrases(PhrasePoolCommand), - Editor(PhraseCommand), - EditPhrase(Option>>), -} - -pub trait ArrangerControl { -} - -impl ArrangerControl for ArrangerTui { -} - impl InputToCommand for ArrangerCommand { fn input_to_command (view: &T, input: &TuiInput) -> Option { use FocusCommand::*; @@ -238,28 +124,28 @@ impl InputToCommand for ArrangerCommand { _ => match input.event() { // FIXME: boundary conditions - key!(KeyCode::Up) => match view.selected { + key!(KeyCode::Up) => match view.selected() { Select::Mix => return None, Select::Track(t) => return None, Select::Scene(s) => Select(Select::Scene(s - 1)), Select::Clip(t, s) => Select(Select::Clip(t, s - 1)), }, - key!(KeyCode::Down) => match view.selected { + key!(KeyCode::Down) => match view.selected() { Select::Mix => Select(Select::Scene(0)), Select::Track(t) => Select(Select::Clip(t, 0)), Select::Scene(s) => Select(Select::Scene(s + 1)), Select::Clip(t, s) => Select(Select::Clip(t, s + 1)), }, - key!(KeyCode::Left) => match view.selected { + key!(KeyCode::Left) => match view.selected() { Select::Mix => return None, Select::Track(t) => Select(Select::Track(t - 1)), Select::Scene(s) => return None, Select::Clip(t, s) => Select(Select::Clip(t - 1, s)), }, - key!(KeyCode::Right) => match view.selected { + key!(KeyCode::Right) => match view.selected() { Select::Mix => return None, Select::Track(t) => Select(Select::Track(t + 1)), Select::Scene(s) => Select(Select::Clip(0, s)), @@ -276,42 +162,42 @@ impl InputToCommand for ArrangerCommand { key!(KeyCode::Char('`')) => { todo!("toggle view mode") }, - key!(KeyCode::Char(',')) => match view.selected { + key!(KeyCode::Char(',')) => match view.selected() { Select::Mix => Zoom(0), Select::Track(t) => Track(Track::Swap(t, t - 1)), Select::Scene(s) => Scene(Scene::Swap(s, s - 1)), Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, - key!(KeyCode::Char('.')) => match view.selected { + key!(KeyCode::Char('.')) => match view.selected() { Select::Mix => Zoom(0), Select::Track(t) => Track(Track::Swap(t, t + 1)), Select::Scene(s) => Scene(Scene::Swap(s, s + 1)), Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, - key!(KeyCode::Char('<')) => match view.selected { + key!(KeyCode::Char('<')) => match view.selected() { Select::Mix => Zoom(0), Select::Track(t) => Track(Track::Swap(t, t - 1)), Select::Scene(s) => Scene(Scene::Swap(s, s - 1)), Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, - key!(KeyCode::Char('>')) => match view.selected { + key!(KeyCode::Char('>')) => match view.selected() { Select::Mix => Zoom(0), Select::Track(t) => Track(Track::Swap(t, t + 1)), Select::Scene(s) => Scene(Scene::Swap(s, s + 1)), Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, - key!(KeyCode::Enter) => match view.selected { + key!(KeyCode::Enter) => match view.selected() { Select::Mix => return None, Select::Track(t) => return None, Select::Scene(s) => Scene(Scene::Play(s)), Select::Clip(t, s) => return None, }, - key!(KeyCode::Delete) => match view.selected { + key!(KeyCode::Delete) => match view.selected() { Select::Mix => Clear, Select::Track(t) => Track(Track::Delete(t)), Select::Scene(s) => Scene(Scene::Delete(s)), @@ -320,12 +206,12 @@ impl InputToCommand for ArrangerCommand { key!(KeyCode::Char('c')) => Clip(Clip::RandomColor), - key!(KeyCode::Char('s')) => match view.selected { + key!(KeyCode::Char('s')) => match view.selected() { Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), _ => return None, }, - key!(KeyCode::Char('g')) => match view.selected { + key!(KeyCode::Char('g')) => match view.selected() { Select::Clip(t, s) => Clip(Clip::Get(t, s)), _ => return None, }, @@ -345,57 +231,6 @@ impl InputToCommand for ArrangerCommand { } } -impl Command for ArrangerCommand { - fn execute (self, state: &mut T) -> Perhaps { - use ArrangerCommand::*; - match self { - Focus(cmd) => { delegate(cmd, Focus, state) }, - Scene(cmd) => { delegate(cmd, Scene, &mut state) }, - Track(cmd) => { delegate(cmd, Track, &mut state) }, - Clip(cmd) => { delegate(cmd, Clip, &mut state) }, - Phrases(cmd) => { delegate(cmd, Phrases, &mut state) }, - Editor(cmd) => { delegate(cmd, Editor, &mut state) }, - Clock(cmd) => { delegate(cmd, Clock, &mut state) }, - Playhead(cmd) => { delegate(cmd, Playhead, &mut state) }, - Zoom(zoom) => { todo!(); }, - Select(selected) => { state.selected = selected; Ok(None) }, - EditPhrase(phrase) => { - state.editor.phrase = phrase.clone(); - state.focus(ArrangerFocus::PhraseEditor); - state.focus_enter(); - Ok(None) - } - } - } -} - -#[derive(Clone, PartialEq, Debug)] -pub enum PhrasesCommand { - Select(usize), - Edit(PhrasePoolCommand), - Rename(PhraseRenameCommand), - Length(PhraseLengthCommand), -} - -#[derive(Clone, Debug, PartialEq)] -pub enum PhraseRenameCommand { - Begin, - Set(String), - Confirm, - Cancel, -} - -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum PhraseLengthCommand { - Begin, - Next, - Prev, - Inc, - Dec, - Set(usize), - Cancel, -} - impl InputToCommand for PhrasesCommand { fn input_to_command (state: &PhrasesTui, input: &TuiInput) -> Option { use PhrasesCommand as Cmd; @@ -427,45 +262,6 @@ impl InputToCommand for PhrasesCommand { } } -impl Command for PhrasesCommand { - fn execute (self, view: &mut PhrasesTui) -> Perhaps { - use PhraseRenameCommand as Rename; - use PhraseLengthCommand as Length; - match self { - Self::Select(phrase) => { - view.phrase = phrase - }, - Self::Edit(command) => { - return Ok(command.execute(&mut view)?.map(Self::Edit)) - } - Self::Rename(command) => match command { - Rename::Begin => { - view.mode = Some(PhrasesMode::Rename( - view.phrase, - view.phrases[view.phrase].read().unwrap().name.clone() - )) - }, - _ => { - return Ok(command.execute(view)?.map(Self::Rename)) - } - }, - Self::Length(command) => match command { - Length::Begin => { - view.mode = Some(PhrasesMode::Length( - view.phrase, - view.phrases[view.phrase].read().unwrap().length, - PhraseLengthFocus::Bar - )) - }, - _ => { - return Ok(command.execute(view)?.map(Self::Length)) - } - }, - } - Ok(None) - } -} - impl InputToCommand for PhraseLengthCommand { fn input_to_command (view: &PhrasesTui, from: &TuiInput) -> Option { if let Some(PhrasesMode::Length(_, length, _)) = view.mode { @@ -484,54 +280,6 @@ impl InputToCommand for PhraseLengthCommand { } } -impl Command for PhraseLengthCommand { - fn execute (self, view: &mut PhrasesTui) -> Perhaps { - use PhraseLengthFocus::*; - use PhraseLengthCommand::*; - if let Some(PhrasesMode::Length(phrase, ref mut length, ref mut focus)) = view.mode { - match self { - Self::Cancel => { - view.mode = None; - }, - Self::Prev => { - focus.prev() - }, - Self::Next => { - focus.next() - }, - Self::Inc => match focus { - Bar => { *length += 4 * PPQ }, - Beat => { *length += PPQ }, - Tick => { *length += 1 }, - }, - Self::Dec => match focus { - Bar => { *length = length.saturating_sub(4 * PPQ) }, - Beat => { *length = length.saturating_sub(PPQ) }, - Tick => { *length = length.saturating_sub(1) }, - }, - Self::Set(length) => { - let mut phrase = view.phrases[phrase].write().unwrap(); - let old_length = phrase.length; - phrase.length = length; - view.mode = None; - return Ok(Some(Self::Set(old_length))) - }, - _ => unreachable!() - } - Ok(None) - } else if self == Begin { - view.mode = Some(PhrasesMode::Length( - view.phrase, - view.phrases[view.phrase].read().unwrap().length, - PhraseLengthFocus::Bar - )); - Ok(None) - } else { - unreachable!() - } - } -} - impl InputToCommand for PhraseRenameCommand { fn input_to_command (view: &PhrasesTui, from: &TuiInput) -> Option { if let Some(PhrasesMode::Rename(_, ref old_name)) = view.mode { @@ -556,55 +304,6 @@ impl InputToCommand for PhraseRenameCommand { } } -impl Command for PhraseRenameCommand { - fn execute (self, view: &mut PhrasesTui) -> Perhaps { - use PhraseRenameCommand::*; - if let Some(PhrasesMode::Rename(phrase, ref mut old_name)) = view.mode { - match self { - Set(s) => { - view.phrases[phrase].write().unwrap().name = s.into(); - return Ok(Some(Self::Set(old_name.clone()))) - }, - Confirm => { - let old_name = old_name.clone(); - view.mode = None; - return Ok(Some(Self::Set(old_name))) - }, - Cancel => { - let mut phrase = view.phrases[phrase].write().unwrap(); - phrase.name = old_name.clone(); - }, - _ => unreachable!() - }; - Ok(None) - } else if self == Begin { - view.mode = Some(PhrasesMode::Rename( - view.phrase, - view.phrases[view.phrase].read().unwrap().name.clone() - )); - Ok(None) - } else { - unreachable!() - } - } -} - -#[derive(Clone, PartialEq, Debug)] -pub enum PhraseCommand { - // TODO: 1-9 seek markers that by default start every 8th of the phrase - ToggleDirection, - EnterEditMode, - ExitEditMode, - NoteAppend, - NoteSet, - NoteCursorSet(usize), - NoteLengthSet(usize), - NoteScrollSet(usize), - TimeCursorSet(usize), - TimeScrollSet(usize), - TimeZoomSet(usize), -} - impl InputToCommand for PhraseCommand { fn input_to_command (state: &PhraseTui, from: &TuiInput) -> Option { use PhraseCommand::*; @@ -642,97 +341,3 @@ impl InputToCommand for PhraseCommand { }) } } - -impl Command for PhraseCommand { - //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 PhraseTui) -> Perhaps { - use PhraseCommand::*; - match self.translate(state) { - ToggleDirection => { - state.mode = !state.mode; - }, - EnterEditMode => { - state.entered = true; - }, - ExitEditMode => { - state.entered = false; - }, - TimeZoomOut => { - let scale = state.time_axis.read().unwrap().scale; - state.time_axis.write().unwrap().scale = next_note_length(scale) - }, - TimeZoomIn => { - let scale = state.time_axis.read().unwrap().scale; - state.time_axis.write().unwrap().scale = prev_note_length(scale) - }, - TimeCursorDec => { - let scale = state.time_axis.read().unwrap().scale; - state.time_axis.write().unwrap().point_dec(scale); - }, - TimeCursorInc => { - let scale = state.time_axis.read().unwrap().scale; - state.time_axis.write().unwrap().point_inc(scale); - }, - TimeScrollDec => { - let scale = state.time_axis.read().unwrap().scale; - state.time_axis.write().unwrap().start_dec(scale); - }, - TimeScrollInc => { - let scale = state.time_axis.read().unwrap().scale; - state.time_axis.write().unwrap().start_inc(scale); - }, - NoteCursorDec => { - let mut axis = state.note_axis.write().unwrap(); - axis.point_inc(1); - if let Some(point) = axis.point { if point > 73 { axis.point = Some(73); } } - }, - 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; } } - }, - NoteScrollDec => { - state.note_axis.write().unwrap().start_inc(1); - }, - NoteScrollInc => { - state.note_axis.write().unwrap().start_dec(1); - }, - NoteLengthDec => { - state.note_len = prev_note_length(state.note_len) - }, - NoteLengthInc => { - state.note_len = next_note_length(state.note_len) - }, - NotePageUp => { - let mut axis = state.note_axis.write().unwrap(); - axis.start_dec(3); - axis.point_dec(3); - }, - NotePageDown => { - let mut axis = state.note_axis.write().unwrap(); - axis.start_inc(3); - axis.point_inc(3); - }, - NoteAppend => { - if state.entered { - state.put(); - state.time_cursor_advance(); - } - }, - NoteSet => { - if state.entered { state.put(); } - }, - _ => unreachable!() - } - Ok(None) - } -} diff --git a/crates/tek_tui/src/tui_jack.rs b/crates/tek_tui/src/tui_jack.rs index a2ddd24e..db3c6e9f 100644 --- a/crates/tek_tui/src/tui_jack.rs +++ b/crates/tek_tui/src/tui_jack.rs @@ -24,9 +24,9 @@ impl Audio for SequencerTui { impl Audio for ArrangerTui { #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { if TracksAudio( - &mut self.app.tracks, - &mut self.app.note_buf, - &mut self.app.midi_buf, + &mut self.tracks, + &mut self.note_buf, + &mut self.midi_buf, Default::default(), ).process(client, scope) == Control::Quit { return Control::Quit diff --git a/crates/tek_tui/src/tui_model.rs b/crates/tek_tui/src/tui_model.rs index 82db3b67..ceb11d1a 100644 --- a/crates/tek_tui/src/tui_model.rs +++ b/crates/tek_tui/src/tui_model.rs @@ -2,24 +2,25 @@ use crate::*; /// Stores and displays time-related info. pub struct TransportTui { - jack: Arc>, + pub(crate) jack: Arc>, /// Playback state - playing: RwLock>, + pub(crate) playing: RwLock>, /// Global sample and usec at which playback started - started: RwLock>, + pub(crate) started: RwLock>, /// Current moment in time - current: Instant, + pub(crate) current: Instant, /// Note quantization factor - quant: Quantize, + pub(crate) quant: Quantize, /// Launch quantization factor - sync: LaunchSync, + pub(crate) sync: LaunchSync, /// JACK transport handle. - transport: jack::Transport, + pub(crate) transport: jack::Transport, /// Enable metronome? - metronome: bool, - focus: TransportFocus, - focused: bool, - size: Measure, + 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 { @@ -33,129 +34,136 @@ impl std::fmt::Debug for TransportTui { /// Root view for standalone `tek_sequencer`. pub struct SequencerTui { - jack: Arc>, - playing: RwLock>, - started: RwLock>, - current: Instant, - quant: Quantize, - sync: LaunchSync, - transport: jack::Transport, - metronome: bool, - phrases: Vec>>, - view_phrase: usize, - split: u16, + 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 - play_phrase: Option<(Instant, Option>>)>, + pub(crate) play_phrase: Option<(Instant, Option>>)>, /// Start time and next phrase - next_phrase: Option<(Instant, Option>>)>, + pub(crate) next_phrase: Option<(Instant, Option>>)>, /// Play input through output. - monitoring: bool, + pub(crate) monitoring: bool, /// Write input to sequence. - recording: bool, + pub(crate) recording: bool, /// Overdub input to sequence. - overdub: bool, + pub(crate) overdub: bool, /// Send all notes off - reset: bool, // TODO?: after Some(nframes) + pub(crate) reset: bool, // TODO?: after Some(nframes) /// Record from MIDI ports to current sequence. - midi_inputs: Vec>, + pub(crate) midi_inputs: Vec>, /// Play from current sequence to MIDI ports - midi_outputs: Vec>, + pub(crate) midi_outputs: Vec>, /// MIDI output buffer - midi_note: Vec, + pub(crate) note_buf: Vec, /// MIDI output buffer - midi_chunk: Vec>>, + pub(crate) midi_buf: Vec>>, /// Notes currently held at input - notes_in: Arc>, + pub(crate) notes_in: Arc>, /// Notes currently held at output - notes_out: Arc>, + pub(crate) notes_out: Arc>, + + pub(crate) entered: bool, + pub(crate) cursor: (usize, usize), } /// Root view for standalone `tek_arranger` pub struct ArrangerTui { - pub jack: Arc>, - pub transport: jack::Transport, - pub playing: RwLock>, - pub started: RwLock>, - pub current: Instant, - pub quant: Quantize, - pub sync: LaunchSync, - pub metronome: bool, - pub phrases: Vec>>, - pub phrase: usize, - pub tracks: Vec, - pub scenes: Vec, - pub name: Arc>, - pub splits: [u16;2], - pub selected: ArrangerSelection, - pub mode: ArrangerMode, - pub color: ItemColor, - pub entered: bool, - pub size: Measure, - pub note_buf: Vec, - pub midi_buf: Vec>>, - pub cursor: (usize, usize), - pub menu_bar: Option>, - pub status_bar: Option, - pub history: Vec, + pub(crate) jack: Arc>, + pub(crate) transport: jack::Transport, + pub(crate) playing: RwLock>, + pub(crate) started: RwLock>, + pub(crate) current: Instant, + pub(crate) quant: Quantize, + pub(crate) sync: LaunchSync, + 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, } #[derive(Default, Debug, Clone)] pub struct ArrangerScene { /// Name of scene - pub name: Arc>, + pub(crate) name: Arc>, /// Clips in scene, one per track - pub clips: Vec>>>, + pub(crate) clips: Vec>>>, /// Identifying color of scene - pub color: ItemColor, + pub(crate) color: ItemColor, } #[derive(Debug)] pub struct ArrangerTrack { /// Name of track - name: Arc>, + pub(crate) name: Arc>, /// Preferred width of track column - width: usize, + pub(crate) width: usize, /// Identifying color of track - color: ItemColor, + pub(crate) color: ItemColor, /// Start time and phrase being played - play_phrase: Option<(Instant, Option>>)>, + pub(crate) play_phrase: Option<(Instant, Option>>)>, /// Start time and next phrase - next_phrase: Option<(Instant, Option>>)>, + pub(crate) next_phrase: Option<(Instant, Option>>)>, /// Play input through output. - monitoring: bool, + pub(crate) monitoring: bool, /// Write input to sequence. - recording: bool, + pub(crate) recording: bool, /// Overdub input to sequence. - overdub: bool, + pub(crate) overdub: bool, /// Send all notes off - reset: bool, // TODO?: after Some(nframes) + pub(crate) reset: bool, // TODO?: after Some(nframes) /// Record from MIDI ports to current sequence. - midi_ins: Vec>, + pub(crate) midi_ins: Vec>, /// Play from current sequence to MIDI ports - midi_outs: Vec>, + pub(crate) midi_outs: Vec>, /// Notes currently held at input - notes_in: Arc>, + pub(crate) notes_in: Arc>, /// Notes currently held at output - notes_out: Arc>, + 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 } pub struct PhrasesTui { /// Collection of phrases - pub phrases: Vec>>, + pub(crate) phrases: Vec>>, /// Selected phrase - pub phrase: usize, + pub(crate) phrase: usize, /// Scroll offset - pub scroll: usize, + pub(crate) scroll: usize, /// Mode switch - pub mode: Option, + pub(crate) mode: Option, /// Whether this widget is focused - pub focused: bool, + pub(crate) focused: bool, /// Whether this widget is entered - pub entered: bool, + pub(crate) entered: bool, } /// Modes for phrase pool @@ -169,29 +177,29 @@ pub enum PhrasesMode { /// Contains state for viewing and editing a phrase pub struct PhraseTui { /// Phrase being played - pub phrase: Option>>, + pub(crate) phrase: Option>>, /// Length of note that will be inserted, in pulses - pub note_len: usize, + pub(crate) note_len: usize, /// The full piano keys are rendered to this buffer - pub keys: Buffer, + pub(crate) keys: Buffer, /// The full piano roll is rendered to this buffer - pub buffer: BigBuffer, + pub(crate) buffer: BigBuffer, /// Cursor/scroll/zoom in pitch axis - pub note_axis: RwLock>, + pub(crate) note_axis: RwLock>, /// Cursor/scroll/zoom in time axis - pub time_axis: RwLock>, + pub(crate) time_axis: RwLock>, /// Whether this widget is focused - pub focused: bool, + pub(crate) focused: bool, /// Whether note enter mode is enabled - pub entered: bool, + pub(crate) entered: bool, /// Display mode - pub mode: bool, + pub(crate) mode: bool, /// Notes currently held at input - pub notes_in: Arc>, + pub(crate) notes_in: Arc>, /// Notes currently held at output - pub notes_out: Arc>, + pub(crate) notes_out: Arc>, /// Current position of global playhead - pub now: Arc, + pub(crate) now: Arc, /// Width and height of notes area at last render - pub size: Measure + pub(crate) size: Measure } diff --git a/crates/tek_tui/src/tui_phrase.rs b/crates/tek_tui/src/tui_phrase.rs deleted file mode 100644 index d21d4223..00000000 --- a/crates/tek_tui/src/tui_phrase.rs +++ /dev/null @@ -1,141 +0,0 @@ -use crate::*; - -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()), - 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 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); - } - - pub fn put (&mut self) { - if let (Some(phrase), Some(time), Some(note)) = ( - &self.phrase, - self.time_axis.read().unwrap().point, - self.note_axis.read().unwrap().point, - ) { - let mut phrase = phrase.write().unwrap(); - let key: u7 = u7::from((127 - note) as u8); - let vel: u7 = 100.into(); - let start = time; - let end = (start + self.note_len) % phrase.length; - phrase.notes[time].push(MidiMessage::NoteOn { key, vel }); - phrase.notes[end].push(MidiMessage::NoteOff { key, vel }); - self.buffer = Self::redraw(&phrase); - } - } - /// Select which pattern to display. This pre-renders it to the buffer at full resolution. - pub fn show (&mut self, phrase: Option<&Arc>>) { - if let Some(phrase) = phrase { - self.phrase = Some(phrase.clone()); - self.time_axis.write().unwrap().clamp = Some(phrase.read().unwrap().length); - self.buffer = Self::redraw(&*phrase.read().unwrap()); - } else { - self.phrase = None; - self.time_axis.write().unwrap().clamp = Some(0); - self.buffer = Default::default(); - } - } - fn redraw (phrase: &Phrase) -> BigBuffer { - let mut buf = BigBuffer::new(usize::MAX.min(phrase.length), 65); - Self::fill_seq_bg(&mut buf, phrase.length, phrase.ppq); - Self::fill_seq_fg(&mut buf, &phrase); - buf - } - fn fill_seq_bg (buf: &mut BigBuffer, length: usize, ppq: usize) { - for x in 0..buf.width { - // Only fill as far as phrase length - if x as usize >= length { break } - // Fill each row with background characters - for y in 0 .. buf.height { - buf.get_mut(x, y).map(|cell|{ - cell.set_char(if ppq == 0 { - '·' - } else if x % (4 * ppq) == 0 { - '│' - } else if x % ppq == 0 { - '╎' - } else { - '·' - }); - cell.set_fg(Color::Rgb(48, 64, 56)); - cell.modifier = Modifier::DIM; - }); - } - } - } - fn fill_seq_fg (buf: &mut BigBuffer, phrase: &Phrase) { - let mut notes_on = [false;128]; - for x in 0..buf.width { - if x as usize >= phrase.length { - break - } - if let Some(notes) = phrase.notes.get(x as usize) { - if phrase.percussive { - for note in notes { - match note { - MidiMessage::NoteOn { key, .. } => - notes_on[key.as_int() as usize] = true, - _ => {} - } - } - } else { - for note in notes { - match note { - MidiMessage::NoteOn { key, .. } => - notes_on[key.as_int() as usize] = true, - MidiMessage::NoteOff { key, .. } => - notes_on[key.as_int() as usize] = false, - _ => {} - } - } - } - for y in 0..buf.height { - if y >= 64 { - break - } - if let Some(block) = half_block( - notes_on[y as usize * 2], - notes_on[y as usize * 2 + 1], - ) { - buf.get_mut(x, y).map(|cell|{ - cell.set_char(block); - cell.set_fg(Color::White); - }); - } - } - if phrase.percussive { - notes_on.fill(false); - } - } - } - } -} diff --git a/crates/tek_tui/src/tui_sequencer.rs b/crates/tek_tui/src/tui_sequencer.rs deleted file mode 100644 index c7b7e813..00000000 --- a/crates/tek_tui/src/tui_sequencer.rs +++ /dev/null @@ -1 +0,0 @@ -use crate::*; diff --git a/crates/tek_tui/src/tui_status.rs b/crates/tek_tui/src/tui_status.rs index ed47881f..1680cd23 100644 --- a/crates/tek_tui/src/tui_status.rs +++ b/crates/tek_tui/src/tui_status.rs @@ -94,6 +94,7 @@ impl StatusBar for ArrangerStatus { } fn update (&mut self, (focused, selected, entered): &Self::State) { *self = match focused { + ArrangerFocus::Menu => { todo!() }, ArrangerFocus::Transport => ArrangerStatus::Transport, ArrangerFocus::Arranger => match selected { ArrangerSelection::Mix => ArrangerStatus::ArrangerMix, diff --git a/crates/tek_tui/src/tui_view.rs b/crates/tek_tui/src/tui_view.rs index bb7a909e..c42fd135 100644 --- a/crates/tek_tui/src/tui_view.rs +++ b/crates/tek_tui/src/tui_view.rs @@ -808,10 +808,10 @@ pub trait PhraseViewState: Send + Sync { impl PhraseViewState for PhraseTui { fn focused (&self) -> bool { - &self.focused + self.focused } fn entered (&self) -> bool { - &self.entered + self.entered } fn keys (&self) -> &Buffer { &self.keys @@ -823,7 +823,7 @@ impl PhraseViewState for PhraseTui { &self.buffer } fn note_len (&self) -> usize { - &self.note_len + self.note_len } fn note_axis (&self) -> &RwLock> { &self.note_axis @@ -1066,7 +1066,7 @@ pub struct PhraseLength { impl PhraseLength { pub fn new (pulses: usize, focus: Option) -> Self { - Self { _engine: Default::default(), ppq: PPQ, bpb: 4, pulses, focus } + Self { ppq: PPQ, bpb: 4, pulses, focus } } pub fn bars (&self) -> usize { self.pulses / (self.bpb * self.ppq)