mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
full block piano roll, pt.2
This commit is contained in:
parent
64ac577f4a
commit
35c0470d15
3 changed files with 90 additions and 59 deletions
|
|
@ -122,3 +122,9 @@ impl Command<PhraseEditorModel> for PhraseCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum PhraseEditMode {
|
||||||
|
Note,
|
||||||
|
Scroll,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,13 +57,16 @@ impl Default for PhraseEditorModel {
|
||||||
now: Pulse::default().into(),
|
now: Pulse::default().into(),
|
||||||
size: Measure::new(),
|
size: Measure::new(),
|
||||||
edit_mode: PhraseEditMode::Scroll,
|
edit_mode: PhraseEditMode::Scroll,
|
||||||
view_mode: PhraseViewMode::Horizontal,
|
|
||||||
note_lo: 0.into(),
|
note_lo: 0.into(),
|
||||||
note_point: 24.into(),
|
note_point: 24.into(),
|
||||||
time_start: 0.into(),
|
time_start: 0.into(),
|
||||||
time_point: 0.into(),
|
time_point: 0.into(),
|
||||||
time_clamp: 0.into(),
|
time_clamp: 0.into(),
|
||||||
time_scale: 24.into(),
|
time_scale: 24.into(),
|
||||||
|
view_mode: PhraseViewMode::PianoHorizontal {
|
||||||
|
time_zoom: 24,
|
||||||
|
note_zoom: PhraseViewNoteZoom::N(1)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +84,7 @@ impl PhraseEditorModel {
|
||||||
let end = (start + self.note_len) % phrase.length;
|
let end = (start + self.note_len) % phrase.length;
|
||||||
phrase.notes[time].push(MidiMessage::NoteOn { key, vel });
|
phrase.notes[time].push(MidiMessage::NoteOn { key, vel });
|
||||||
phrase.notes[end].push(MidiMessage::NoteOff { key, vel });
|
phrase.notes[end].push(MidiMessage::NoteOff { key, vel });
|
||||||
self.buffer = Self::redraw(&phrase);
|
self.buffer = self.view_mode.draw(&phrase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Move time cursor forward by current note length
|
/// Move time cursor forward by current note length
|
||||||
|
|
@ -96,7 +99,7 @@ impl PhraseEditorModel {
|
||||||
if let Some(phrase) = phrase {
|
if let Some(phrase) = phrase {
|
||||||
self.phrase = Some(phrase.clone());
|
self.phrase = Some(phrase.clone());
|
||||||
self.time_clamp.store(phrase.read().unwrap().length, Ordering::Relaxed);
|
self.time_clamp.store(phrase.read().unwrap().length, Ordering::Relaxed);
|
||||||
self.buffer = Self::redraw(&*phrase.read().unwrap());
|
self.buffer = self.view_mode.draw(&*phrase.read().unwrap());
|
||||||
} else {
|
} else {
|
||||||
self.phrase = None;
|
self.phrase = None;
|
||||||
self.time_clamp.store(0, Ordering::Relaxed);
|
self.time_clamp.store(0, Ordering::Relaxed);
|
||||||
|
|
@ -105,19 +108,6 @@ impl PhraseEditorModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub enum PhraseEditMode {
|
|
||||||
Note,
|
|
||||||
Scroll,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub enum PhraseViewMode {
|
|
||||||
Horizontal,
|
|
||||||
HorizontalHalf,
|
|
||||||
Vertical,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasEditor {
|
pub trait HasEditor {
|
||||||
fn editor (&self) -> &PhraseEditorModel;
|
fn editor (&self) -> &PhraseEditorModel;
|
||||||
fn editor_focused (&self) -> bool;
|
fn editor_focused (&self) -> bool;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub struct PhraseView<'a> {
|
pub struct PhraseView<'a> {
|
||||||
focused: bool,
|
focused: bool,
|
||||||
entered: bool,
|
entered: bool,
|
||||||
phrase: &'a Option<Arc<RwLock<Phrase>>>,
|
phrase: &'a Option<Arc<RwLock<Phrase>>>,
|
||||||
buffer: &'a BigBuffer,
|
buffer: &'a BigBuffer,
|
||||||
note_len: usize,
|
note_len: usize,
|
||||||
now: &'a Arc<Pulse>,
|
now: &'a Arc<Pulse>,
|
||||||
|
size: &'a Measure<Tui>,
|
||||||
size: &'a Measure<Tui>,
|
view_mode: &'a PhraseViewMode,
|
||||||
|
|
||||||
note_point: usize,
|
note_point: usize,
|
||||||
note_range: (usize, usize),
|
note_range: (usize, usize),
|
||||||
|
|
@ -42,14 +42,14 @@ impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
focused: state.editor_focused(),
|
focused: state.editor_focused(),
|
||||||
entered: state.editor_entered(),
|
entered: state.editor_entered(),
|
||||||
note_len: editor.note_len,
|
note_len: editor.note_len,
|
||||||
phrase: &editor.phrase,
|
phrase: &editor.phrase,
|
||||||
buffer: &editor.buffer,
|
buffer: &editor.buffer,
|
||||||
now: &editor.now,
|
now: &editor.now,
|
||||||
|
size: &editor.size,
|
||||||
size: &editor.size,
|
view_mode: &editor.view_mode,
|
||||||
|
|
||||||
note_point,
|
note_point,
|
||||||
note_range: (note_lo, note_hi),
|
note_range: (note_lo, note_hi),
|
||||||
|
|
@ -72,6 +72,7 @@ impl<'a> Content for PhraseView<'a> {
|
||||||
phrase,
|
phrase,
|
||||||
size,
|
size,
|
||||||
buffer,
|
buffer,
|
||||||
|
view_mode,
|
||||||
note_len,
|
note_len,
|
||||||
note_range: (note_lo, note_hi),
|
note_range: (note_lo, note_hi),
|
||||||
note_names: (note_lo_name, note_hi_name),
|
note_names: (note_lo_name, note_hi_name),
|
||||||
|
|
@ -122,18 +123,19 @@ impl<'a> Content for PhraseView<'a> {
|
||||||
size.set_wh(area.w(), h - 1);
|
size.set_wh(area.w(), h - 1);
|
||||||
Ok(if to.area().h() >= 2 {
|
Ok(if to.area().h() >= 2 {
|
||||||
let area = to.area();
|
let area = to.area();
|
||||||
to.buffer_update(area, &move |cell, x, y|{
|
view_mode.blit(buffer, &mut to.buffer, area, *time_start, *note_hi);
|
||||||
cell.set_bg(notes_bg_null);
|
//to.buffer_update(area, &move |cell, x, y|{
|
||||||
let src_x = (x as usize + time_start) * time_scale;
|
//cell.set_bg(notes_bg_null);
|
||||||
let src_y = y as usize + note_lo / 2;
|
//let src_x = (x as usize + time_start) * time_scale;
|
||||||
if src_x < buffer.width && src_y < buffer.height - 1 {
|
//let src_y = y as usize + note_lo / 2;
|
||||||
buffer.get(src_x, (note_hi - y as usize) / 2).map(|src|{
|
//if src_x < buffer.width && src_y < buffer.height - 1 {
|
||||||
cell.set_symbol(src.symbol());
|
//buffer.get(src_x, (note_hi - y as usize) / 2).map(|src|{
|
||||||
cell.set_fg(src.fg);
|
//cell.set_symbol(src.symbol());
|
||||||
cell.set_bg(src.bg);
|
//cell.set_fg(src.fg);
|
||||||
});
|
//cell.set_bg(src.bg);
|
||||||
}
|
//});
|
||||||
});
|
//}
|
||||||
|
//});
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let cursor = move|to: &mut TuiOutput|Ok(if *focused && *entered {
|
let cursor = move|to: &mut TuiOutput|Ok(if *focused && *entered {
|
||||||
|
|
@ -230,27 +232,53 @@ pub enum PhraseViewMode {
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum PhraseViewNoteZoom {
|
pub enum PhraseViewNoteZoom {
|
||||||
Half,
|
|
||||||
N(usize),
|
N(usize),
|
||||||
|
Half,
|
||||||
|
Octant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhraseViewMode {
|
impl PhraseViewMode {
|
||||||
/// Return a new [BigBuffer] containing a render of the phrase.
|
/// Return a new [BigBuffer] containing a render of the phrase.
|
||||||
fn draw (&self, phrase: &Phrase) -> BigBuffer {
|
pub fn draw (&self, phrase: &Phrase) -> BigBuffer {
|
||||||
let mut buffer = BigBuffer::new(self.buffer_width(phrase), self.buffer_height(phrase));
|
let mut buffer = BigBuffer::new(self.buffer_width(phrase), self.buffer_height(phrase));
|
||||||
match self {
|
match self {
|
||||||
Self::PianoHorizontal { time_zoom, note_zoom } => match note_zoom {
|
Self::PianoHorizontal { time_zoom, note_zoom } => match note_zoom {
|
||||||
PhraseViewNoteZoom::Half => Self::draw_piano_horizontal_half(
|
|
||||||
&mut buffer, phrase, *time_zoom
|
|
||||||
),
|
|
||||||
PhraseViewNoteZoom::N(_) => Self::draw_piano_horizontal(
|
PhraseViewNoteZoom::N(_) => Self::draw_piano_horizontal(
|
||||||
&mut buffer, phrase, *time_zoom, 1
|
&mut buffer, phrase, *time_zoom, 1
|
||||||
),
|
),
|
||||||
|
_ => unimplemented!(),
|
||||||
},
|
},
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
buffer
|
buffer
|
||||||
}
|
}
|
||||||
|
/// Draw a subsection of the [BigBuffer] onto a regular ratatui [Buffer].
|
||||||
|
fn blit (
|
||||||
|
&self,
|
||||||
|
source: &BigBuffer,
|
||||||
|
target: &mut Buffer,
|
||||||
|
area: impl Area<u16>,
|
||||||
|
time_start: usize,
|
||||||
|
note_hi: usize,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
Self::PianoHorizontal { .. } => {
|
||||||
|
let [x0, y0, w, h] = area.xywh();
|
||||||
|
for (x, target_x) in (x0..x0+w).enumerate() {
|
||||||
|
for (y, target_y) in (y0..y0+h).enumerate() {
|
||||||
|
let source_x = time_start + x;
|
||||||
|
let source_y = note_hi - y;
|
||||||
|
let target_cell = target.get_mut(target_x, target_y);
|
||||||
|
target_cell.set_char('x');
|
||||||
|
if let Some(source_cell) = source.get(source_x, source_y) {
|
||||||
|
*target_cell = source_cell.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Determine the required width to render the phrase.
|
/// Determine the required width to render the phrase.
|
||||||
fn buffer_width (&self, phrase: &Phrase) -> usize {
|
fn buffer_width (&self, phrase: &Phrase) -> usize {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -260,6 +288,7 @@ impl PhraseViewMode {
|
||||||
Self::PianoVertical { note_zoom, .. } => match note_zoom {
|
Self::PianoVertical { note_zoom, .. } => match note_zoom {
|
||||||
PhraseViewNoteZoom::Half => 64,
|
PhraseViewNoteZoom::Half => 64,
|
||||||
PhraseViewNoteZoom::N(n) => 128*n,
|
PhraseViewNoteZoom::N(n) => 128*n,
|
||||||
|
_ => unimplemented!()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -269,19 +298,16 @@ impl PhraseViewMode {
|
||||||
Self::PianoHorizontal { note_zoom, .. } => match note_zoom {
|
Self::PianoHorizontal { note_zoom, .. } => match note_zoom {
|
||||||
PhraseViewNoteZoom::Half => 64,
|
PhraseViewNoteZoom::Half => 64,
|
||||||
PhraseViewNoteZoom::N(n) => 128*n,
|
PhraseViewNoteZoom::N(n) => 128*n,
|
||||||
|
_ => unimplemented!()
|
||||||
},
|
},
|
||||||
Self::PianoVertical { time_zoom, .. } => {
|
Self::PianoVertical { time_zoom, .. } => {
|
||||||
phrase.length / time_zoom
|
phrase.length / time_zoom
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn draw_piano_horizontal_half (
|
/// Draw the piano roll using full blocks on note on and half blocks on legato: █▄ █▄ █▄
|
||||||
_: &mut BigBuffer, _: &Phrase, _: usize
|
|
||||||
) {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
fn draw_piano_horizontal (
|
fn draw_piano_horizontal (
|
||||||
buffer: &mut BigBuffer, phrase: &Phrase, time_zoom: usize, note_zoom: usize
|
buffer: &mut BigBuffer, phrase: &Phrase, time_zoom: usize, _: usize
|
||||||
) {
|
) {
|
||||||
let mut notes_on = [false;128];
|
let mut notes_on = [false;128];
|
||||||
for col in 0..buffer.width {
|
for col in 0..buffer.width {
|
||||||
|
|
@ -295,6 +321,8 @@ impl PhraseViewMode {
|
||||||
} else {
|
} else {
|
||||||
cell.set_char(' ');
|
cell.set_char(' ');
|
||||||
}
|
}
|
||||||
|
cell.set_fg(Color::Rgb(255, 255, 255));
|
||||||
|
cell.set_bg(Color::Rgb(28, 35, 25));
|
||||||
}
|
}
|
||||||
for event in phrase.notes[time].iter() {
|
for event in phrase.notes[time].iter() {
|
||||||
match event {
|
match event {
|
||||||
|
|
@ -311,10 +339,17 @@ impl PhraseViewMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//for row in 0..buf.height {
|
}
|
||||||
//let pitch = 127 - row;
|
/// TODO: Draw the piano roll using octant blocks (U+1CD00-U+1CDE5)
|
||||||
|
fn draw_piano_horizontal_octant (
|
||||||
//}
|
_: &mut BigBuffer, _: &Phrase, _: usize
|
||||||
|
) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
/// TODO: Draw the piano roll using half blocks: ▄▀▄
|
||||||
|
fn draw_piano_horizontal_half (
|
||||||
|
_: &mut BigBuffer, _: &Phrase, _: usize
|
||||||
|
) {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue