diff --git a/crates/tek_tui/src/tui_control_phrase_editor.rs b/crates/tek_tui/src/tui_control_phrase_editor.rs index 8fa4807e..3a64a621 100644 --- a/crates/tek_tui/src/tui_control_phrase_editor.rs +++ b/crates/tek_tui/src/tui_control_phrase_editor.rs @@ -122,3 +122,9 @@ impl Command for PhraseCommand { }) } } + +#[derive(Copy, Clone, Debug)] +pub enum PhraseEditMode { + Note, + Scroll, +} diff --git a/crates/tek_tui/src/tui_model_phrase_editor.rs b/crates/tek_tui/src/tui_model_phrase_editor.rs index 205cbfa6..8c1387ad 100644 --- a/crates/tek_tui/src/tui_model_phrase_editor.rs +++ b/crates/tek_tui/src/tui_model_phrase_editor.rs @@ -57,13 +57,16 @@ impl Default for PhraseEditorModel { now: Pulse::default().into(), size: Measure::new(), edit_mode: PhraseEditMode::Scroll, - view_mode: PhraseViewMode::Horizontal, note_lo: 0.into(), note_point: 24.into(), time_start: 0.into(), time_point: 0.into(), time_clamp: 0.into(), time_scale: 24.into(), + view_mode: PhraseViewMode::PianoHorizontal { + time_zoom: 24, + note_zoom: PhraseViewNoteZoom::N(1) + }, } } } @@ -81,7 +84,7 @@ impl PhraseEditorModel { 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); + self.buffer = self.view_mode.draw(&phrase); } } /// Move time cursor forward by current note length @@ -96,7 +99,7 @@ impl PhraseEditorModel { if let Some(phrase) = phrase { self.phrase = Some(phrase.clone()); self.time_clamp.store(phrase.read().unwrap().length, Ordering::Relaxed); - self.buffer = Self::redraw(&*phrase.read().unwrap()); + self.buffer = self.view_mode.draw(&*phrase.read().unwrap()); } else { self.phrase = None; self.time_clamp.store(0, Ordering::Relaxed); @@ -105,19 +108,6 @@ impl PhraseEditorModel { } } -#[derive(Copy, Clone, Debug)] -pub enum PhraseEditMode { - Note, - Scroll, -} - -#[derive(Copy, Clone, Debug)] -pub enum PhraseViewMode { - Horizontal, - HorizontalHalf, - Vertical, -} - pub trait HasEditor { fn editor (&self) -> &PhraseEditorModel; fn editor_focused (&self) -> bool; diff --git a/crates/tek_tui/src/tui_view_phrase_editor.rs b/crates/tek_tui/src/tui_view_phrase_editor.rs index 55229bab..c2dee9cd 100644 --- a/crates/tek_tui/src/tui_view_phrase_editor.rs +++ b/crates/tek_tui/src/tui_view_phrase_editor.rs @@ -1,14 +1,14 @@ use crate::*; pub struct PhraseView<'a> { - focused: bool, - entered: bool, - phrase: &'a Option>>, - buffer: &'a BigBuffer, - note_len: usize, - now: &'a Arc, - - size: &'a Measure, + focused: bool, + entered: bool, + phrase: &'a Option>>, + buffer: &'a BigBuffer, + note_len: usize, + now: &'a Arc, + size: &'a Measure, + view_mode: &'a PhraseViewMode, note_point: usize, note_range: (usize, usize), @@ -42,14 +42,14 @@ impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> { } Self { - focused: state.editor_focused(), - entered: state.editor_entered(), - note_len: editor.note_len, - phrase: &editor.phrase, - buffer: &editor.buffer, - now: &editor.now, - - size: &editor.size, + focused: state.editor_focused(), + entered: state.editor_entered(), + note_len: editor.note_len, + phrase: &editor.phrase, + buffer: &editor.buffer, + now: &editor.now, + size: &editor.size, + view_mode: &editor.view_mode, note_point, note_range: (note_lo, note_hi), @@ -72,6 +72,7 @@ impl<'a> Content for PhraseView<'a> { phrase, size, buffer, + view_mode, note_len, note_range: (note_lo, note_hi), note_names: (note_lo_name, note_hi_name), @@ -122,18 +123,19 @@ impl<'a> Content for PhraseView<'a> { size.set_wh(area.w(), h - 1); Ok(if to.area().h() >= 2 { let area = to.area(); - to.buffer_update(area, &move |cell, x, y|{ - cell.set_bg(notes_bg_null); - let src_x = (x as usize + time_start) * time_scale; - let src_y = y as usize + note_lo / 2; - if src_x < buffer.width && src_y < buffer.height - 1 { - buffer.get(src_x, (note_hi - y as usize) / 2).map(|src|{ - cell.set_symbol(src.symbol()); - cell.set_fg(src.fg); - cell.set_bg(src.bg); - }); - } - }); + view_mode.blit(buffer, &mut to.buffer, area, *time_start, *note_hi); + //to.buffer_update(area, &move |cell, x, y|{ + //cell.set_bg(notes_bg_null); + //let src_x = (x as usize + time_start) * time_scale; + //let src_y = y as usize + note_lo / 2; + //if src_x < buffer.width && src_y < buffer.height - 1 { + //buffer.get(src_x, (note_hi - y as usize) / 2).map(|src|{ + //cell.set_symbol(src.symbol()); + //cell.set_fg(src.fg); + //cell.set_bg(src.bg); + //}); + //} + //}); }) }; let cursor = move|to: &mut TuiOutput|Ok(if *focused && *entered { @@ -230,27 +232,53 @@ pub enum PhraseViewMode { #[derive(Copy, Clone, Debug)] pub enum PhraseViewNoteZoom { - Half, N(usize), + Half, + Octant, } impl PhraseViewMode { /// Return a new [BigBuffer] containing a render of the phrase. - fn draw (&self, phrase: &Phrase) -> BigBuffer { + pub fn draw (&self, phrase: &Phrase) -> BigBuffer { let mut buffer = BigBuffer::new(self.buffer_width(phrase), self.buffer_height(phrase)); match self { Self::PianoHorizontal { time_zoom, note_zoom } => match note_zoom { - PhraseViewNoteZoom::Half => Self::draw_piano_horizontal_half( - &mut buffer, phrase, *time_zoom - ), PhraseViewNoteZoom::N(_) => Self::draw_piano_horizontal( &mut buffer, phrase, *time_zoom, 1 ), + _ => unimplemented!(), }, _ => unimplemented!(), } buffer } + /// Draw a subsection of the [BigBuffer] onto a regular ratatui [Buffer]. + fn blit ( + &self, + source: &BigBuffer, + target: &mut Buffer, + area: impl Area, + time_start: usize, + note_hi: usize, + ) { + match self { + Self::PianoHorizontal { .. } => { + let [x0, y0, w, h] = area.xywh(); + for (x, target_x) in (x0..x0+w).enumerate() { + for (y, target_y) in (y0..y0+h).enumerate() { + let source_x = time_start + x; + let source_y = note_hi - y; + let target_cell = target.get_mut(target_x, target_y); + target_cell.set_char('x'); + if let Some(source_cell) = source.get(source_x, source_y) { + *target_cell = source_cell.clone(); + } + } + } + }, + _ => unimplemented!() + } + } /// Determine the required width to render the phrase. fn buffer_width (&self, phrase: &Phrase) -> usize { match self { @@ -260,6 +288,7 @@ impl PhraseViewMode { Self::PianoVertical { note_zoom, .. } => match note_zoom { PhraseViewNoteZoom::Half => 64, PhraseViewNoteZoom::N(n) => 128*n, + _ => unimplemented!() }, } } @@ -269,19 +298,16 @@ impl PhraseViewMode { Self::PianoHorizontal { note_zoom, .. } => match note_zoom { PhraseViewNoteZoom::Half => 64, PhraseViewNoteZoom::N(n) => 128*n, + _ => unimplemented!() }, Self::PianoVertical { time_zoom, .. } => { phrase.length / time_zoom }, } } - fn draw_piano_horizontal_half ( - _: &mut BigBuffer, _: &Phrase, _: usize - ) { - unimplemented!() - } + /// Draw the piano roll using full blocks on note on and half blocks on legato: █▄ █▄ █▄ fn draw_piano_horizontal ( - buffer: &mut BigBuffer, phrase: &Phrase, time_zoom: usize, note_zoom: usize + buffer: &mut BigBuffer, phrase: &Phrase, time_zoom: usize, _: usize ) { let mut notes_on = [false;128]; for col in 0..buffer.width { @@ -295,6 +321,8 @@ impl PhraseViewMode { } else { cell.set_char(' '); } + cell.set_fg(Color::Rgb(255, 255, 255)); + cell.set_bg(Color::Rgb(28, 35, 25)); } for event in phrase.notes[time].iter() { match event { @@ -311,10 +339,17 @@ impl PhraseViewMode { } } } - //for row in 0..buf.height { - //let pitch = 127 - row; - - //} + } + /// TODO: Draw the piano roll using octant blocks (U+1CD00-U+1CDE5) + fn draw_piano_horizontal_octant ( + _: &mut BigBuffer, _: &Phrase, _: usize + ) { + unimplemented!() + } + /// TODO: Draw the piano roll using half blocks: ▄▀▄ + fn draw_piano_horizontal_half ( + _: &mut BigBuffer, _: &Phrase, _: usize + ) { unimplemented!() } }