mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
full block piano roll: fix zooming
This commit is contained in:
parent
aef0213a2b
commit
59d4da1b22
3 changed files with 70 additions and 44 deletions
|
|
@ -24,15 +24,15 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
|
||||||
let note_point = state.note_point.load(Ordering::Relaxed);
|
let note_point = state.note_point.load(Ordering::Relaxed);
|
||||||
let time_start = state.time_start.load(Ordering::Relaxed);
|
let time_start = state.time_start.load(Ordering::Relaxed);
|
||||||
let time_point = state.time_point.load(Ordering::Relaxed);
|
let time_point = state.time_point.load(Ordering::Relaxed);
|
||||||
let time_scale = state.time_scale.load(Ordering::Relaxed);
|
let time_zoom = state.view_mode.time_zoom();
|
||||||
let length = state.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
let length = state.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
||||||
Some(match from.event() {
|
Some(match from.event() {
|
||||||
key!(Char('`')) => ToggleDirection,
|
key!(Char('`')) => ToggleDirection,
|
||||||
key!(Esc) => SetEditMode(PhraseEditMode::Scroll),
|
key!(Esc) => SetEditMode(PhraseEditMode::Scroll),
|
||||||
key!(Char('-')) => SetTimeZoom(next_note_length(time_scale)),
|
key!(Char('-')) => SetTimeZoom(next_note_length(time_zoom)),
|
||||||
key!(Char('_')) => SetTimeZoom(next_note_length(time_scale)),
|
key!(Char('_')) => SetTimeZoom(next_note_length(time_zoom)),
|
||||||
key!(Char('=')) => SetTimeZoom(prev_note_length(time_scale)),
|
key!(Char('=')) => SetTimeZoom(prev_note_length(time_zoom)),
|
||||||
key!(Char('+')) => SetTimeZoom(prev_note_length(time_scale)),
|
key!(Char('+')) => SetTimeZoom(prev_note_length(time_zoom)),
|
||||||
key!(Char('a')) => AppendNote,
|
key!(Char('a')) => AppendNote,
|
||||||
key!(Char('s')) => PutNote,
|
key!(Char('s')) => PutNote,
|
||||||
key!(Char('[')) => SetNoteLength(prev_note_length(state.note_len)),
|
key!(Char('[')) => SetNoteLength(prev_note_length(state.note_len)),
|
||||||
|
|
@ -55,8 +55,8 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
|
||||||
key!(Down) => SetNoteCursor(note_point.saturating_sub(1)),
|
key!(Down) => SetNoteCursor(note_point.saturating_sub(1)),
|
||||||
key!(PageUp) => SetNoteCursor(note_point + 3),
|
key!(PageUp) => SetNoteCursor(note_point + 3),
|
||||||
key!(PageDown) => SetNoteCursor(note_point.saturating_sub(3)),
|
key!(PageDown) => SetNoteCursor(note_point.saturating_sub(3)),
|
||||||
key!(Left) => SetTimeCursor(time_point.saturating_sub(time_scale)),
|
key!(Left) => SetTimeCursor(time_point.saturating_sub(time_zoom)),
|
||||||
key!(Right) => SetTimeCursor((time_point + time_scale) % length),
|
key!(Right) => SetTimeCursor((time_point + time_zoom) % length),
|
||||||
_ => return None
|
_ => return None
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +98,8 @@ impl Command<PhraseEditorModel> for PhraseCommand {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
SetTimeZoom(zoom) => {
|
SetTimeZoom(zoom) => {
|
||||||
state.time_scale.store(zoom, Ordering::Relaxed);
|
state.view_mode.set_time_zoom(zoom);
|
||||||
|
state.show_phrase(state.phrase.clone());
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
SetNoteScroll(note) => {
|
SetNoteScroll(note) => {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ pub struct PhraseEditorModel {
|
||||||
|
|
||||||
pub(crate) time_start: AtomicUsize,
|
pub(crate) time_start: AtomicUsize,
|
||||||
pub(crate) time_point: AtomicUsize,
|
pub(crate) time_point: AtomicUsize,
|
||||||
pub(crate) time_clamp: AtomicUsize,
|
|
||||||
pub(crate) time_scale: AtomicUsize,
|
pub(crate) time_scale: AtomicUsize,
|
||||||
|
|
||||||
pub(crate) edit_mode: PhraseEditMode,
|
pub(crate) edit_mode: PhraseEditMode,
|
||||||
|
|
@ -36,10 +35,9 @@ impl std::fmt::Debug for PhraseEditorModel {
|
||||||
self.note_lo.load(Ordering::Relaxed),
|
self.note_lo.load(Ordering::Relaxed),
|
||||||
self.note_point.load(Ordering::Relaxed),
|
self.note_point.load(Ordering::Relaxed),
|
||||||
))
|
))
|
||||||
.field("time_axis", &format!("{} {} {} {}",
|
.field("time_axis", &format!("{} {} {}",
|
||||||
self.time_start.load(Ordering::Relaxed),
|
self.time_start.load(Ordering::Relaxed),
|
||||||
self.time_point.load(Ordering::Relaxed),
|
self.time_point.load(Ordering::Relaxed),
|
||||||
self.time_clamp.load(Ordering::Relaxed),
|
|
||||||
self.time_scale.load(Ordering::Relaxed),
|
self.time_scale.load(Ordering::Relaxed),
|
||||||
))
|
))
|
||||||
.finish()
|
.finish()
|
||||||
|
|
@ -61,7 +59,6 @@ impl Default for PhraseEditorModel {
|
||||||
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_scale: 24.into(),
|
time_scale: 24.into(),
|
||||||
view_mode: PhraseViewMode::PianoHorizontal {
|
view_mode: PhraseViewMode::PianoHorizontal {
|
||||||
time_zoom: 24,
|
time_zoom: 24,
|
||||||
|
|
@ -96,14 +93,12 @@ impl PhraseEditorModel {
|
||||||
}
|
}
|
||||||
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
|
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
|
||||||
pub fn show_phrase (&mut self, phrase: Option<Arc<RwLock<Phrase>>>) {
|
pub fn show_phrase (&mut self, phrase: Option<Arc<RwLock<Phrase>>>) {
|
||||||
if let Some(phrase) = phrase {
|
if phrase.is_some() {
|
||||||
self.phrase = Some(phrase.clone());
|
self.buffer = self.view_mode.draw(&*phrase.as_ref().unwrap().read().unwrap());
|
||||||
self.time_clamp.store(phrase.read().unwrap().length, Ordering::Relaxed);
|
self.phrase = phrase;
|
||||||
self.buffer = self.view_mode.draw(&*phrase.read().unwrap());
|
|
||||||
} else {
|
} else {
|
||||||
self.phrase = None;
|
|
||||||
self.time_clamp.store(0, Ordering::Relaxed);
|
|
||||||
self.buffer = Default::default();
|
self.buffer = Default::default();
|
||||||
|
self.phrase = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,6 @@ pub struct PhraseView<'a> {
|
||||||
|
|
||||||
time_start: usize,
|
time_start: usize,
|
||||||
time_point: usize,
|
time_point: usize,
|
||||||
time_clamp: usize,
|
|
||||||
time_scale: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> {
|
impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> {
|
||||||
|
|
@ -57,8 +55,6 @@ impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> {
|
||||||
|
|
||||||
time_start: editor.time_start.load(Ordering::Relaxed),
|
time_start: editor.time_start.load(Ordering::Relaxed),
|
||||||
time_point: editor.time_point.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),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -79,8 +75,6 @@ impl<'a> Content for PhraseView<'a> {
|
||||||
note_point,
|
note_point,
|
||||||
time_start,
|
time_start,
|
||||||
time_point,
|
time_point,
|
||||||
time_clamp,
|
|
||||||
time_scale,
|
|
||||||
now,
|
now,
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
@ -110,7 +104,7 @@ impl<'a> Content for PhraseView<'a> {
|
||||||
);
|
);
|
||||||
if let Some(phrase) = phrase {
|
if let Some(phrase) = phrase {
|
||||||
upper_right = format!("┤Time: {}/{} {}├{upper_right}",
|
upper_right = format!("┤Time: {}/{} {}├{upper_right}",
|
||||||
time_point, phrase.read().unwrap().length, pulses_to_name(*time_scale),
|
time_point, phrase.read().unwrap().length, pulses_to_name(view_mode.time_zoom()),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -137,7 +131,7 @@ impl<'a> Content for PhraseView<'a> {
|
||||||
Ok(if *focused && *entered {
|
Ok(if *focused && *entered {
|
||||||
view_mode.blit_cursor(
|
view_mode.blit_cursor(
|
||||||
to,
|
to,
|
||||||
*time_point, *time_start, *time_scale,
|
*time_point, *time_start, view_mode.time_zoom(),
|
||||||
*note_point, *note_len, *note_hi, *note_lo,
|
*note_point, *note_len, *note_hi, *note_lo,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
@ -149,21 +143,21 @@ impl<'a> Content for PhraseView<'a> {
|
||||||
Color::Rgb(70, 80, 50)
|
Color::Rgb(70, 80, 50)
|
||||||
}))),
|
}))),
|
||||||
CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_h(1))), move|to: &mut TuiOutput|{
|
CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_h(1))), move|to: &mut TuiOutput|{
|
||||||
let playhead_inactive = Style::default().fg(Color::Rgb(255,255,255)).bg(Color::Rgb(40,50,30));
|
//let playhead_inactive = Style::default().fg(Color::Rgb(255,255,255)).bg(Color::Rgb(40,50,30));
|
||||||
let playhead_active = playhead_inactive.clone().yellow().bold().not_dim();
|
//let playhead_active = playhead_inactive.clone().yellow().bold().not_dim();
|
||||||
if let Some(_) = phrase {
|
//if let Some(_) = phrase {
|
||||||
let now = now.get() as usize; // TODO FIXME: self.now % phrase.read().unwrap().length;
|
//let now = now.get() as usize; // TODO FIXME: self.now % phrase.read().unwrap().length;
|
||||||
let time_clamp = time_clamp;
|
//let time_clamp = time_clamp;
|
||||||
for x in 0..(time_clamp/time_scale).saturating_sub(*time_start) {
|
//for x in 0..(time_clamp/time_scale).saturating_sub(*time_start) {
|
||||||
let this_step = time_start + (x + 0) * time_scale;
|
//let this_step = time_start + (x + 0) * time_scale;
|
||||||
let next_step = time_start + (x + 1) * time_scale;
|
//let next_step = time_start + (x + 1) * time_scale;
|
||||||
let x = to.area().x() + x as u16;
|
//let x = to.area().x() + x as u16;
|
||||||
let active = this_step <= now && now < next_step;
|
//let active = this_step <= now && now < next_step;
|
||||||
let character = if active { "|" } else { "·" };
|
//let character = if active { "|" } else { "·" };
|
||||||
let style = if active { playhead_active } else { playhead_inactive };
|
//let style = if active { playhead_active } else { playhead_inactive };
|
||||||
to.blit(&character, x, to.area.y(), Some(style));
|
//to.blit(&character, x, to.area.y(), Some(style));
|
||||||
}
|
//}
|
||||||
}
|
//}
|
||||||
Ok(())
|
Ok(())
|
||||||
}).push_x(6).align_sw(),
|
}).push_x(6).align_sw(),
|
||||||
TuiStyle::fg(upper_left.to_string(), title_color).push_x(1).align_nw(),
|
TuiStyle::fg(upper_left.to_string(), title_color).push_x(1).align_nw(),
|
||||||
|
|
@ -194,6 +188,21 @@ pub enum PhraseViewNoteZoom {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhraseViewMode {
|
impl PhraseViewMode {
|
||||||
|
pub fn time_zoom (&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Self::PianoHorizontal { time_zoom, .. } => *time_zoom,
|
||||||
|
_ => unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn set_time_zoom (&mut self, time_zoom: usize) {
|
||||||
|
*self = match self {
|
||||||
|
Self::PianoHorizontal { note_zoom, .. } => Self::PianoHorizontal {
|
||||||
|
note_zoom: *note_zoom,
|
||||||
|
time_zoom,
|
||||||
|
},
|
||||||
|
_ => unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Return a new [BigBuffer] containing a render of the phrase.
|
/// Return a new [BigBuffer] containing a render of the phrase.
|
||||||
pub 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));
|
||||||
|
|
@ -328,15 +337,36 @@ impl PhraseViewMode {
|
||||||
fn draw_piano_horizontal (
|
fn draw_piano_horizontal (
|
||||||
target: &mut BigBuffer, phrase: &Phrase, time_zoom: usize, _: usize
|
target: &mut BigBuffer, phrase: &Phrase, time_zoom: usize, _: usize
|
||||||
) {
|
) {
|
||||||
|
//cell.set_char(if ppq == 0 {
|
||||||
|
//'·'
|
||||||
|
//} else if x % (4 * ppq) == 0 {
|
||||||
|
//'│'
|
||||||
|
//} else if x % ppq == 0 {
|
||||||
|
//'╎'
|
||||||
|
//} else {
|
||||||
|
//'·'
|
||||||
|
//});
|
||||||
|
//cell.set_fg(Color::Rgb(48, 64, 56));
|
||||||
let mut notes_on = [false;128];
|
let mut notes_on = [false;128];
|
||||||
|
for (y, _) in (127..0).enumerate() {
|
||||||
|
for (x, _) in (0..phrase.length).step_by(time_zoom).enumerate() {
|
||||||
|
let cell = target.get_mut(x, y).unwrap();
|
||||||
|
cell.set_fg(Color::Rgb(28, 35, 25));
|
||||||
|
cell.set_char('·');
|
||||||
|
}
|
||||||
|
}
|
||||||
for (x, time_start) in (0..phrase.length).step_by(time_zoom).enumerate() {
|
for (x, time_start) in (0..phrase.length).step_by(time_zoom).enumerate() {
|
||||||
let time_end = time_start + time_zoom;
|
let time_end = time_start + time_zoom;
|
||||||
for time in time_start..time_end {
|
for time in time_start..time_end {
|
||||||
for (y, note) in (127..0).enumerate() {
|
for (y, note) in (127..0).enumerate() {
|
||||||
let cell = target.get_mut(x, y).unwrap();
|
let cell = target.get_mut(x, y).unwrap();
|
||||||
cell.set_fg(Color::Rgb(255, 255, 255));
|
if notes_on[note] {
|
||||||
cell.set_bg(Color::Rgb(28, 35, 25));
|
cell.set_fg(Color::Rgb(255, 255, 255));
|
||||||
cell.set_char(if notes_on[note] { '▄' } else { '?' });
|
cell.set_bg(Color::Rgb(0, 0, 0));
|
||||||
|
cell.set_char('▄');
|
||||||
|
} else {
|
||||||
|
cell.set_char('x');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for event in phrase.notes[time].iter() {
|
for event in phrase.notes[time].iter() {
|
||||||
match event {
|
match event {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue