mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
enable interior mutability for time/note axis
this will allow to adapt the cursor position during render, always keeping it visible
This commit is contained in:
parent
03e2e20258
commit
dd21f73e9d
4 changed files with 83 additions and 60 deletions
|
|
@ -1,5 +1,16 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
pub struct FixedAxis<T> {
|
||||||
|
pub start: T,
|
||||||
|
pub point: Option<T>,
|
||||||
|
pub clamp: Option<T>,
|
||||||
|
}
|
||||||
|
pub struct ScaledAxis<T> {
|
||||||
|
pub start: T,
|
||||||
|
pub scale: T,
|
||||||
|
pub point: Option<T>,
|
||||||
|
pub clamp: Option<T>,
|
||||||
|
}
|
||||||
macro_rules! impl_axis_common { ($A:ident $T:ty) => {
|
macro_rules! impl_axis_common { ($A:ident $T:ty) => {
|
||||||
impl $A<$T> {
|
impl $A<$T> {
|
||||||
#[inline] pub fn start_inc (&mut self, n: $T) -> $T {
|
#[inline] pub fn start_inc (&mut self, n: $T) -> $T {
|
||||||
|
|
@ -20,23 +31,8 @@ macro_rules! impl_axis_common { ($A:ident $T:ty) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} }
|
} }
|
||||||
|
|
||||||
pub struct FixedAxis<T> {
|
|
||||||
pub start: T,
|
|
||||||
pub point: Option<T>,
|
|
||||||
pub clamp: Option<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_axis_common!(FixedAxis u16);
|
impl_axis_common!(FixedAxis u16);
|
||||||
impl_axis_common!(FixedAxis usize);
|
impl_axis_common!(FixedAxis usize);
|
||||||
|
|
||||||
pub struct ScaledAxis<T> {
|
|
||||||
pub start: T,
|
|
||||||
pub scale: T,
|
|
||||||
pub point: Option<T>,
|
|
||||||
pub clamp: Option<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_axis_common!(ScaledAxis u16);
|
impl_axis_common!(ScaledAxis u16);
|
||||||
impl_axis_common!(ScaledAxis usize);
|
impl_axis_common!(ScaledAxis usize);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,9 +88,9 @@ pub struct PhraseEditor<E: Engine> {
|
||||||
/// The full piano roll is rendered to this buffer
|
/// The full piano roll is rendered to this buffer
|
||||||
pub buffer: BigBuffer,
|
pub buffer: BigBuffer,
|
||||||
/// Cursor/scroll/zoom in pitch axis
|
/// Cursor/scroll/zoom in pitch axis
|
||||||
pub note_axis: FixedAxis<usize>,
|
pub note_axis: RwLock<FixedAxis<usize>>,
|
||||||
/// Cursor/scroll/zoom in time axis
|
/// Cursor/scroll/zoom in time axis
|
||||||
pub time_axis: ScaledAxis<usize>,
|
pub time_axis: RwLock<ScaledAxis<usize>>,
|
||||||
/// Whether this widget is focused
|
/// Whether this widget is focused
|
||||||
pub focused: bool,
|
pub focused: bool,
|
||||||
/// Whether note enter mode is enabled
|
/// Whether note enter mode is enabled
|
||||||
|
|
@ -235,14 +235,23 @@ impl<E: Engine> PhraseEditor<E> {
|
||||||
notes_out: Arc::new(RwLock::new([false;128])),
|
notes_out: Arc::new(RwLock::new([false;128])),
|
||||||
keys: keys_vert(),
|
keys: keys_vert(),
|
||||||
buffer: Default::default(),
|
buffer: Default::default(),
|
||||||
note_axis: FixedAxis { start: 12, point: Some(36), clamp: Some(127) },
|
|
||||||
time_axis: ScaledAxis { start: 00, point: Some(00), clamp: Some(000), scale: 24 },
|
|
||||||
focused: false,
|
focused: false,
|
||||||
entered: false,
|
entered: false,
|
||||||
mode: false,
|
mode: false,
|
||||||
now: Arc::new(0.into()),
|
now: Arc::new(0.into()),
|
||||||
width: 0.into(),
|
width: 0.into(),
|
||||||
height: 0.into(),
|
height: 0.into(),
|
||||||
|
note_axis: RwLock::new(FixedAxis {
|
||||||
|
start: 12,
|
||||||
|
point: Some(36),
|
||||||
|
clamp: Some(127)
|
||||||
|
}),
|
||||||
|
time_axis: RwLock::new(ScaledAxis {
|
||||||
|
start: 00,
|
||||||
|
point: Some(00),
|
||||||
|
clamp: Some(000),
|
||||||
|
scale: 24
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,35 +89,45 @@ impl Handle<Tui> for PhraseEditor<Tui> {
|
||||||
key!(KeyCode::Char('`')) => { self.mode = !self.mode; },
|
key!(KeyCode::Char('`')) => { self.mode = !self.mode; },
|
||||||
key!(KeyCode::Enter) => { self.entered = true; },
|
key!(KeyCode::Enter) => { self.entered = true; },
|
||||||
key!(KeyCode::Esc) => { self.entered = false; },
|
key!(KeyCode::Esc) => { self.entered = false; },
|
||||||
key!(KeyCode::PageUp) => { self.note_axis.start_dec(3); },
|
key!(KeyCode::PageUp) => { self.note_axis.write().unwrap().start_dec(3); },
|
||||||
key!(KeyCode::PageDown) => { self.note_axis.start_inc(3); },
|
key!(KeyCode::PageDown) => { self.note_axis.write().unwrap().start_inc(3); },
|
||||||
key!(KeyCode::Up) => match self.entered {
|
key!(KeyCode::Up) => match self.entered {
|
||||||
true => { self.note_axis.point_dec(1); },
|
true => { self.note_axis.write().unwrap().point_dec(1); },
|
||||||
false => { self.note_axis.start_dec(1); },
|
false => { self.note_axis.write().unwrap().start_dec(1); },
|
||||||
},
|
},
|
||||||
key!(KeyCode::Down) => match self.entered {
|
key!(KeyCode::Down) => match self.entered {
|
||||||
true => { self.note_axis.point_inc(1); },
|
true => { self.note_axis.write().unwrap().point_inc(1); },
|
||||||
false => { self.note_axis.start_inc(1); },
|
false => { self.note_axis.write().unwrap().start_inc(1); },
|
||||||
},
|
},
|
||||||
key!(KeyCode::Left) => match self.entered {
|
key!(KeyCode::Left) => {
|
||||||
true => { self.time_axis.point_dec(self.time_axis.scale); },
|
let scale = self.time_axis.read().unwrap().scale;
|
||||||
false => { self.time_axis.start_dec(self.time_axis.scale); },
|
match self.entered {
|
||||||
|
true => { self.time_axis.write().unwrap().point_dec(scale); },
|
||||||
|
false => { self.time_axis.write().unwrap().start_dec(scale); },
|
||||||
|
};
|
||||||
},
|
},
|
||||||
key!(KeyCode::Right) => match self.entered {
|
key!(KeyCode::Right) => {
|
||||||
true => { self.time_axis.point_inc(self.time_axis.scale); },
|
let scale = self.time_axis.read().unwrap().scale;
|
||||||
false => { self.time_axis.start_inc(self.time_axis.scale); },
|
match self.entered {
|
||||||
|
true => { self.time_axis.write().unwrap().point_inc(scale); },
|
||||||
|
false => { self.time_axis.write().unwrap().start_inc(scale); },
|
||||||
|
}
|
||||||
},
|
},
|
||||||
key!(KeyCode::Char('-')) => {
|
key!(KeyCode::Char('-')) => {
|
||||||
self.time_axis.scale = next_note_length(self.time_axis.scale)
|
let scale = self.time_axis.read().unwrap().scale;
|
||||||
|
self.time_axis.write().unwrap().scale = next_note_length(scale)
|
||||||
},
|
},
|
||||||
key!(KeyCode::Char('_')) => {
|
key!(KeyCode::Char('_')) => {
|
||||||
self.time_axis.scale = next_note_length(self.time_axis.scale)
|
let scale = self.time_axis.read().unwrap().scale;
|
||||||
|
self.time_axis.write().unwrap().scale = next_note_length(scale)
|
||||||
},
|
},
|
||||||
key!(KeyCode::Char('=')) => {
|
key!(KeyCode::Char('=')) => {
|
||||||
self.time_axis.scale = prev_note_length(self.time_axis.scale)
|
let scale = self.time_axis.read().unwrap().scale;
|
||||||
|
self.time_axis.write().unwrap().scale = prev_note_length(scale)
|
||||||
},
|
},
|
||||||
key!(KeyCode::Char('+')) => {
|
key!(KeyCode::Char('+')) => {
|
||||||
self.time_axis.scale = prev_note_length(self.time_axis.scale)
|
let scale = self.time_axis.read().unwrap().scale;
|
||||||
|
self.time_axis.write().unwrap().scale = prev_note_length(scale)
|
||||||
},
|
},
|
||||||
key!(KeyCode::Char('[')) => if self.entered {
|
key!(KeyCode::Char('[')) => if self.entered {
|
||||||
self.note_len = prev_note_length(self.note_len)
|
self.note_len = prev_note_length(self.note_len)
|
||||||
|
|
@ -127,8 +137,10 @@ impl Handle<Tui> for PhraseEditor<Tui> {
|
||||||
},
|
},
|
||||||
key!(KeyCode::Char('a')) => if self.entered {
|
key!(KeyCode::Char('a')) => if self.entered {
|
||||||
self.put();
|
self.put();
|
||||||
self.time_axis.point = self.time_axis.point.map(|time|(time + self.note_len)
|
let point = self.time_axis.read().unwrap().point;
|
||||||
% self.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1));
|
let length = self.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
||||||
|
let forward = |time|(time + self.note_len) % length;
|
||||||
|
self.time_axis.write().unwrap().point = point.map(forward);
|
||||||
},
|
},
|
||||||
key!(KeyCode::Char('s')) => if self.entered {
|
key!(KeyCode::Char('s')) => if self.entered {
|
||||||
self.put();
|
self.put();
|
||||||
|
|
|
||||||
|
|
@ -52,12 +52,18 @@ impl Content for PhraseEditor<Tui> {
|
||||||
let Self {
|
let Self {
|
||||||
focused, entered, time_axis, note_axis, keys, phrase, buffer, note_len, ..
|
focused, entered, time_axis, note_axis, keys, phrase, buffer, note_len, ..
|
||||||
} = self;
|
} = self;
|
||||||
|
let FixedAxis {
|
||||||
|
start: note_start, point: note_point, clamp: note_clamp
|
||||||
|
} = *self.note_axis.read().unwrap();
|
||||||
|
let ScaledAxis {
|
||||||
|
start: time_start, point: time_point, clamp: time_clamp, scale: time_scale
|
||||||
|
} = *self.time_axis.read().unwrap();
|
||||||
let color = Color::Rgb(0,255,0);
|
let color = Color::Rgb(0,255,0);
|
||||||
let color = phrase.as_ref().map(|p|p.read().unwrap().color).unwrap_or(color);
|
let color = phrase.as_ref().map(|p|p.read().unwrap().color).unwrap_or(color);
|
||||||
let keys = CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_w(5))), move|to: &mut TuiOutput|{
|
let keys = CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_w(5))), move|to: &mut TuiOutput|{
|
||||||
if to.area().h() >= 2 {
|
if to.area().h() >= 2 {
|
||||||
to.buffer_update(to.area().set_w(5), &|cell, x, y|{
|
to.buffer_update(to.area().set_w(5), &|cell, x, y|{
|
||||||
let y = y + note_axis.start as u16;
|
let y = y + note_start as u16;
|
||||||
if x < keys.area.width && y < keys.area.height {
|
if x < keys.area.width && y < keys.area.height {
|
||||||
*cell = keys.get(x, y).clone()
|
*cell = keys.get(x, y).clone()
|
||||||
}
|
}
|
||||||
|
|
@ -73,8 +79,8 @@ impl Content for PhraseEditor<Tui> {
|
||||||
let area = to.area();
|
let area = to.area();
|
||||||
to.buffer_update(area, &move |cell, x, y|{
|
to.buffer_update(area, &move |cell, x, y|{
|
||||||
cell.set_bg(notes_bg_null);
|
cell.set_bg(notes_bg_null);
|
||||||
let src_x = (x as usize + time_axis.start) * time_axis.scale;
|
let src_x = (x as usize + time_start) * time_scale;
|
||||||
let src_y = y as usize + note_axis.start;
|
let src_y = y as usize + note_start;
|
||||||
if src_x < buffer.width && src_y < buffer.height - 1 {
|
if src_x < buffer.width && src_y < buffer.height - 1 {
|
||||||
buffer.get(src_x, buffer.height - src_y).map(|src|{
|
buffer.get(src_x, buffer.height - src_y).map(|src|{
|
||||||
cell.set_symbol(src.symbol());
|
cell.set_symbol(src.symbol());
|
||||||
|
|
@ -89,9 +95,9 @@ impl Content for PhraseEditor<Tui> {
|
||||||
let cursor = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
|
let cursor = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
|
||||||
if *entered {
|
if *entered {
|
||||||
let area = to.area();
|
let area = to.area();
|
||||||
if let (Some(time), Some(note)) = (time_axis.point, note_axis.point) {
|
if let (Some(time), Some(note)) = (time_point, note_point) {
|
||||||
let x1 = area.x() + (time / time_axis.scale) as u16;
|
let x1 = area.x() + (time / time_scale) as u16;
|
||||||
let x2 = x1 + (self.note_len / time_axis.scale) as u16;
|
let x2 = x1 + (self.note_len / time_scale) as u16;
|
||||||
let y = area.y() + 1 + note as u16 / 2;
|
let y = area.y() + 1 + note as u16 / 2;
|
||||||
let c = if note % 2 == 0 { "▀" } else { "▄" };
|
let c = if note % 2 == 0 { "▀" } else { "▄" };
|
||||||
for x in x1..x2 {
|
for x in x1..x2 {
|
||||||
|
|
@ -108,11 +114,11 @@ impl Content for PhraseEditor<Tui> {
|
||||||
move|to: &mut TuiOutput|{
|
move|to: &mut TuiOutput|{
|
||||||
if let Some(_) = phrase {
|
if let Some(_) = phrase {
|
||||||
let now = self.now.load(Ordering::Relaxed); // TODO FIXME: self.now % phrase.read().unwrap().length;
|
let now = self.now.load(Ordering::Relaxed); // TODO FIXME: self.now % phrase.read().unwrap().length;
|
||||||
let ScaledAxis { start: first_beat, scale: time_zoom, clamp, .. } = time_axis;
|
let time_clamp = time_clamp
|
||||||
let clamp = clamp.expect("time_axis of sequencer expected to be clamped");
|
.expect("time_axis of sequencer expected to be clamped");
|
||||||
for x in 0..clamp/time_zoom {
|
for x in 0..time_clamp/time_scale {
|
||||||
let this_step = (x * time_zoom + first_beat + 0) * time_zoom;
|
let this_step = (x * time_scale + time_start + 0) * time_scale;
|
||||||
let next_step = (x * time_zoom + first_beat + 1) * time_zoom;
|
let next_step = (x * time_scale + time_start + 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 { "·" };
|
||||||
|
|
@ -136,18 +142,18 @@ impl Content for PhraseEditor<Tui> {
|
||||||
upper_left = format!("{upper_left}: {}", phrase.read().unwrap().name);
|
upper_left = format!("{upper_left}: {}", phrase.read().unwrap().name);
|
||||||
}
|
}
|
||||||
let mut upper_right = format!("Zoom: {} (+{}:{}*{}|{})",
|
let mut upper_right = format!("Zoom: {} (+{}:{}*{}|{})",
|
||||||
ppq_to_name(time_axis.scale),
|
ppq_to_name(time_scale),
|
||||||
time_axis.start,
|
time_start,
|
||||||
time_axis.point.unwrap_or(0),
|
time_point.unwrap_or(0),
|
||||||
time_axis.scale,
|
time_scale,
|
||||||
time_axis.clamp.unwrap_or(0),
|
time_clamp.unwrap_or(0),
|
||||||
);
|
);
|
||||||
if *focused && *entered {
|
if *focused && *entered {
|
||||||
upper_right = format!("Note: {} (+{}:{}|{}) {upper_right}",
|
upper_right = format!("Note: {} (+{}:{}|{}) {upper_right}",
|
||||||
ppq_to_name(*note_len),
|
ppq_to_name(*note_len),
|
||||||
note_axis.start,
|
note_start,
|
||||||
note_axis.point.unwrap_or(0),
|
note_point.unwrap_or(0),
|
||||||
note_axis.clamp.unwrap_or(0),
|
note_clamp.unwrap_or(0),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
lay!(
|
lay!(
|
||||||
|
|
@ -161,8 +167,8 @@ impl<E: Engine> PhraseEditor<E> {
|
||||||
pub fn put (&mut self) {
|
pub fn put (&mut self) {
|
||||||
if let (Some(phrase), Some(time), Some(note)) = (
|
if let (Some(phrase), Some(time), Some(note)) = (
|
||||||
&self.phrase,
|
&self.phrase,
|
||||||
self.time_axis.point,
|
self.time_axis.read().unwrap().point,
|
||||||
self.note_axis.point,
|
self.note_axis.read().unwrap().point,
|
||||||
) {
|
) {
|
||||||
let mut phrase = phrase.write().unwrap();
|
let mut phrase = phrase.write().unwrap();
|
||||||
let key: u7 = u7::from((127 - note) as u8);
|
let key: u7 = u7::from((127 - note) as u8);
|
||||||
|
|
@ -178,11 +184,11 @@ impl<E: Engine> PhraseEditor<E> {
|
||||||
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
|
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
|
||||||
if let Some(phrase) = phrase {
|
if let Some(phrase) = phrase {
|
||||||
self.phrase = Some(phrase.clone());
|
self.phrase = Some(phrase.clone());
|
||||||
self.time_axis.clamp = Some(phrase.read().unwrap().length);
|
self.time_axis.write().unwrap().clamp = Some(phrase.read().unwrap().length);
|
||||||
self.buffer = Self::redraw(&*phrase.read().unwrap());
|
self.buffer = Self::redraw(&*phrase.read().unwrap());
|
||||||
} else {
|
} else {
|
||||||
self.phrase = None;
|
self.phrase = None;
|
||||||
self.time_axis.clamp = Some(0);
|
self.time_axis.write().unwrap().clamp = Some(0);
|
||||||
self.buffer = Default::default();
|
self.buffer = Default::default();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue