mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
wip: fixed piano
This commit is contained in:
parent
86188b59db
commit
1b82a957aa
9 changed files with 100 additions and 105 deletions
|
|
@ -12,9 +12,7 @@ mod midi_view; pub use midi_view::*;
|
||||||
mod midi_editor; pub use midi_editor::*;
|
mod midi_editor; pub use midi_editor::*;
|
||||||
mod midi_select; pub use midi_select::*;
|
mod midi_select; pub use midi_select::*;
|
||||||
|
|
||||||
mod piano_h; pub use self::piano_h::*;
|
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::*;
|
|
||||||
|
|
||||||
pub(crate) use ::tek_time::*;
|
pub(crate) use ::tek_time::*;
|
||||||
pub(crate) use ::tek_jack::{*, jack::*};
|
pub(crate) use ::tek_jack::{*, jack::*};
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ has_size!(<TuiOut>|self: MidiEditor|&self.size);
|
||||||
render!(TuiOut: (self: MidiEditor) => {
|
render!(TuiOut: (self: MidiEditor) => {
|
||||||
self.autoscroll();
|
self.autoscroll();
|
||||||
//self.autozoom();
|
//self.autozoom();
|
||||||
Fill::xy(Bsp::b(&self.size, &self.mode))
|
self.size.of(&self.mode)
|
||||||
});
|
});
|
||||||
impl TimeRange for MidiEditor {
|
impl TimeRange for MidiEditor {
|
||||||
fn time_len (&self) -> &AtomicUsize { self.mode.time_len() }
|
fn time_len (&self) -> &AtomicUsize { self.mode.time_len() }
|
||||||
|
|
|
||||||
|
|
@ -40,16 +40,20 @@ impl PianoHorizontal {
|
||||||
pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iterator<Item=(usize, u16, usize)> {
|
pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iterator<Item=(usize, u16, usize)> {
|
||||||
(note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n))
|
(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
|
render!(TuiOut: (self: PianoHorizontal) => Tui::bg(TuiTheme::g(40), Bsp::s(
|
||||||
Fixed::y(1, Bsp::e(
|
Bsp::e(
|
||||||
Fixed::x(self.keys_width, ""),
|
Fixed::x(5, format!("{}x{}", self.size.w(), self.size.h())),
|
||||||
Fill::x(PianoHorizontalTimeline(self)),
|
self.timeline()
|
||||||
)),
|
),
|
||||||
Fill::xy(Bsp::e(
|
Bsp::e(
|
||||||
Fixed::x(self.keys_width, PianoHorizontalKeys(self)),
|
self.keys(),
|
||||||
Fill::xy(self.size.of(lay!(self.notes(), self.cursor()))),
|
self.size.of(Tui::bg(TuiTheme::g(32), Bsp::b(
|
||||||
)),
|
Fill::xy(self.notes()),
|
||||||
));
|
Fill::xy(self.cursor()),
|
||||||
|
)))
|
||||||
|
),
|
||||||
|
)));
|
||||||
|
|
||||||
impl PianoHorizontal {
|
impl PianoHorizontal {
|
||||||
/// Draw the piano roll foreground using full blocks on note on and half blocks on legato: █▄ █▄ █▄
|
/// 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) {
|
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_hi = self.note_hi();
|
||||||
let note_point = self.note_point();
|
let note_point = self.note_point();
|
||||||
let buffer = self.buffer.clone();
|
let buffer = self.buffer.clone();
|
||||||
RenderThunk::new(move|render: &mut TuiOut|{
|
RenderThunk::new(move|to: &mut TuiOut|{
|
||||||
let source = buffer.read().unwrap();
|
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 {
|
//if h as usize != note_axis {
|
||||||
//panic!("area height mismatch: {h} <> {note_axis}");
|
//panic!("area height mismatch: {h} <> {note_axis}");
|
||||||
//}
|
//}
|
||||||
|
|
@ -132,7 +136,7 @@ impl PianoHorizontal {
|
||||||
let is_in_y = source_y < source.height;
|
let is_in_y = source_y < source.height;
|
||||||
if is_in_x && is_in_y {
|
if is_in_x && is_in_y {
|
||||||
if let Some(source_cell) = source.get(source_x, source_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();
|
*cell = source_cell.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -150,8 +154,8 @@ impl PianoHorizontal {
|
||||||
let time_point = self.time_point();
|
let time_point = self.time_point();
|
||||||
let time_start = self.time_start().get();
|
let time_start = self.time_start().get();
|
||||||
let time_zoom = self.time_zoom().get();
|
let time_zoom = self.time_zoom().get();
|
||||||
RenderThunk::new(move|render: &mut TuiOut|{
|
RenderThunk::new(move|to: &mut TuiOut|{
|
||||||
let [x0, y0, w, _] = render.area().xywh();
|
let [x0, y0, w, _] = to.area().xywh();
|
||||||
for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
|
for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
|
||||||
if note == note_point {
|
if note == note_point {
|
||||||
for x in 0..w {
|
for x in 0..w {
|
||||||
|
|
@ -159,10 +163,10 @@ impl PianoHorizontal {
|
||||||
let time_1 = time_start + x as usize * time_zoom;
|
let time_1 = time_start + x as usize * time_zoom;
|
||||||
let time_2 = time_1 + time_zoom;
|
let time_2 = time_1 + time_zoom;
|
||||||
if time_1 <= time_point && time_point < time_2 {
|
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;
|
let tail = note_len as u16 / time_zoom as u16;
|
||||||
for x_tail in (screen_x + 1)..(screen_x + tail) {
|
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
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -172,6 +176,43 @@ impl PianoHorizontal {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
fn keys (&self) -> impl Content<TuiOut> {
|
||||||
|
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<TuiOut> + '_ {
|
||||||
|
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!(<TuiOut>|self:PianoHorizontal|&self.size);
|
has_size!(<TuiOut>|self:PianoHorizontal|&self.size);
|
||||||
|
|
@ -252,3 +293,21 @@ impl std::fmt::Debug for PianoHorizontal {
|
||||||
//self.now().set(now);
|
//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!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -54,10 +54,6 @@ pub trait Area<N: Coordinate>: From<[N;4]> + Debug + Copy {
|
||||||
let [x, y, w, h] = self.xywh();
|
let [x, y, w, h] = self.xywh();
|
||||||
[(x + w / 2.into()).minus(n / 2.into()), y + h / 2.into(), n, 1.into()]
|
[(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] {
|
#[inline] fn center_y (&self, n: N) -> [N;4] {
|
||||||
let [x, y, w, h] = self.xywh();
|
let [x, y, w, h] = self.xywh();
|
||||||
[x + w / 2.into(), (y + h / 2.into()).minus(n / 2.into()), 1.into(), n]
|
[x + w / 2.into(), (y + h / 2.into()).minus(n / 2.into()), 1.into(), n]
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,9 @@ impl EdnViewData<TuiOut> for &Example {
|
||||||
fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> {
|
fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> {
|
||||||
use EdnItem::*;
|
use EdnItem::*;
|
||||||
match item {
|
match item {
|
||||||
Nil => Box::new(()),
|
Nil => Box::new(()),
|
||||||
Exp(items) => Box::new(EdnView::from_items(*self, items.as_slice())),
|
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,
|
Sym(":title") => Tui::bg(Color::Rgb(60,10,10), Push::y(1,
|
||||||
Align::n(format!("Example {}/{}:", self.0 + 1, EDN.len())))).boxed(),
|
Align::n(format!("Example {}/{}:", self.0 + 1, EDN.len())))).boxed(),
|
||||||
Sym(":code") => Tui::bg(Color::Rgb(10,60,10), Push::y(2,
|
Sym(":code") => Tui::bg(Color::Rgb(10,60,10), Push::y(2,
|
||||||
|
|
|
||||||
1
tek/src/sequencer.edn
Normal file
1
tek/src/sequencer.edn
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
:editor
|
||||||
|
|
@ -22,12 +22,26 @@ pub struct Sequencer {
|
||||||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
||||||
pub perf: PerfModel,
|
pub perf: PerfModel,
|
||||||
}
|
}
|
||||||
render!(TuiOut: (self: Sequencer) => self.size.of(
|
render!(TuiOut: (self: Sequencer) => self.size.of(EdnView::from_source(self, Self::EDN)));
|
||||||
Bsp::s(self.toolbar_view(),
|
impl EdnViewData<TuiOut> for &Sequencer {
|
||||||
Bsp::n(self.selector_view(),
|
fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> {
|
||||||
Bsp::n(self.status_view(),
|
use EdnItem::*;
|
||||||
Bsp::w(self.pool_view(), Fill::xy(&self.editor)))))));
|
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 {
|
impl Sequencer {
|
||||||
|
const EDN: &'static str = include_str!("sequencer.edn");
|
||||||
fn toolbar_view (&self) -> impl Content<TuiOut> + use<'_> {
|
fn toolbar_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.player.clock))))
|
Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.player.clock))))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue