From c1e6a0137efd3ca5a587038f3240edd260759020 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 16 Dec 2024 22:05:36 +0100 Subject: [PATCH] fix range of piano roll --- crates/tek/src/api/note.rs | 12 +- crates/tek/src/tui/phrase_editor.rs | 2 +- crates/tek/src/tui/piano_horizontal.rs | 159 ++++++++++++------------- 3 files changed, 83 insertions(+), 90 deletions(-) diff --git a/crates/tek/src/api/note.rs b/crates/tek/src/api/note.rs index 004aaa15..abbc4bc0 100644 --- a/crates/tek/src/api/note.rs +++ b/crates/tek/src/api/note.rs @@ -6,7 +6,7 @@ pub trait MidiViewport: MidiRange + MidiPoint + HasSize { fn autoscroll (&self) { let note_lo = self.note_lo(); let note_axis = self.note_axis(); - let note_hi = self.note_hi().saturating_sub(1); + let note_hi = self.note_hi(); let note_point = self.note_point().min(127); if note_point < note_lo { self.set_note_lo(note_point); @@ -57,7 +57,7 @@ pub trait MidiRange { fn set_note_lo (&self, x: usize); fn note_axis (&self) -> usize; fn time_axis (&self) -> usize; - fn note_hi (&self) -> usize { self.note_lo() + self.note_axis() } + fn note_hi (&self) -> usize { (self.note_lo() + self.note_axis().saturating_sub(1)).min(127) } fn time_end (&self) -> usize { self.time_start() + self.time_axis() * self.time_zoom() } } impl MidiRange for MidiRangeModel { @@ -67,8 +67,8 @@ impl MidiRange for MidiRangeModel { fn set_time_lock (&self, x: bool) { self.time_lock.store(x, Relaxed); } fn time_start (&self) -> usize { self.time_start.load(Relaxed) } fn set_time_start (&self, x: usize) { self.time_start.store(x, Relaxed); } - fn note_lo (&self) -> usize { self.note_lo.load(Relaxed) } - fn set_note_lo (&self, x: usize) { self.note_lo.store(x, Relaxed); } + fn note_lo (&self) -> usize { self.note_lo.load(Relaxed).min(127) } + fn set_note_lo (&self, x: usize) { self.note_lo.store(x.min(127), Relaxed); } fn note_axis (&self) -> usize { self.note_axis.load(Relaxed) } fn time_axis (&self) -> usize { self.time_axis.load(Relaxed) } } @@ -103,8 +103,8 @@ pub trait MidiPoint { impl MidiPoint for MidiPointModel { fn note_len (&self) -> usize { self.note_len.load(Relaxed)} fn set_note_len (&self, x: usize) { self.note_len.store(x, Relaxed) } - fn note_point (&self) -> usize { self.note_point.load(Relaxed) } - fn set_note_point (&self, x: usize) { self.note_point.store(x, Relaxed) } + fn note_point (&self) -> usize { self.note_point.load(Relaxed).min(127) } + fn set_note_point (&self, x: usize) { self.note_point.store(x.min(127), Relaxed) } fn time_point (&self) -> usize { self.time_point.load(Relaxed) } fn set_time_point (&self, x: usize) { self.time_point.store(x, Relaxed) } } diff --git a/crates/tek/src/tui/phrase_editor.rs b/crates/tek/src/tui/phrase_editor.rs index f23c704d..bf260365 100644 --- a/crates/tek/src/tui/phrase_editor.rs +++ b/crates/tek/src/tui/phrase_editor.rs @@ -87,7 +87,7 @@ impl Command for PhraseCommand { SetTimeZoom(x) => { state.set_time_zoom(x); }, SetTimeLock(x) => { state.set_time_lock(x); }, SetTimeScroll(x) => { state.set_time_start(x); }, - SetNoteScroll(x) => { state.set_note_lo(x); }, + SetNoteScroll(x) => { state.set_note_lo(x.min(127)); }, SetNoteLength(x) => { state.set_note_len(x); }, SetTimeCursor(x) => { state.set_time_point(x); diff --git a/crates/tek/src/tui/piano_horizontal.rs b/crates/tek/src/tui/piano_horizontal.rs index 722f7236..f55b07f3 100644 --- a/crates/tek/src/tui/piano_horizontal.rs +++ b/crates/tek/src/tui/piano_horizontal.rs @@ -1,6 +1,28 @@ use crate::*; use super::*; +fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iterator { + (note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n)) +} + +fn to_key (note: usize) -> &'static str { + match note % 12 { + 11 => "████▌", + 10 => " ", + 9 => "████▌", + 8 => " ", + 7 => "████▌", + 6 => " ", + 5 => "████▌", + 4 => "████▌", + 3 => " ", + 2 => "████▌", + 1 => " ", + 0 => "████▌", + _ => unreachable!(), + } +} + /// A phrase, rendered as a horizontal piano roll. pub struct PianoHorizontal { phrase: Option>>, @@ -39,16 +61,13 @@ impl PianoHorizontal { render!(|self: PianoHorizontal|{ let color = self.color; - let range = &self.range; - let point = &self.point; - let buffer = &self.buffer; - let keys = move||PianoHorizontalKeys { range, point, color }; //note_lo, note_hi, note_point: Some(note_point), }; - let timeline = move||PianoHorizontalTimeline { range, color }; - let notes = move||PianoHorizontalNotes { range, buffer }; - let cursor = move||PianoHorizontalCursor { range, point }; + let keys = move||PianoHorizontalKeys(&self); + let timeline = move||PianoHorizontalTimeline(&self); + let notes = move||PianoHorizontalNotes(&self); + let cursor = move||PianoHorizontalCursor(&self); let keys_width = 5; Tui::fill_xy(Tui::bg(color.darker.rgb, Bsp::s( - Tui::fill_x(Tui::push_x(keys_width, timeline())), + Tui::fixed_y(1, Tui::fill_x(Tui::push_x(keys_width, timeline()))), Bsp::w( Tui::shrink_x(keys_width, Tui::fill_xy( lay!([&self.size, Tui::fill_xy(lay!([ @@ -61,55 +80,34 @@ render!(|self: PianoHorizontal|{ ))) }); -pub struct PianoHorizontalTimeline<'a> { - color: ItemPalette, - range: &'a MidiRangeModel, -} +pub struct PianoHorizontalTimeline<'a>(&'a PianoHorizontal); render!(|self: PianoHorizontalTimeline<'a>|Tui::fixed_y(1, Tui::fg_bg( - self.color.lightest.rgb, - self.color.darkest.rgb, - format!("{}*{}", self.range.time_start(), self.range.time_zoom()).as_str() + self.0.color.lightest.rgb, + self.0.color.darkest.rgb, + format!("{}*{}", self.0.time_start(), self.0.time_zoom()).as_str() ))); -pub struct PianoHorizontalKeys<'a> { - color: ItemPalette, - range: &'a MidiRangeModel, - point: &'a MidiPointModel, -} +pub struct PianoHorizontalKeys<'a>(&'a PianoHorizontal); render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok({ - let color = self.color; - let note_lo = self.range.note_lo(); - let note_hi = self.range.note_hi(); - let note_point = self.point.note_point(); + let color = self.0.color; + let note_lo = self.0.note_lo(); + let note_hi = self.0.note_hi(); + let note_point = self.0.note_point(); let [x, y0, w, h] = to.area().xywh(); let key_style = Some(Style::default().fg(Color::Rgb(192, 192, 192)).bg(Color::Rgb(0, 0, 0))); let off_style = Some(Style::default().fg(TuiTheme::g(160))); - let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(self.color.light.rgb).bold()); - for (y, note) in (note_lo..note_hi).rev().enumerate().map(|(y, n)|(y0 + y as u16, n)) { - let key = match note % 12 { - 11 => "████▌", - 10 => " ", - 9 => "████▌", - 8 => " ", - 7 => "████▌", - 6 => " ", - 5 => "████▌", - 4 => "████▌", - 3 => " ", - 2 => "████▌", - 1 => " ", - 0 => "████▌", - _ => unreachable!(), - }; - to.blit(&key, x, y, key_style); - + let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.light.rgb).bold()); + for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { + to.blit(&to_key(note), x, screen_y, key_style); + if note > 127 { + continue + } if note == note_point { - to.blit(&format!("{:<5}", to_note_name(note)), x, y, on_style) + to.blit(&format!("{:<5}", to_note_name(note)), x, screen_y, on_style) } else { - to.blit(&to_note_name(note), x, y, off_style) + to.blit(&to_note_name(note), x, screen_y, off_style) }; } - let debug = false; if debug { to.blit(&format!("XYU"), x, y0, None); @@ -122,31 +120,28 @@ render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok({ } }))); -pub struct PianoHorizontalCursor<'a> { - range: &'a MidiRangeModel, - point: &'a MidiPointModel, -} +pub struct PianoHorizontalCursor<'a>(&'a PianoHorizontal); render!(|self: PianoHorizontalCursor<'a>|render(|to|Ok({ + let note_hi = self.0.note_hi(); + let note_len = self.0.note_len(); + let note_lo = self.0.note_lo(); + let note_point = self.0.note_point(); + let time_point = self.0.time_point(); + let time_start = self.0.time_start(); + let time_zoom = self.0.time_zoom(); let [x0, y0, w, _] = to.area().xywh(); - let note_hi = self.range.note_hi(); - let note_len = self.point.note_len(); - let note_lo = self.range.note_lo(); - let note_point = self.point.note_point(); - let time_point = self.point.time_point(); - let time_start = self.range.time_start(); - let time_zoom = self.range.time_zoom(); let style = Some(Style::default().fg(Color::Rgb(0,255,0))); - for (input_y, output_y, note) in (note_lo..note_hi).rev().enumerate().map(|(y, n)|(y, y0 + y as u16, n)) { + for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { if note == note_point { for x in 0..w { - let output_x = x0 + x as u16; + let screen_x = x0 + x as u16; let time_1 = time_start + x as usize * time_zoom; let time_2 = time_1 + time_zoom; if time_1 <= time_point && time_point < time_2 { - to.blit(&"█", output_x, output_y, style); + to.blit(&"█", screen_x, screen_y, style); let tail = note_len as u16 / time_zoom as u16; - for x_tail in (output_x + 1)..(output_x + tail) { - to.blit(&"▂", x_tail, output_y, style); + for x_tail in (screen_x + 1)..(screen_x + tail) { + to.blit(&"▂", x_tail, screen_y, style); } break } @@ -156,36 +151,34 @@ render!(|self: PianoHorizontalCursor<'a>|render(|to|Ok({ } }))); -pub struct PianoHorizontalNotes<'a> { - range: &'a MidiRangeModel, - buffer: &'a BigBuffer, -} +pub struct PianoHorizontalNotes<'a>(&'a PianoHorizontal); render!(|self: PianoHorizontalNotes<'a>|render(|to|Ok({ - let note_hi = self.range.note_hi(); - let note_lo = self.range.note_lo(); - let time_start = self.range.time_start(); - let source = &self.buffer; + let time_start = self.0.time_start(); + let note_hi = self.0.note_hi(); + let note_lo = self.0.note_lo(); + let note_point = self.0.note_point(); + let source = &self.0.buffer; let [x0, y0, w, h] = to.area().xywh(); - if h as usize != self.range.note_axis() { + if h as usize != self.0.note_axis() { panic!("area height mismatch"); } - for (input_x, output_x) in (x0..x0+w).enumerate() { - for (input_y, output_y, note) in (note_lo..note_hi).rev().enumerate().map(|(y, n)|(y, y0 + y as u16, n)) { - if input_x % 10 == 0 { - to.blit(&format!("{input_y} {note}"), output_x, output_y, None); + for (area_x, screen_x) in (x0..x0+w).enumerate() { + for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { + if area_x % 10 == 0 { + to.blit(&format!("{area_y} {note}"), screen_x, screen_y, None); } } - //for (input_y, output_y) in (y0..=y0+h).enumerate() { - //if note_hi < input_y { + //for (area_y, screen_y) in (y0..=y0+h).enumerate() { + //if note_hi < area_y { //continue //} //if note_hi > 127 { //continue //} - //let note = note_hi - input_y; - //if input_x % 4 == 0 { - //to.blit(&format!("{note}"), output_x, output_y, None); + //let note = note_hi - area_y; + //if area_x % 4 == 0 { + //to.blit(&format!("{note}"), screen_x, screen_y, None); //} ////if y >= note_hi { ////break @@ -198,7 +191,7 @@ render!(|self: PianoHorizontalNotes<'a>|render(|to|Ok({ ////let in_x = source_x < source.width; ////let in_y = source_y < source.height; ////if in_x && in_y { - ////let output_cell = to.buffer.get_mut(output_x, output_y); + ////let output_cell = to.buffer.get_mut(screen_x, screen_y); ////if let Some(source_cell) = source.get(source_x, source_y) { //*output_cell = source_cell.clone(); ////} @@ -213,7 +206,8 @@ render!(|self: PianoHorizontalNotes<'a>|render(|to|Ok({ to.blit(&format!("y0={y0} "), x0, y0+2, None); to.blit(&format!("note_lo={note_lo}, {} ", to_note_name(note_lo)), x0, y0+3, None); to.blit(&format!("note_hi={note_hi}, {} ", to_note_name(note_hi)), x0, y0+4, None); - to.blit(&format!("time_start={time_start} "), x0, y0+5, None); + to.blit(&format!("note_point={note_point}, {} ", to_note_name(note_point)), x0, y0+5, None); + to.blit(&format!("time_start={time_start} "), x0, y0+6, None); return Ok(()); } }))); @@ -293,7 +287,6 @@ impl MidiRange for PianoHorizontal { fn note_lo (&self) -> usize { self.range.note_lo() } fn note_axis (&self) -> usize { self.range.note_axis() } fn time_axis (&self) -> usize { self.range.time_axis() } - fn note_hi (&self) -> usize { self.note_lo() + self.note_axis() } } impl MidiPoint for PianoHorizontal { fn note_len (&self) -> usize { self.point.note_len()}