mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
draw keys correctly without buffer
This commit is contained in:
parent
eafc06edc6
commit
130a53220a
4 changed files with 132 additions and 126 deletions
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue