draw keys correctly without buffer

This commit is contained in:
🪞👃🪞 2024-11-30 19:44:51 +01:00
parent eafc06edc6
commit 130a53220a
4 changed files with 132 additions and 126 deletions

View file

@ -8,7 +8,7 @@ pub use std::collections::BTreeMap;
pub use once_cell::sync::Lazy;
pub use std::sync::atomic::{Ordering, AtomicBool, AtomicUsize, AtomicU16};
pub use std::rc::Rc;
pub use std::cell::RefCell;
pub use std::cell::{Cell, RefCell};
pub use std::marker::PhantomData;
pub(crate) use std::error::Error;
pub(crate) use std::io::{stdout};

View file

@ -61,22 +61,22 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
PhraseEditMode::Note => match from.event() {
key!(Char('e')) => SetEditMode(PhraseEditMode::Scroll),
key!(Up) => SetNoteScroll(
state.note_start.load(Ordering::Relaxed) + 1
state.note_lo.load(Ordering::Relaxed) + 1
),
key!(Down) => SetNoteScroll(
state.note_start.load(Ordering::Relaxed).saturating_sub(1)
state.note_lo.load(Ordering::Relaxed).saturating_sub(1)
),
key!(PageUp) => SetNoteScroll(
state.note_start.load(Ordering::Relaxed) + 3
state.note_lo.load(Ordering::Relaxed) + 3
),
key!(PageDown) => SetNoteScroll(
state.note_start.load(Ordering::Relaxed).saturating_sub(3)
state.note_lo.load(Ordering::Relaxed).saturating_sub(3)
),
key!(Left) => SetTimeScroll(
state.note_start.load(Ordering::Relaxed).saturating_sub(1)
state.note_lo.load(Ordering::Relaxed).saturating_sub(1)
),
key!(Right) => SetTimeScroll(
state.note_start.load(Ordering::Relaxed) + 1
state.note_lo.load(Ordering::Relaxed) + 1
),
key!(Char('a')) => AppendNote,
key!(Char('s')) => PutNote,
@ -127,7 +127,7 @@ impl Command<PhraseEditorModel> for PhraseCommand {
None
},
SetNoteScroll(note) => {
state.note_start.store(note, Ordering::Relaxed);
state.note_lo.store(note, Ordering::Relaxed);
None
},
SetNoteLength(time) => {
@ -136,10 +136,10 @@ impl Command<PhraseEditorModel> for PhraseCommand {
},
SetNoteCursor(note) => {
let note = 127.min(note);
let start = state.note_start.load(Ordering::Relaxed);
let start = state.note_lo.load(Ordering::Relaxed);
state.note_point.store(note, Ordering::Relaxed);
if note < start {
state.note_start.store((note / 2) * 2, Ordering::Relaxed);
state.note_lo.store((note / 2) * 2, Ordering::Relaxed);
}
None
},

View file

@ -6,8 +6,6 @@ pub struct PhraseEditorModel {
pub(crate) phrase: Option<Arc<RwLock<Phrase>>>,
/// Length of note that will be inserted, in pulses
pub(crate) note_len: usize,
/// The full piano keys are rendered to this buffer
pub(crate) keys: Buffer,
/// The full piano roll is rendered to this buffer
pub(crate) buffer: BigBuffer,
/// Notes currently held at input
@ -19,9 +17,8 @@ pub struct PhraseEditorModel {
/// Width and height of notes area at last render
pub(crate) size: Measure<Tui>,
pub(crate) note_start: AtomicUsize,
pub(crate) note_lo: AtomicUsize,
pub(crate) note_point: AtomicUsize,
pub(crate) note_clamp: AtomicUsize,
pub(crate) time_start: AtomicUsize,
pub(crate) time_point: AtomicUsize,
@ -34,10 +31,9 @@ pub struct PhraseEditorModel {
impl std::fmt::Debug for PhraseEditorModel {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("PhraseEditorModel")
.field("note_axis", &format!("{} {} {}",
self.note_start.load(Ordering::Relaxed),
.field("note_axis", &format!("{} {}",
self.note_lo.load(Ordering::Relaxed),
self.note_point.load(Ordering::Relaxed),
self.note_clamp.load(Ordering::Relaxed),
))
.field("time_axis", &format!("{} {} {} {}",
self.time_start.load(Ordering::Relaxed),
@ -54,16 +50,14 @@ impl Default for PhraseEditorModel {
Self {
phrase: None,
note_len: 24,
keys: keys_vert(),
buffer: Default::default(),
notes_in: RwLock::new([false;128]).into(),
notes_out: RwLock::new([false;128]).into(),
now: Pulse::default().into(),
size: Measure::new(),
edit_mode: PhraseEditMode::Scroll,
note_start: 12.into(),
note_point: 36.into(),
note_clamp: 127.into(),
note_lo: 0.into(),
note_point: 24.into(),
time_start: 0.into(),
time_point: 0.into(),
time_clamp: 0.into(),

View file

@ -4,17 +4,17 @@ pub struct PhraseView<'a> {
pub(crate) focused: bool,
pub(crate) entered: bool,
pub(crate) phrase: &'a Option<Arc<RwLock<Phrase>>>,
pub(crate) size: &'a Measure<Tui>,
pub(crate) keys: &'a Buffer,
pub(crate) buffer: &'a BigBuffer,
pub(crate) note_len: usize,
pub(crate) now: &'a Arc<Pulse>,
pub(crate) note_start: &'a AtomicUsize,
pub(crate) note_point: usize,
pub(crate) note_clamp: usize,
size: &'a Measure<Tui>,
width: usize,
height: usize,
note_range: (&'a str, &'a str),
note_point: usize,
note_range: (usize, usize),
note_names: (&'a str, &'a str),
pub(crate) time_start: usize,
pub(crate) time_point: usize,
@ -24,30 +24,45 @@ pub struct PhraseView<'a> {
impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> {
fn from (state: &'a T) -> Self {
let [w, h] = state.editor().size.wh();
let note_start = state.editor().note_start.load(Ordering::Relaxed);
let note_clamp = state.editor().note_clamp.load(Ordering::Relaxed);
let note_end = (note_start + h.saturating_sub(2) / 2).min(127);
let note_range = (to_note_name(note_start), to_note_name(note_end));
let editor = state.editor();
let [width, height] = editor.size.wh();
let note_point = editor.note_point.load(Ordering::Relaxed);
let mut note_lo = editor.note_lo.load(Ordering::Relaxed);
if note_point < note_lo {
note_lo = note_point;
editor.note_lo.store(note_lo, Ordering::Relaxed);
}
let mut note_hi = 127.min(note_lo + height * 2 + 1);
if note_point > note_hi {
note_lo += note_point - note_hi;
note_hi = note_point;
editor.note_lo.store(note_lo, Ordering::Relaxed);
}
Self {
focused: state.editor_focused(),
entered: state.editor_entered(),
note_len: state.editor().note_len,
phrase: &state.editor().phrase,
size: &state.editor().size,
keys: &state.editor().keys,
buffer: &state.editor().buffer,
now: &state.editor().now,
note_len: editor.note_len,
phrase: &editor.phrase,
buffer: &editor.buffer,
now: &editor.now,
note_start: &state.editor().note_start,
note_point: state.editor().note_point.load(Ordering::Relaxed),
note_clamp,
note_range,
size: &editor.size,
width,
height,
time_start: state.editor().time_start.load(Ordering::Relaxed),
time_point: state.editor().time_point.load(Ordering::Relaxed),
time_clamp: state.editor().time_clamp.load(Ordering::Relaxed),
time_scale: state.editor().time_scale.load(Ordering::Relaxed),
note_point,
note_range: (note_lo, note_hi),
note_names: (to_note_name(note_lo), to_note_name(note_hi)),
time_start: editor.time_start.load(Ordering::Relaxed),
time_point: editor.time_point.load(Ordering::Relaxed),
time_clamp: editor.time_clamp.load(Ordering::Relaxed),
time_scale: editor.time_scale.load(Ordering::Relaxed),
}
}
}
@ -56,33 +71,73 @@ impl<'a> Content for PhraseView<'a> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let Self {
focused, entered, phrase, size, keys, buffer, note_len, now,
note_point, note_range,
time_start, time_point, time_clamp, time_scale, ..
focused,
entered,
phrase,
size,
buffer,
note_len,
note_range: (note_lo, note_hi),
note_names: (note_lo_name, note_hi_name),
note_point,
time_start,
time_point,
time_clamp,
time_scale,
now,
..
} = self;
let note_start = self.note_start.load(Ordering::Relaxed);
let keys = move|to: &mut TuiOutput|Ok(if to.area().h() >= 2 {
to.buffer_update(to.area().set_w(5), &|cell, x, y|{
let y = y + (note_start / 2) as u16;
if x < keys.area.width && y < keys.area.height {
*cell = keys.get(x, y).clone()
for y in to.area.y()..to.area.y2() {
let n = y - to.area.y();
let m = note_hi.saturating_sub(n as usize*2);
//let c = format!("{m:>03} {n}");
//to.blit(&c, to.area.x(), y, None);
let x = to.area.x();
let s = Some(Style::default().fg(Color::Rgb(255,255,255)).bg(Color::Rgb(0,0,0)));
match m % 2 {
1 => match (m / 2) % 6 {
5 => to.blit(&"", x, y, s),
4 => to.blit(&"", x, y, s),
3 => to.blit(&"", x, y, s),
2 => to.blit(&"", x, y, s),
1 => to.blit(&"", x, y, s),
0 => to.blit(&"", x, y, s),
_ => unreachable!(),
},
0 => match (m / 2) % 6 {
5 => to.blit(&"", x, y, s),
4 => to.blit(&"", x, y, s),
3 => to.blit(&"", x, y, s),
2 => to.blit(&"", x, y, s),
1 => to.blit(&"", x, y, s),
0 => to.blit(&"", x, y, s),
_ => unreachable!(),
},
_ => unreachable!()
}
});
}
//to.buffer_update(to.area().set_w(5), &|cell, x, y|{
////let y = y + (note_hi / 2) as u16;
//if x < keys.area.width && y < keys.area.height {
//[>cell = keys.get(x, y).clone()
////cell.set_symbol(&format!("{y}"));
////}
//}
//});
});
let notes_bg_null = Color::Rgb(28, 35, 25);
let notes = move|to: &mut TuiOutput|{
let area = to.area();
let h = area.h() as usize;
size.set_wh(area.w(), h);
if note_point.saturating_sub(note_start) > (h * 2).saturating_sub(1) {
self.note_start.store(note_start + 2, Ordering::Relaxed);
}
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_start / 2;
let src_y = y as usize + note_lo / 2;
if src_x < buffer.width && src_y < buffer.height - 1 {
buffer.get(src_x, buffer.height - src_y - 2).map(|src|{
cell.set_symbol(src.symbol());
@ -97,10 +152,15 @@ impl<'a> Content for PhraseView<'a> {
let area = to.area();
let x1 = area.x() + (time_point / time_scale) as u16;
let x2 = x1 + (note_len / time_scale) as u16;
let y = area.y() + note_point.saturating_sub(note_start) as u16 / 2;
let c = if note_point % 2 == 0 { "" } else { "" };
let y = area.y() + (note_hi - note_point) as u16 / 2;
let c = if note_lo % 2 == 0 {
if note_point % 2 == 0 { "" } else { "" }
} else {
if note_point % 2 == 0 { "" } else { "" }
};
let style = Some(Style::default().fg(Color::Rgb(0,255,0)));
for x in x1..x2 {
to.blit(&c, x, y, Some(Style::default().fg(Color::Rgb(0,255,0))));
to.blit(&c, x, y, style);
}
});
let playhead_inactive = Style::default().fg(Color::Rgb(255,255,255)).bg(Color::Rgb(40,50,30));
@ -129,7 +189,7 @@ impl<'a> Content for PhraseView<'a> {
CustomWidget::new(|to|Ok(Some(to)), cursor)
).fill_x();
let piano_roll = row!(
CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_w(5))), keys).fill_y(),
CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_w(2))), keys).fill_y(),
note_area
).fill_x().bg(Color::Rgb(40, 50, 30)).border(border);
let content = lay!(
@ -140,30 +200,22 @@ impl<'a> Content for PhraseView<'a> {
if let Some(phrase) = phrase {
name = phrase.read().unwrap().name.clone();
}
let mut upper_left = format!("{} [{}] {name}", note_range.1, if *entered {""} else {" "},);
let mut lower_left = format!("{}", note_range.0);
let mut upper_left = format!("{note_hi} {note_hi_name} {name}");
let mut lower_left = format!("{note_lo} {note_lo_name}");
let mut lower_right = format!("{}", size.format());
lower_right = format!("┤Zoom: {}├─{lower_right}", pulses_to_name(*time_scale));
//lower_right = format!("Zoom: {} (+{}:{}*{}|{})",
//pulses_to_name(time_scale),
//time_start, time_point.unwrap_or(0),
//time_scale, time_clamp.unwrap_or(0),
//);
if *focused && *entered {
lower_right = format!("┤Note: {} {}├─{lower_right}",
lower_right = format!("┤Note: {} ({}) {}├─{lower_right}",
note_point,
pulses_to_name(*note_len));
//lower_right = format!("Note: {} (+{}:{}|{}) {upper_right}",
//pulses_to_name(*note_len),
//note_start,
//note_point.unwrap_or(0),
//note_clamp.unwrap_or(0),
//);
to_note_name(*note_point),
pulses_to_name(*note_len)
);
}
let upper_right = if let Some(phrase) = phrase {
format!("┤Length: {}", phrase.read().unwrap().length)
} else {
String::new()
let mut upper_right = format!("[{}]", if *entered {""} else {" "});
if let Some(phrase) = phrase {
upper_right = format!("┤Length: {}├─┤Zoom: {}{upper_right}",
phrase.read().unwrap().length,
pulses_to_name(*time_scale),
)
};
lay!(
content,
@ -175,49 +227,9 @@ impl<'a> Content for PhraseView<'a> {
}
}
/// Colors of piano keys
const KEY_COLORS: [(Color, Color);6] = [
(Color::Rgb(255, 255, 255), Color::Rgb(255, 255, 255)),
(Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)),
(Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)),
(Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)),
(Color::Rgb(255, 255, 255), Color::Rgb(0, 0, 0)),
(Color::Rgb(255, 255, 255), Color::Rgb(0, 0, 0)),
];
pub(crate) fn keys_vert () -> Buffer {
let area = [0, 0, 5, 64];
let mut buffer = Buffer::empty(Rect {
x: area.x(), y: area.y(), width: area.w(), height: area.h()
});
buffer_update(&mut buffer, area, &|cell, x, y| {
let y = 63 - y;
match x {
0 => if y % 6 == 0 {
cell.set_char('C');
},
1 => if y % 6 == 0 {
cell.set_symbol(NTH_OCTAVE[(y / 6) as usize]);
},
2 => {
cell.set_char('▀');
let (fg, bg) = KEY_COLORS[((6 - y % 6) % 6) as usize];
cell.set_fg(fg);
cell.set_bg(bg);
},
3 => {
cell.set_char('▀');
cell.set_fg(Color::White);
cell.set_bg(Color::White);
},
_ => {}
}
});
buffer
}
const NTH_OCTAVE: [&'static str; 11] = [
"-2", "-1", "0", "1", "2", "3", "4", "5", "6", "7", "8",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "X",
//"-2", "-1", "0", "1", "2", "3", "4", "5", "6", "7", "8",
];
impl PhraseEditorModel {