diff --git a/midi/src/lib.rs b/midi/src/lib.rs index b2ef11bd..f856bc58 100644 --- a/midi/src/lib.rs +++ b/midi/src/lib.rs @@ -12,9 +12,7 @@ mod midi_view; pub use midi_view::*; mod midi_editor; pub use midi_editor::*; mod midi_select; pub use midi_select::*; -mod piano_h; pub use self::piano_h::*; -mod piano_h_keys; pub use self::piano_h_keys::*; -mod piano_h_time; pub use self::piano_h_time::*; +mod piano_h; pub use self::piano_h::*; pub(crate) use ::tek_time::*; pub(crate) use ::tek_jack::{*, jack::*}; diff --git a/midi/src/midi_editor.rs b/midi/src/midi_editor.rs index d8f997bd..79fb6c63 100644 --- a/midi/src/midi_editor.rs +++ b/midi/src/midi_editor.rs @@ -39,7 +39,7 @@ has_size!(|self: MidiEditor|&self.size); render!(TuiOut: (self: MidiEditor) => { self.autoscroll(); //self.autozoom(); - Fill::xy(Bsp::b(&self.size, &self.mode)) + self.size.of(&self.mode) }); impl TimeRange for MidiEditor { fn time_len (&self) -> &AtomicUsize { self.mode.time_len() } diff --git a/midi/src/piano_h.rs b/midi/src/piano_h.rs index aa0e3b00..c8a13048 100644 --- a/midi/src/piano_h.rs +++ b/midi/src/piano_h.rs @@ -40,16 +40,20 @@ impl PianoHorizontal { pub(crate) 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)) } -render!(TuiOut: (self: PianoHorizontal) => Bsp::s( // the freeze is in the Measure - Fixed::y(1, Bsp::e( - Fixed::x(self.keys_width, ""), - Fill::x(PianoHorizontalTimeline(self)), - )), - Fill::xy(Bsp::e( - Fixed::x(self.keys_width, PianoHorizontalKeys(self)), - Fill::xy(self.size.of(lay!(self.notes(), self.cursor()))), - )), -)); +render!(TuiOut: (self: PianoHorizontal) => Tui::bg(TuiTheme::g(40), Bsp::s( + Bsp::e( + Fixed::x(5, format!("{}x{}", self.size.w(), self.size.h())), + self.timeline() + ), + Bsp::e( + self.keys(), + self.size.of(Tui::bg(TuiTheme::g(32), Bsp::b( + Fill::xy(self.notes()), + Fill::xy(self.cursor()), + ))) + ), +))); + impl PianoHorizontal { /// Draw the piano roll foreground using full blocks on note on and half blocks on legato: █▄ █▄ █▄ fn draw_bg (buf: &mut BigBuffer, clip: &MidiClip, zoom: usize, note_len: usize) { @@ -115,9 +119,9 @@ impl PianoHorizontal { let note_hi = self.note_hi(); let note_point = self.note_point(); let buffer = self.buffer.clone(); - RenderThunk::new(move|render: &mut TuiOut|{ + RenderThunk::new(move|to: &mut TuiOut|{ let source = buffer.read().unwrap(); - let [x0, y0, w, h] = render.area().xywh(); + let [x0, y0, w, h] = to.area().xywh(); //if h as usize != note_axis { //panic!("area height mismatch: {h} <> {note_axis}"); //} @@ -132,7 +136,7 @@ impl PianoHorizontal { let is_in_y = source_y < source.height; if is_in_x && is_in_y { if let Some(source_cell) = source.get(source_x, source_y) { - if let Some(cell) = render.buffer.cell_mut(ratatui::prelude::Position::from((screen_x, screen_y))) { + if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((screen_x, screen_y))) { *cell = source_cell.clone(); } } @@ -150,8 +154,8 @@ impl PianoHorizontal { let time_point = self.time_point(); let time_start = self.time_start().get(); let time_zoom = self.time_zoom().get(); - RenderThunk::new(move|render: &mut TuiOut|{ - let [x0, y0, w, _] = render.area().xywh(); + RenderThunk::new(move|to: &mut TuiOut|{ + let [x0, y0, w, _] = to.area().xywh(); for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { if note == note_point { for x in 0..w { @@ -159,10 +163,10 @@ impl PianoHorizontal { 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 { - render.blit(&"█", screen_x, screen_y, style); + to.blit(&"█", screen_x, screen_y, style); let tail = note_len as u16 / time_zoom as u16; for x_tail in (screen_x + 1)..(screen_x + tail) { - render.blit(&"▂", x_tail, screen_y, style); + to.blit(&"▂", x_tail, screen_y, style); } break } @@ -172,6 +176,43 @@ impl PianoHorizontal { } }) } + fn keys (&self) -> impl Content { + let state = self; + let color = state.color; + let note_lo = state.note_lo().get(); + let note_hi = state.note_hi(); + let note_point = state.note_point(); + 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(color.base.rgb).bold()); + Fill::y(Fixed::x(self.keys_width, RenderThunk::new(move|to: &mut TuiOut|{ + let [x, y0, w, h] = to.area().xywh(); + 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}", Note::pitch_to_name(note)), x, screen_y, on_style) + } else { + to.blit(&Note::pitch_to_name(note), x, screen_y, off_style) + }; + } + }))) + } + fn timeline (&self) -> impl Content + '_ { + Fill::x(Fixed::y(1, RenderThunk::new(move|to: &mut TuiOut|{ + let [x, y, w, h] = to.area(); + let style = Some(Style::default().dim()); + let length = self.clip.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); + for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) { + let t = area_x as usize * self.time_zoom().get(); + if t < length { + to.blit(&"|", screen_x, y, style); + } + } + }))) + } } has_size!(|self:PianoHorizontal|&self.size); @@ -252,3 +293,21 @@ impl std::fmt::Debug for PianoHorizontal { //self.now().set(now); //} //} + +fn to_key (note: usize) -> &'static str { + match note % 12 { + 11 => "████▌", + 10 => " ", + 9 => "████▌", + 8 => " ", + 7 => "████▌", + 6 => " ", + 5 => "████▌", + 4 => "████▌", + 3 => " ", + 2 => "████▌", + 1 => " ", + 0 => "████▌", + _ => unreachable!(), + } +} diff --git a/midi/src/piano_h_keys.rs b/midi/src/piano_h_keys.rs deleted file mode 100644 index 9c79aeab..00000000 --- a/midi/src/piano_h_keys.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::*; -use super::*; - -pub struct PianoHorizontalKeys<'a>(pub(crate) &'a PianoHorizontal); - -render!(TuiOut: |self: PianoHorizontalKeys<'a>, to|{ - let state = self.0; - let color = state.color; - let note_lo = state.note_lo().get(); - let note_hi = state.note_hi(); - let note_point = state.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(color.base.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}", Note::pitch_to_name(note)), x, screen_y, on_style) - } else { - to.blit(&Note::pitch_to_name(note), x, screen_y, off_style) - }; - } -}); - -has_color!(|self: PianoHorizontalKeys<'a>|self.0.color.base); - -impl<'a> NoteRange for PianoHorizontalKeys<'a> { - fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo() } - fn note_axis (&self) -> &AtomicUsize { &self.0.note_axis() } -} - -impl<'a> NotePoint for PianoHorizontalKeys<'a> { - fn note_len (&self) -> usize { self.0.note_len() } - fn set_note_len (&self, x: usize) { self.0.set_note_len(x) } - fn note_point (&self) -> usize { self.0.note_point() } - fn set_note_point (&self, x: usize) { self.0.set_note_point(x) } -} - -fn to_key (note: usize) -> &'static str { - match note % 12 { - 11 => "████▌", - 10 => " ", - 9 => "████▌", - 8 => " ", - 7 => "████▌", - 6 => " ", - 5 => "████▌", - 4 => "████▌", - 3 => " ", - 2 => "████▌", - 1 => " ", - 0 => "████▌", - _ => unreachable!(), - } -} diff --git a/midi/src/piano_h_time.rs b/midi/src/piano_h_time.rs deleted file mode 100644 index fcfd1f5b..00000000 --- a/midi/src/piano_h_time.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::*; -use super::*; - -pub struct PianoHorizontalTimeline<'a>(pub(crate) &'a PianoHorizontal); -render!(TuiOut: |self: PianoHorizontalTimeline<'a>, render|{ - let [x, y, w, h] = render.area(); - let style = Some(Style::default().dim()); - let length = self.0.clip.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); - for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) { - let t = area_x as usize * self.0.time_zoom().get(); - if t < length { - render.blit(&"|", screen_x, y, style); - } - } -}); diff --git a/output/src/area.rs b/output/src/area.rs index 0b2c8276..8607bc7a 100644 --- a/output/src/area.rs +++ b/output/src/area.rs @@ -54,10 +54,6 @@ pub trait Area: From<[N;4]> + Debug + Copy { let [x, y, w, h] = self.xywh(); [(x + w / 2.into()).minus(n / 2.into()), y + h / 2.into(), n, 1.into()] } - #[inline] fn center_xw (&self, n: N, m: N) -> [N;4] { - let [x, y, w, h] = self.xywh(); - [(x + w / 2.into()).minus(n / 2.into()), y + h / 2.into(), n, 1.into()] - } #[inline] fn center_y (&self, n: N) -> [N;4] { let [x, y, w, h] = self.xywh(); [x + w / 2.into(), (y + h / 2.into()).minus(n / 2.into()), 1.into(), n] diff --git a/tek/examples/edn.rs b/tek/examples/edn.rs index 2c708dce..fa6b75e7 100644 --- a/tek/examples/edn.rs +++ b/tek/examples/edn.rs @@ -30,8 +30,9 @@ impl EdnViewData for &Example { fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> { use EdnItem::*; match item { - Nil => Box::new(()), + Nil => Box::new(()), Exp(items) => Box::new(EdnView::from_items(*self, items.as_slice())), + //Sym(name) => self.get_sym(name), TODO Sym(":title") => Tui::bg(Color::Rgb(60,10,10), Push::y(1, Align::n(format!("Example {}/{}:", self.0 + 1, EDN.len())))).boxed(), Sym(":code") => Tui::bg(Color::Rgb(10,60,10), Push::y(2, diff --git a/tek/src/sequencer.edn b/tek/src/sequencer.edn new file mode 100644 index 00000000..4827ef70 --- /dev/null +++ b/tek/src/sequencer.edn @@ -0,0 +1 @@ +:editor diff --git a/tek/src/sequencer.rs b/tek/src/sequencer.rs index c4e8b4a4..399a9055 100644 --- a/tek/src/sequencer.rs +++ b/tek/src/sequencer.rs @@ -22,12 +22,26 @@ pub struct Sequencer { pub midi_buf: Vec>>, pub perf: PerfModel, } -render!(TuiOut: (self: Sequencer) => self.size.of( - Bsp::s(self.toolbar_view(), - Bsp::n(self.selector_view(), - Bsp::n(self.status_view(), - Bsp::w(self.pool_view(), Fill::xy(&self.editor))))))); +render!(TuiOut: (self: Sequencer) => self.size.of(EdnView::from_source(self, Self::EDN))); +impl EdnViewData for &Sequencer { + fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> { + use EdnItem::*; + match item { + Nil => Box::new(()), + Exp(items) => Box::new(EdnView::from_items(*self, items.as_slice())), + Sym(":editor") => (&self.editor).boxed(), + _ => panic!("no content for {item:?}") + } + } + +} +//render!(TuiOut: (self: Sequencer) => self.size.of( + //Bsp::s(self.toolbar_view(), + //Bsp::n(self.selector_view(), + //Bsp::n(self.status_view(), + //Bsp::w(self.pool_view(), Fill::xy(&self.editor))))))); impl Sequencer { + const EDN: &'static str = include_str!("sequencer.edn"); fn toolbar_view (&self) -> impl Content + use<'_> { Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.player.clock)))) }