From 1c93646fcfb43097379909ae272200f48b469ef6 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 16 Dec 2024 21:47:18 +0100 Subject: [PATCH] wip: fix piano roll area --- crates/tek/src/api/note.rs | 2 +- crates/tek/src/layout/bsp.rs | 2 +- crates/tek/src/tui/app_sequencer.rs | 96 ++++---------------------- crates/tek/src/tui/phrase_list.rs | 70 +++++++++++++++++++ crates/tek/src/tui/piano_horizontal.rs | 94 +++++++++++++++---------- 5 files changed, 144 insertions(+), 120 deletions(-) diff --git a/crates/tek/src/api/note.rs b/crates/tek/src/api/note.rs index 35862d79..004aaa15 100644 --- a/crates/tek/src/api/note.rs +++ b/crates/tek/src/api/note.rs @@ -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().saturating_sub(1) } + fn note_hi (&self) -> usize { self.note_lo() + self.note_axis() } fn time_end (&self) -> usize { self.time_start() + self.time_axis() * self.time_zoom() } } impl MidiRange for MidiRangeModel { diff --git a/crates/tek/src/layout/bsp.rs b/crates/tek/src/layout/bsp.rs index 83ffd2ba..4b0b255f 100644 --- a/crates/tek/src/layout/bsp.rs +++ b/crates/tek/src/layout/bsp.rs @@ -60,7 +60,7 @@ impl, Y: Render> Render for Bsp { let s_b = b.min_size(s)?.unwrap_or(n); let s_y = s_a.h().into(); to.render_in(to.area().clip_h(s_y).into(), a)?; - to.render_in(to.area().push_y(s_y).into(), b)?; + to.render_in(to.area().push_y(s_y).shrink_y(s_y).into(), b)?; }, Self::W(a, b) => { let n = [0.into(), 0.into()].into(); diff --git a/crates/tek/src/tui/app_sequencer.rs b/crates/tek/src/tui/app_sequencer.rs index f933a0e2..cc1bf7e3 100644 --- a/crates/tek/src/tui/app_sequencer.rs +++ b/crates/tek/src/tui/app_sequencer.rs @@ -163,19 +163,19 @@ audio!(|self:SequencerTui, client, scope|{ }); render!(|self: SequencerTui|{ - let w = self.size.w(); - let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; - let pool_w = if self.show_pool { phrase_w } else { 0 }; - let pool = Tui::fill_y(Tui::at_e(PhraseListView(&self.phrases))); - let with_pool = move|x|Tui::split_w(false, pool_w, pool, x); - let with_status = |x|Tui::split_n(false, 2, SequencerStatusBar::from(self), x); - let with_bar = |x|Tui::split_n(false, 3, PhraseEditStatus(&self.editor), x); - let with_size = |x|lay!([self.size, x]); - let editor = with_bar(with_pool(Tui::fill_xy(&self.editor))); - let color = self.player.play_phrase().as_ref().map(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)).flatten().clone(); - let play = Tui::fixed_xy(5, 2, PlayPause(self.clock.is_rolling())); - let transport = Tui::fixed_y(2, TransportView::from((self, color, true))); - let toolbar = row!([play, col!([ + let w = self.size.w(); + let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; + let pool_w = if self.show_pool { phrase_w } else { 0 }; + let pool = Tui::fill_y(Tui::at_e(PhraseListView(&self.phrases))); + let with_pool = move|x|Tui::split_w(false, pool_w, pool, x); + let with_status = |x|Tui::split_n(false, 2, SequencerStatusBar::from(self), x); + let with_editbar = |x|x;//Tui::split_n(false, 3, PhraseEditStatus(&self.editor), x); + let with_size = |x|lay!([self.size, x]); + let editor = with_editbar(with_pool(Tui::fill_xy(&self.editor))); + let color = self.player.play_phrase().as_ref().map(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)).flatten().clone(); + let play = Tui::fixed_xy(5, 2, PlayPause(self.clock.is_rolling())); + let transport = Tui::fixed_y(2, TransportView::from((self, color, true))); + let toolbar = row!([play, col!([ PhraseSelector::play_phrase(&self.player), PhraseSelector::next_phrase(&self.player), ]), transport]); @@ -187,76 +187,6 @@ has_clock!(|self:SequencerTui|&self.clock); has_phrases!(|self:SequencerTui|self.phrases.phrases); has_editor!(|self:SequencerTui|self.editor); -pub struct PhraseSelector { - pub(crate) title: &'static str, - pub(crate) name: String, - pub(crate) color: ItemPalette, - pub(crate) time: String, -} - -// TODO: Display phrases always in order of appearance -render!(|self: PhraseSelector|Tui::fixed_xy(24, 1, row!([ - Tui::fg(self.color.lighter.rgb, Tui::bold(true, &self.title)), - Tui::fg_bg(self.color.lighter.rgb, self.color.base.rgb, row!([ - format!("{:8}", &self.name[0..8.min(self.name.len())]), - Tui::bg(self.color.dark.rgb, &self.time), - ])), -]))); - -impl PhraseSelector { - // beats elapsed - pub fn play_phrase (state: &T) -> Self { - let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() { - let Phrase { ref name, color, .. } = *phrase.read().unwrap(); - (name.clone(), color) - } else { - ("".to_string(), ItemPalette::from(TuiTheme::g(64))) - }; - let time = if let Some(elapsed) = state.pulses_since_start_looped() { - format!("+{:>}", state.clock().timebase.format_beats_0(elapsed)) - } else { - String::from(" ") - }; - Self { title: " Now|", time, name, color, } - } - // beats until switchover - pub fn next_phrase (state: &T) -> Self { - let (time, name, color) = if let Some((t, Some(phrase))) = state.next_phrase() { - let Phrase { ref name, color, .. } = *phrase.read().unwrap(); - let time = { - let target = t.pulse.get(); - let current = state.clock().playhead.pulse.get(); - if target > current { - let remaining = target - current; - format!("-{:>}", state.clock().timebase.format_beats_0(remaining)) - } else { - String::new() - } - }; - (time, name.clone(), color) - } else if let Some((_, Some(phrase))) = state.play_phrase() { - let phrase = phrase.read().unwrap(); - if phrase.loop_on { - (" ".into(), phrase.name.clone(), phrase.color.clone()) - } else { - (" ".into(), " ".into(), TuiTheme::g(64).into()) - } - } else { - (" ".into(), " ".into(), TuiTheme::g(64).into()) - }; - Self { title: " Next|", time, name, color, } - } - pub fn edit_phrase (phrase: &Option>>) -> Self { - let (time, name, color) = if let Some(phrase) = phrase { - let phrase = phrase.read().unwrap(); - (format!("{}", phrase.length), phrase.name.clone(), phrase.color) - } else { - ("".to_string(), " ".to_string(), ItemPalette::from(TuiTheme::g(64))) - }; - Self { title: "Editing:", time, name, color } - } -} - impl HasPhraseList for SequencerTui { fn phrases_focused (&self) -> bool { true diff --git a/crates/tek/src/tui/phrase_list.rs b/crates/tek/src/tui/phrase_list.rs index 2ee0a514..b80ce7da 100644 --- a/crates/tek/src/tui/phrase_list.rs +++ b/crates/tek/src/tui/phrase_list.rs @@ -260,3 +260,73 @@ render!(|self: PhraseListView<'a>|{ add(&self.0.size) })) }); + +pub struct PhraseSelector { + pub(crate) title: &'static str, + pub(crate) name: String, + pub(crate) color: ItemPalette, + pub(crate) time: String, +} + +// TODO: Display phrases always in order of appearance +render!(|self: PhraseSelector|Tui::fixed_xy(24, 1, row!([ + Tui::fg(self.color.lightest.rgb, Tui::bold(true, &self.title)), + Tui::fg_bg(self.color.lighter.rgb, self.color.base.rgb, row!([ + format!("{:8}", &self.name[0..8.min(self.name.len())]), + Tui::bg(self.color.dark.rgb, &self.time), + ])), +]))); + +impl PhraseSelector { + // beats elapsed + pub fn play_phrase (state: &T) -> Self { + let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() { + let Phrase { ref name, color, .. } = *phrase.read().unwrap(); + (name.clone(), color) + } else { + ("".to_string(), ItemPalette::from(TuiTheme::g(64))) + }; + let time = if let Some(elapsed) = state.pulses_since_start_looped() { + format!("+{:>}", state.clock().timebase.format_beats_0(elapsed)) + } else { + String::from(" ") + }; + Self { title: " Now|", time, name, color, } + } + // beats until switchover + pub fn next_phrase (state: &T) -> Self { + let (time, name, color) = if let Some((t, Some(phrase))) = state.next_phrase() { + let Phrase { ref name, color, .. } = *phrase.read().unwrap(); + let time = { + let target = t.pulse.get(); + let current = state.clock().playhead.pulse.get(); + if target > current { + let remaining = target - current; + format!("-{:>}", state.clock().timebase.format_beats_0(remaining)) + } else { + String::new() + } + }; + (time, name.clone(), color) + } else if let Some((_, Some(phrase))) = state.play_phrase() { + let phrase = phrase.read().unwrap(); + if phrase.loop_on { + (" ".into(), phrase.name.clone(), phrase.color.clone()) + } else { + (" ".into(), " ".into(), TuiTheme::g(64).into()) + } + } else { + (" ".into(), " ".into(), TuiTheme::g(64).into()) + }; + Self { title: " Next|", time, name, color, } + } + pub fn edit_phrase (phrase: &Option>>) -> Self { + let (time, name, color) = if let Some(phrase) = phrase { + let phrase = phrase.read().unwrap(); + (format!("{}", phrase.length), phrase.name.clone(), phrase.color) + } else { + ("".to_string(), " ".to_string(), ItemPalette::from(TuiTheme::g(64))) + }; + Self { title: "Editing:", time, name, color } + } +} diff --git a/crates/tek/src/tui/piano_horizontal.rs b/crates/tek/src/tui/piano_horizontal.rs index 99fb53da..722f7236 100644 --- a/crates/tek/src/tui/piano_horizontal.rs +++ b/crates/tek/src/tui/piano_horizontal.rs @@ -65,11 +65,11 @@ pub struct PianoHorizontalTimeline<'a> { color: ItemPalette, range: &'a MidiRangeModel, } -render!(|self: PianoHorizontalTimeline<'a>|Tui::fg_bg( +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() -)); +))); pub struct PianoHorizontalKeys<'a> { color: ItemPalette, @@ -81,11 +81,10 @@ render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok({ let note_lo = self.range.note_lo(); let note_hi = self.range.note_hi(); let note_point = self.point.note_point(); - let [x, y0, ..] = to.area().xywh(); + 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 => "████▌", @@ -116,8 +115,10 @@ render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok({ to.blit(&format!("XYU"), x, y0, None); to.blit(&format!("x={x}"), x, y0+1, None); to.blit(&format!("y0={y0}"), x, y0+2, None); - to.blit(&format!("note_lo={note_lo}"), x, y0+3, None); - to.blit(&format!("note_hi={note_hi}"), x, y0+4, None); + to.blit(&format!("w={w}"), x, y0+3, None); + to.blit(&format!("h={h}"), x, y0+4, None); + to.blit(&format!("note_lo={note_lo}"), x, y0+5, None); + to.blit(&format!("note_hi={note_hi}"), x, y0+6, None); } }))); @@ -135,16 +136,17 @@ render!(|self: PianoHorizontalCursor<'a>|render(|to|Ok({ 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 (y, note) in (note_lo..=note_hi).rev().enumerate() { + for (input_y, output_y, note) in (note_lo..note_hi).rev().enumerate().map(|(y, n)|(y, y0 + y as u16, n)) { if note == note_point { for x in 0..w { + let output_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(&"█", x0 + x as u16, y0 + y as u16, style); + to.blit(&"█", output_x, output_y, style); let tail = note_len as u16 / time_zoom as u16; - for x_tail in (x0 + x + 1)..(x0 + x + tail) { - to.blit(&"▂", x_tail, y0 + y as u16, style); + for x_tail in (output_x + 1)..(output_x + tail) { + to.blit(&"▂", x_tail, output_y, style); } break } @@ -160,37 +162,59 @@ pub struct PianoHorizontalNotes<'a> { } 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 [x0, y0, w, h] = to.area().xywh(); - let debug = false; - if debug { - to.blit(&format!("KYP"), x0, y0, None); - to.blit(&format!("x0={x0}"), x0, y0+1, None); - to.blit(&format!("y0={y0}"), x0, y0+2, None); - to.blit(&format!("note_lo={note_hi}"), x0, y0+3, None); - to.blit(&format!("time_start={time_start}"), x0, y0+4, None); - return Ok(()); + if h as usize != self.range.note_axis() { + panic!("area height mismatch"); } - for (x, target_x) in (x0..x0+w).enumerate() { - for (y, target_y) in (y0..y0+h).enumerate() { - if y > note_hi { - break - } - let source_x = time_start + x; - let source_y = note_hi - y; - // TODO: enable loop rollover: - //let source_x = (time_start + x) % source.width.max(1); - //let source_y = (note_hi - y) % source.height.max(1); - let in_x = source_x < source.width; - let in_y = source_y < source.height; - if in_x && in_y { - let target_cell = to.buffer.get_mut(target_x, target_y); - if let Some(source_cell) = source.get(source_x, source_y) { - *target_cell = source_cell.clone(); - } + 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 (input_y, output_y) in (y0..=y0+h).enumerate() { + //if note_hi < input_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); + //} + ////if y >= note_hi { + ////break + ////} + ////let source_x = time_start + x; + ////let source_y = note_hi - y; + ////// TODO: enable loop rollover: + //////let source_x = (time_start + x) % source.width.max(1); + //////let source_y = (note_hi - y) % source.height.max(1); + ////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); + ////if let Some(source_cell) = source.get(source_x, source_y) { + //*output_cell = source_cell.clone(); + ////} + ////} + //} + } + let debug = true; + if debug { + let x0=20+x0; + to.blit(&format!("KYP "), x0, y0, None); + to.blit(&format!("x0={x0} "), x0, y0+1, None); + 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); + return Ok(()); } })));