fix editor behaviors

This commit is contained in:
🪞👃🪞 2025-01-16 16:22:16 +01:00
parent 6408cd26b8
commit 968441850f
8 changed files with 151 additions and 148 deletions

View file

@ -1,12 +1,12 @@
(@up note/pos :note-pos-next) (@up note/pos :note-pos-next)
(@down note/pos :note-pos-prev) (@down note/pos :note-pos-prev)
(@comma note/len :note-length-prev) (@comma note/len :note-len-prev)
(@period note/len :note-length-next) (@period note/len :note-len-next)
(@plus note/range :note-range-next-) (@plus note/range :note-range-next-)
(@underscore note/range :note-range-prev-) (@underscore note/range :note-range-prev-)
(@left time/pos :time-pos-next) (@left time/pos :time-pos-prev)
(@right time/pos :time-pos-prev) (@right time/pos :time-pos-next)
(@equal time/zoom :time-zoom-prev) (@equal time/zoom :time-zoom-prev)
(@minus time/zoom :time-zoom-next) (@minus time/zoom :time-zoom-next)
(@z time/lock) (@z time/lock)

View file

@ -1,6 +1,4 @@
use crate::*; use crate::*;
use self::MidiEditCommand::*;
use KeyCode::*;
pub trait HasEditor { pub trait HasEditor {
fn editor (&self) -> &Option<MidiEditor>; fn editor (&self) -> &Option<MidiEditor>;
fn editor_mut (&mut self) -> &Option<MidiEditor>; fn editor_mut (&mut self) -> &Option<MidiEditor>;
@ -52,21 +50,33 @@ from!(|clip: Option<Arc<RwLock<MidiClip>>>|MidiEditor = {
model model
}); });
edn_provide!(bool: |self: MidiEditor| { edn_provide!(bool: |self: MidiEditor| {
":true" => true, ":true" => true,
":false" => false ":false" => false,
":time-lock" => self.time_lock().get(),
":time-lock-toggle" => !self.time_lock().get(),
}); });
edn_provide!(usize: |self: MidiEditor| { edn_provide!(usize: |self: MidiEditor| {
":note-length" => self.note_len(), ":note-length" => self.note_len(),
":note-pos" => self.note_point(), ":note-pos" => self.note_pos(),
":note-pos-next" => self.note_point() + 1, ":note-pos-next" => self.note_pos() + 1,
":note-pos-prev" => self.note_point().saturating_sub(1), ":note-pos-prev" => self.note_pos().saturating_sub(1),
":time-pos" => self.time_point(), ":note-len" => self.note_len(),
":time-pos-next" => self.time_point() + self.time_zoom().get(), ":note-len-next" => self.note_len() + 1,
":time-pos-prev" => self.time_point().saturating_sub(self.time_zoom().get()), ":note-len-prev" => self.note_len().saturating_sub(1),
":note-range" => self.note_axis().get(),
":note-range-prev" => self.note_axis().get() + 1,
":note-range-next" => self.note_axis().get().saturating_sub(1),
":time-pos" => self.time_pos(),
":time-pos-next" => self.time_pos() + self.time_zoom().get(),
":time-pos-prev" => self.time_pos().saturating_sub(self.time_zoom().get()),
":time-zoom" => self.time_zoom().get(), ":time-zoom" => self.time_zoom().get(),
":time-zoom-next" => self.time_zoom().get() + 1,
":time-zoom-prev" => self.time_zoom().get().saturating_sub(1).max(1),
}); });
impl std::fmt::Debug for MidiEditor { impl std::fmt::Debug for MidiEditor {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
@ -91,12 +101,12 @@ impl MidiEditor {
pub fn put_note (&mut self, advance: bool) { pub fn put_note (&mut self, advance: bool) {
let mut redraw = false; let mut redraw = false;
if let Some(clip) = self.clip() { if let Some(clip) = self.clip() {
let mut clip = clip.write().unwrap(); let mut clip = clip.write().unwrap();
let note_start = self.time_point(); let note_start = self.time_pos();
let note_point = self.note_point(); let note_pos = self.note_pos();
let note_len = self.note_len(); let note_len = self.note_len();
let note_end = note_start + (note_len.saturating_sub(1)); let note_end = note_start + (note_len.saturating_sub(1));
let key: u7 = u7::from(note_point as u8); let key: u7 = u7::from(note_pos as u8);
let vel: u7 = 100.into(); let vel: u7 = 100.into();
let length = clip.length; let length = clip.length;
let note_end = note_end % length; let note_end = note_end % length;
@ -109,7 +119,7 @@ impl MidiEditor {
clip.notes[note_end].push(note_off); clip.notes[note_end].push(note_off);
} }
if advance { if advance {
self.set_time_point(note_end); self.set_time_pos(note_end);
} }
redraw = true; redraw = true;
} }
@ -119,25 +129,25 @@ impl MidiEditor {
} }
} }
impl TimeRange for MidiEditor { impl TimeRange for MidiEditor {
fn time_len (&self) -> &AtomicUsize { self.mode.time_len() } fn time_len (&self) -> &AtomicUsize { self.mode.time_len() }
fn time_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() } fn time_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() }
fn time_lock (&self) -> &AtomicBool { self.mode.time_lock() } fn time_lock (&self) -> &AtomicBool { self.mode.time_lock() }
fn time_start (&self) -> &AtomicUsize { self.mode.time_start() } fn time_start (&self) -> &AtomicUsize { self.mode.time_start() }
fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() } fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() }
} }
impl NoteRange for MidiEditor { impl NoteRange for MidiEditor {
fn note_lo (&self) -> &AtomicUsize { self.mode.note_lo() } fn note_lo (&self) -> &AtomicUsize { self.mode.note_lo() }
fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() } fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() }
} }
impl NotePoint for MidiEditor { impl NotePoint for MidiEditor {
fn note_len (&self) -> usize { self.mode.note_len() } fn note_len (&self) -> usize { self.mode.note_len() }
fn set_note_len (&self, x: usize) { self.mode.set_note_len(x) } fn set_note_len (&self, x: usize) { self.mode.set_note_len(x) }
fn note_point (&self) -> usize { self.mode.note_point() } fn note_pos (&self) -> usize { self.mode.note_pos() }
fn set_note_point (&self, x: usize) { self.mode.set_note_point(x) } fn set_note_pos (&self, x: usize) { self.mode.set_note_pos(x) }
} }
impl TimePoint for MidiEditor { impl TimePoint for MidiEditor {
fn time_point (&self) -> usize { self.mode.time_point() } fn time_pos (&self) -> usize { self.mode.time_pos() }
fn set_time_point (&self, x: usize) { self.mode.set_time_point(x) } fn set_time_pos (&self, x: usize) { self.mode.set_time_pos(x) }
} }
impl MidiViewer for MidiEditor { impl MidiViewer for MidiEditor {
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) { self.mode.buffer_size(clip) } fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) { self.mode.buffer_size(clip) }
@ -164,15 +174,15 @@ impl MidiEditor {
} else { } else {
(ItemPalette::from(TuiTheme::g(64)), 0) (ItemPalette::from(TuiTheme::g(64)), 0)
}; };
let time_point = self.time_point(); let time_pos = self.time_pos();
let time_zoom = self.time_zoom().get(); let time_zoom = self.time_zoom().get();
let time_lock = if self.time_lock().get() { "[lock]" } else { " " }; let time_lock = if self.time_lock().get() { "[lock]" } else { " " };
let note_point = format!("{:>3}", self.note_point()); let note_pos = format!("{:>3}", self.note_pos());
let note_name = format!("{:4}", Note::pitch_to_name(self.note_point())); let note_name = format!("{:4}", Note::pitch_to_name(self.note_pos()));
let note_len = format!("{:>4}", self.note_len()); let note_len = format!("{:>4}", self.note_len());
Bsp::e( Bsp::e(
FieldV(color, "Time", format!("{length}/{time_zoom}+{time_point} {time_lock}")), FieldV(color, "Time", format!("{length}/{time_zoom}+{time_pos} {time_lock}")),
FieldV(color, "Note", format!("{note_name} {note_point} {note_len}")), FieldV(color, "Note", format!("{note_name} {note_pos} {note_len}")),
) )
} }
} }
@ -184,23 +194,8 @@ edn_command!(MidiEditCommand: |state: MidiEditor| {
("time/pos" [a: usize] Self::SetTimeCursor(a.expect("no time cursor"))) ("time/pos" [a: usize] Self::SetTimeCursor(a.expect("no time cursor")))
("time/zoom" [a: usize] Self::SetTimeZoom(a.expect("no time zoom"))) ("time/zoom" [a: usize] Self::SetTimeZoom(a.expect("no time zoom")))
("time/lock" [a: bool] Self::SetTimeLock(a.expect("no time lock"))) ("time/lock" [a: bool] Self::SetTimeLock(a.expect("no time lock")))
("time/lock" [] Self::SetTimeLock(!state.time_lock().get()))
}); });
//impl EdnCommand<MidiEditor> for MidiEditCommand {
//fn from_edn <'a> (state: &MidiEditor, head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
//use EdnItem::*;
//match (head, tail) {
//(Key("note/put"), [a]) => Self::PutNote,
//(Key("note/del"), [a]) => Self::AppendNote,
//(Key("note/dur"), [a]) => Self::AppendNote,
//(Key("note/range"), [a]) => Self::AppendNote,
//(Key("note/pos"), [a]) => Self::AppendNote,
//(Key("time/pos"), [a]) => Self::AppendNote,
//(Key("time/zoom"), [a]) => Self::AppendNote,
//(Key("time/lock"), [a]) => Self::AppendNote,
//_ => todo!()
//}
//}
//}
#[derive(Clone, Debug)] pub enum MidiEditCommand { #[derive(Clone, Debug)] pub enum MidiEditCommand {
// TODO: 1-9 seek markers that by default start every 8th of the clip // TODO: 1-9 seek markers that by default start every 8th of the clip
AppendNote, AppendNote,
@ -241,27 +236,54 @@ handle!(TuiIn: |self: MidiEditor, input|{
None None
}) })
}); });
impl Command<MidiEditor> for MidiEditCommand {
fn execute (self, state: &mut MidiEditor) -> Perhaps<Self> {
use MidiEditCommand::*;
match self {
Show(clip) => { state.set_clip(clip.as_ref()); },
PutNote => { state.put_note(false); },
AppendNote => { state.put_note(true); },
SetTimeZoom(x) => { state.time_zoom().set(x); state.redraw(); },
SetTimeLock(x) => { state.time_lock().set(x); },
SetTimeScroll(x) => { state.time_start().set(x); },
SetNoteScroll(x) => { state.note_lo().set(x.min(127)); },
SetNoteLength(x) => {
let note_len = state.note_len();
let time_zoom = state.time_zoom().get();
state.set_note_len(x);
if note_len / time_zoom != x / time_zoom {
state.redraw();
}
},
SetTimeCursor(x) => { state.set_time_pos(x); },
SetNoteCursor(note) => { state.set_note_pos(note.min(127)); },
//_ => todo!("{:?}", self)
}
Ok(None)
}
}
//keymap!(KEYS_MIDI_EDITOR = |s: MidiEditor, _input: Event| MidiEditCommand { //keymap!(KEYS_MIDI_EDITOR = |s: MidiEditor, _input: Event| MidiEditCommand {
//key(Up) => SetNoteCursor(s.note_point() + 1), //key(Up) => SetNoteCursor(s.note_pos() + 1),
//key(Char('w')) => SetNoteCursor(s.note_point() + 1), //key(Char('w')) => SetNoteCursor(s.note_pos() + 1),
//key(Down) => SetNoteCursor(s.note_point().saturating_sub(1)), //key(Down) => SetNoteCursor(s.note_pos().saturating_sub(1)),
//key(Char('s')) => SetNoteCursor(s.note_point().saturating_sub(1)), //key(Char('s')) => SetNoteCursor(s.note_pos().saturating_sub(1)),
//key(Left) => SetTimeCursor(s.time_point().saturating_sub(s.note_len())), //key(Left) => SetTimeCursor(s.time_pos().saturating_sub(s.note_len())),
//key(Char('a')) => SetTimeCursor(s.time_point().saturating_sub(s.note_len())), //key(Char('a')) => SetTimeCursor(s.time_pos().saturating_sub(s.note_len())),
//key(Right) => SetTimeCursor((s.time_point() + s.note_len()) % s.clip_length()), //key(Right) => SetTimeCursor((s.time_pos() + s.note_len()) % s.clip_length()),
//ctrl(alt(key(Up))) => SetNoteScroll(s.note_point() + 3), //ctrl(alt(key(Up))) => SetNoteScroll(s.note_pos() + 3),
//ctrl(alt(key(Down))) => SetNoteScroll(s.note_point().saturating_sub(3)), //ctrl(alt(key(Down))) => SetNoteScroll(s.note_pos().saturating_sub(3)),
//ctrl(alt(key(Left))) => SetTimeScroll(s.time_point().saturating_sub(s.time_zoom().get())), //ctrl(alt(key(Left))) => SetTimeScroll(s.time_pos().saturating_sub(s.time_zoom().get())),
//ctrl(alt(key(Right))) => SetTimeScroll((s.time_point() + s.time_zoom().get()) % s.clip_length()), //ctrl(alt(key(Right))) => SetTimeScroll((s.time_pos() + s.time_zoom().get()) % s.clip_length()),
//ctrl(key(Up)) => SetNoteScroll(s.note_lo().get() + 1), //ctrl(key(Up)) => SetNoteScroll(s.note_lo().get() + 1),
//ctrl(key(Down)) => SetNoteScroll(s.note_lo().get().saturating_sub(1)), //ctrl(key(Down)) => SetNoteScroll(s.note_lo().get().saturating_sub(1)),
//ctrl(key(Left)) => SetTimeScroll(s.time_start().get().saturating_sub(s.note_len())), //ctrl(key(Left)) => SetTimeScroll(s.time_start().get().saturating_sub(s.note_len())),
//ctrl(key(Right)) => SetTimeScroll(s.time_start().get() + s.note_len()), //ctrl(key(Right)) => SetTimeScroll(s.time_start().get() + s.note_len()),
//alt(key(Up)) => SetNoteCursor(s.note_point() + 3), //alt(key(Up)) => SetNoteCursor(s.note_pos() + 3),
//alt(key(Down)) => SetNoteCursor(s.note_point().saturating_sub(3)), //alt(key(Down)) => SetNoteCursor(s.note_pos().saturating_sub(3)),
//alt(key(Left)) => SetTimeCursor(s.time_point().saturating_sub(s.time_zoom().get())), //alt(key(Left)) => SetTimeCursor(s.time_pos().saturating_sub(s.time_zoom().get())),
//alt(key(Right)) => SetTimeCursor((s.time_point() + s.time_zoom().get()) % s.clip_length()), //alt(key(Right)) => SetTimeCursor((s.time_pos() + s.time_zoom().get()) % s.clip_length()),
//key(Char('d')) => SetTimeCursor((s.time_point() + s.note_len()) % s.clip_length()), //key(Char('d')) => SetTimeCursor((s.time_pos() + s.note_len()) % s.clip_length()),
//key(Char('z')) => SetTimeLock(!s.time_lock().get()), //key(Char('z')) => SetTimeLock(!s.time_lock().get()),
//key(Char('-')) => SetTimeZoom(if s.time_lock().get() { s.time_zoom().get() } else { NoteDuration::next(s.time_zoom().get()) }), //key(Char('-')) => SetTimeZoom(if s.time_lock().get() { s.time_zoom().get() } else { NoteDuration::next(s.time_zoom().get()) }),
//key(Char('_')) => SetTimeZoom(if s.time_lock().get() { s.time_zoom().get() } else { NoteDuration::next(s.time_zoom().get()) }), //key(Char('_')) => SetTimeZoom(if s.time_lock().get() { s.time_zoom().get() } else { NoteDuration::next(s.time_zoom().get()) }),
@ -276,22 +298,3 @@ handle!(TuiIn: |self: MidiEditor, input|{
////// TODO: kpat!(Char('/')) => // toggle 3plet ////// TODO: kpat!(Char('/')) => // toggle 3plet
////// TODO: kpat!(Char('?')) => // toggle dotted ////// TODO: kpat!(Char('?')) => // toggle dotted
//}); //});
impl Command<MidiEditor> for MidiEditCommand {
fn execute (self, state: &mut MidiEditor) -> Perhaps<Self> {
use MidiEditCommand::*;
match self {
Show(clip) => { state.set_clip(clip.as_ref()); },
PutNote => { state.put_note(false); },
AppendNote => { state.put_note(true); },
SetTimeZoom(x) => { state.time_zoom().set(x); state.redraw(); },
SetTimeLock(x) => { state.time_lock().set(x); },
SetTimeScroll(x) => { state.time_start().set(x); },
SetNoteScroll(x) => { state.note_lo().set(x.min(127)); },
SetNoteLength(x) => { state.set_note_len(x); },
SetTimeCursor(x) => { state.set_time_point(x); },
SetNoteCursor(note) => { state.set_note_point(note.min(127)); },
_ => todo!("{:?}", self)
}
Ok(None)
}
}

View file

@ -3,9 +3,9 @@ use crate::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MidiPointModel { pub struct MidiPointModel {
/// Time coordinate of cursor /// Time coordinate of cursor
pub time_point: Arc<AtomicUsize>, pub time_pos: Arc<AtomicUsize>,
/// Note coordinate of cursor /// Note coordinate of cursor
pub note_point: Arc<AtomicUsize>, pub note_pos: Arc<AtomicUsize>,
/// Length of note that will be inserted, in pulses /// Length of note that will be inserted, in pulses
pub note_len: Arc<AtomicUsize>, pub note_len: Arc<AtomicUsize>,
} }
@ -13,8 +13,8 @@ pub struct MidiPointModel {
impl Default for MidiPointModel { impl Default for MidiPointModel {
fn default () -> Self { fn default () -> Self {
Self { Self {
time_point: Arc::new(0.into()), time_pos: Arc::new(0.into()),
note_point: Arc::new(36.into()), note_pos: Arc::new(36.into()),
note_len: Arc::new(24.into()), note_len: Arc::new(24.into()),
} }
} }
@ -23,14 +23,14 @@ impl Default for MidiPointModel {
pub trait NotePoint { pub trait NotePoint {
fn note_len (&self) -> usize; fn note_len (&self) -> usize;
fn set_note_len (&self, x: usize); fn set_note_len (&self, x: usize);
fn note_point (&self) -> usize; fn note_pos (&self) -> usize;
fn set_note_point (&self, x: usize); fn set_note_pos (&self, x: usize);
fn note_end (&self) -> usize { self.note_point() + self.note_len() } fn note_end (&self) -> usize { self.note_pos() + self.note_len() }
} }
pub trait TimePoint { pub trait TimePoint {
fn time_point (&self) -> usize; fn time_pos (&self) -> usize;
fn set_time_point (&self, x: usize); fn set_time_pos (&self, x: usize);
} }
pub trait MidiPoint: NotePoint + TimePoint {} pub trait MidiPoint: NotePoint + TimePoint {}
@ -40,11 +40,11 @@ impl<T: NotePoint + TimePoint> MidiPoint for T {}
impl NotePoint for MidiPointModel { impl NotePoint for MidiPointModel {
fn note_len (&self) -> usize { self.note_len.load(Relaxed)} fn note_len (&self) -> usize { self.note_len.load(Relaxed)}
fn set_note_len (&self, x: usize) { self.note_len.store(x, Relaxed) } fn set_note_len (&self, x: usize) { self.note_len.store(x, Relaxed) }
fn note_point (&self) -> usize { self.note_point.load(Relaxed).min(127) } fn note_pos (&self) -> usize { self.note_pos.load(Relaxed).min(127) }
fn set_note_point (&self, x: usize) { self.note_point.store(x.min(127), Relaxed) } fn set_note_pos (&self, x: usize) { self.note_pos.store(x.min(127), Relaxed) }
} }
impl TimePoint for MidiPointModel { impl TimePoint for MidiPointModel {
fn time_point (&self) -> usize { self.time_point.load(Relaxed) } fn time_pos (&self) -> usize { self.time_pos.load(Relaxed) }
fn set_time_point (&self, x: usize) { self.time_point.store(x, Relaxed) } fn set_time_pos (&self, x: usize) { self.time_pos.store(x, Relaxed) }
} }

View file

@ -11,13 +11,13 @@ pub trait MidiViewer: HasSize<TuiOut> + MidiRange + MidiPoint + Debug + Send + S
} }
/// Make sure cursor is within note range /// Make sure cursor is within note range
fn autoscroll (&self) { fn autoscroll (&self) {
let note_point = self.note_point().min(127); let note_pos = self.note_pos().min(127);
let note_lo = self.note_lo().get(); let note_lo = self.note_lo().get();
let note_hi = self.note_hi(); let note_hi = self.note_hi();
if note_point < note_lo { if note_pos < note_lo {
self.note_lo().set(note_point); self.note_lo().set(note_pos);
} else if note_point > note_hi { } else if note_pos > note_hi {
self.note_lo().set((note_lo + note_point).saturating_sub(note_hi)); self.note_lo().set((note_lo + note_pos).saturating_sub(note_hi));
} }
} }
/// Make sure time range is within display /// Make sure time range is within display

View file

@ -1,5 +1,4 @@
use crate::*; use crate::*;
use super::*;
/// A clip, rendered as a horizontal piano roll. /// A clip, rendered as a horizontal piano roll.
pub struct PianoHorizontal { pub struct PianoHorizontal {
pub clip: Option<Arc<RwLock<MidiClip>>>, pub clip: Option<Arc<RwLock<MidiClip>>>,
@ -83,7 +82,7 @@ impl PianoHorizontal {
let style = Style::default().fg(clip.color.base.rgb);//.bg(Color::Rgb(0, 0, 0)); let style = Style::default().fg(clip.color.base.rgb);//.bg(Color::Rgb(0, 0, 0));
let mut notes_on = [false;128]; let mut notes_on = [false;128];
for (x, time_start) in (0..clip.length).step_by(zoom).enumerate() { for (x, time_start) in (0..clip.length).step_by(zoom).enumerate() {
for (y, note) in (0..=127).rev().enumerate() { for (_y, note) in (0..=127).rev().enumerate() {
if let Some(cell) = buf.get_mut(x, note) { if let Some(cell) = buf.get_mut(x, note) {
if notes_on[note] { if notes_on[note] {
cell.set_char('▂'); cell.set_char('▂');
@ -114,19 +113,17 @@ impl PianoHorizontal {
} }
fn notes (&self) -> impl Content<TuiOut> { fn notes (&self) -> impl Content<TuiOut> {
let time_start = self.time_start().get(); let time_start = self.time_start().get();
let note_axis = self.note_axis().get();
let note_lo = self.note_lo().get(); let note_lo = self.note_lo().get();
let note_hi = self.note_hi(); let note_hi = self.note_hi();
let note_point = self.note_point();
let buffer = self.buffer.clone(); let buffer = self.buffer.clone();
RenderThunk::new(move|to: &mut TuiOut|{ RenderThunk::new(move|to: &mut TuiOut|{
let source = buffer.read().unwrap(); let source = buffer.read().unwrap();
let [x0, y0, w, h] = to.area().xywh(); let [x0, y0, w, _h] = to.area().xywh();
//if h as usize != note_axis { //if h as usize != note_axis {
//panic!("area height mismatch: {h} <> {note_axis}"); //panic!("area height mismatch: {h} <> {note_axis}");
//} //}
for (area_x, screen_x) in (x0..x0+w).enumerate() { for (area_x, screen_x) in (x0..x0+w).enumerate() {
for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { for (area_y, screen_y, _note) in note_y_iter(note_lo, note_hi, y0) {
let source_x = time_start + area_x; let source_x = time_start + area_x;
let source_y = note_hi - area_y; let source_y = note_hi - area_y;
// TODO: enable loop rollover: // TODO: enable loop rollover:
@ -150,19 +147,19 @@ impl PianoHorizontal {
let note_hi = self.note_hi(); let note_hi = self.note_hi();
let note_len = self.note_len(); let note_len = self.note_len();
let note_lo = self.note_lo().get(); let note_lo = self.note_lo().get();
let note_point = self.note_point(); let note_pos = self.note_pos();
let time_point = self.time_point(); let time_pos = self.time_pos();
let time_start = self.time_start().get(); let time_start = self.time_start().get();
let time_zoom = self.time_zoom().get(); let time_zoom = self.time_zoom().get();
RenderThunk::new(move|to: &mut TuiOut|{ RenderThunk::new(move|to: &mut TuiOut|{
let [x0, y0, w, _] = to.area().xywh(); let [x0, y0, w, _] = to.area().xywh();
for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
if note == note_point { if note == note_pos {
for x in 0..w { for x in 0..w {
let screen_x = x0 + x; let screen_x = x0 + x;
let time_1 = time_start + x as usize * time_zoom; let time_1 = time_start + x as usize * time_zoom;
let time_2 = time_1 + time_zoom; let time_2 = time_1 + time_zoom;
if time_1 <= time_point && time_point < time_2 { if time_1 <= time_pos && time_pos < time_2 {
to.blit(&"", screen_x, screen_y, style); to.blit(&"", screen_x, screen_y, style);
let tail = note_len as u16 / time_zoom as u16; let tail = note_len as u16 / time_zoom as u16;
for x_tail in (screen_x + 1)..(screen_x + tail) { for x_tail in (screen_x + 1)..(screen_x + tail) {
@ -181,18 +178,18 @@ impl PianoHorizontal {
let color = state.color; let color = state.color;
let note_lo = state.note_lo().get(); let note_lo = state.note_lo().get();
let note_hi = state.note_hi(); let note_hi = state.note_hi();
let note_point = state.note_point(); let note_pos = state.note_pos();
let key_style = Some(Style::default().fg(Color::Rgb(192, 192, 192)).bg(Color::Rgb(0, 0, 0))); let key_style = Some(Style::default().fg(Color::Rgb(192, 192, 192)).bg(Color::Rgb(0, 0, 0)));
let off_style = Some(Style::default().fg(TuiTheme::g(160))); let off_style = Some(Style::default().fg(TuiTheme::g(160)));
let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.base.rgb).bold()); let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.base.rgb).bold());
Fill::y(Fixed::x(self.keys_width, RenderThunk::new(move|to: &mut TuiOut|{ Fill::y(Fixed::x(self.keys_width, RenderThunk::new(move|to: &mut TuiOut|{
let [x, y0, w, h] = to.area().xywh(); let [x, y0, _w, _h] = to.area().xywh();
for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
to.blit(&to_key(note), x, screen_y, key_style); to.blit(&to_key(note), x, screen_y, key_style);
if note > 127 { if note > 127 {
continue continue
} }
if note == note_point { if note == note_pos {
to.blit(&format!("{:<5}", Note::pitch_to_name(note)), x, screen_y, on_style) to.blit(&format!("{:<5}", Note::pitch_to_name(note)), x, screen_y, on_style)
} else { } else {
to.blit(&Note::pitch_to_name(note), x, screen_y, off_style) to.blit(&Note::pitch_to_name(note), x, screen_y, off_style)
@ -202,7 +199,7 @@ impl PianoHorizontal {
} }
fn timeline (&self) -> impl Content<TuiOut> + '_ { fn timeline (&self) -> impl Content<TuiOut> + '_ {
Fill::x(Fixed::y(1, RenderThunk::new(move|to: &mut TuiOut|{ Fill::x(Fixed::y(1, RenderThunk::new(move|to: &mut TuiOut|{
let [x, y, w, h] = to.area(); let [x, y, w, _h] = to.area();
let style = Some(Style::default().dim()); let style = Some(Style::default().dim());
let length = self.clip.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); let length = self.clip.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) { for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) {
@ -231,12 +228,12 @@ impl NoteRange for PianoHorizontal {
impl NotePoint for PianoHorizontal { impl NotePoint for PianoHorizontal {
fn note_len (&self) -> usize { self.point.note_len() } fn note_len (&self) -> usize { self.point.note_len() }
fn set_note_len (&self, x: usize) { self.point.set_note_len(x) } fn set_note_len (&self, x: usize) { self.point.set_note_len(x) }
fn note_point (&self) -> usize { self.point.note_point() } fn note_pos (&self) -> usize { self.point.note_pos() }
fn set_note_point (&self, x: usize) { self.point.set_note_point(x) } fn set_note_pos (&self, x: usize) { self.point.set_note_pos(x) }
} }
impl TimePoint for PianoHorizontal { impl TimePoint for PianoHorizontal {
fn time_point (&self) -> usize { self.point.time_point() } fn time_pos (&self) -> usize { self.point.time_pos() }
fn set_time_point (&self, x: usize) { self.point.set_time_point(x) } fn set_time_pos (&self, x: usize) { self.point.set_time_pos(x) }
} }
impl MidiViewer for PianoHorizontal { impl MidiViewer for PianoHorizontal {
fn clip (&self) -> &Option<Arc<RwLock<MidiClip>>> { fn clip (&self) -> &Option<Arc<RwLock<MidiClip>>> {

View file

@ -44,8 +44,8 @@ command!(|self:FileBrowserCommand,state:SamplerTui|match self {
//_ => match input { //_ => match input {
//// load sample //// load sample
//kpat!(Shift-Char('L')) => Self::Import(FileBrowserCommand::Begin), //kpat!(Shift-Char('L')) => Self::Import(FileBrowserCommand::Begin),
//kpat!(KeyCode::Up) => Self::Select(state.note_point().overflowing_add(1).0.min(127)), //kpat!(KeyCode::Up) => Self::Select(state.note_pos().overflowing_add(1).0.min(127)),
//kpat!(KeyCode::Down) => Self::Select(state.note_point().overflowing_sub(1).0.min(127)), //kpat!(KeyCode::Down) => Self::Select(state.note_pos().overflowing_sub(1).0.min(127)),
//_ => return None //_ => return None
//} //}
//}); //});
@ -58,8 +58,8 @@ command!(|self: SamplerTuiCommand, state: SamplerTui|match self {
None None
}, },
Self::Select(index) => { Self::Select(index) => {
let old = state.note_point(); let old = state.note_pos();
state.set_note_point(index); state.set_note_pos(index);
Some(Self::Select(old)) Some(Self::Select(old))
}, },
Self::Sample(cmd) => cmd.execute(&mut state.state)?.map(Self::Sample), Self::Sample(cmd) => cmd.execute(&mut state.state)?.map(Self::Sample),

View file

@ -42,7 +42,7 @@ content!(TuiOut: |self: SamplerTui| {
Fill::xy(SamplesTui { Fill::xy(SamplesTui {
color: self.color, color: self.color,
note_hi: self.note_hi(), note_hi: self.note_hi(),
note_pt: self.note_point(), note_pt: self.note_pos(),
height: self.size.h(), height: self.size.h(),
}), }),
))), ))),
@ -74,14 +74,14 @@ impl NoteRange for SamplerTui {
impl NotePoint for SamplerTui { impl NotePoint for SamplerTui {
fn note_len (&self) -> usize {0/*TODO*/} fn note_len (&self) -> usize {0/*TODO*/}
fn set_note_len (&self, x: usize) {} fn set_note_len (&self, x: usize) {}
fn note_point (&self) -> usize { self.note_pt.load(Relaxed) } fn note_pos (&self) -> usize { self.note_pt.load(Relaxed) }
fn set_note_point (&self, x: usize) { self.note_pt.store(x, Relaxed); } fn set_note_pos (&self, x: usize) { self.note_pt.store(x, Relaxed); }
} }
impl Sampler { impl Sampler {
const EMPTY: &[(f64, f64)] = &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)]; const EMPTY: &[(f64, f64)] = &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)];
pub fn list <'a> (&'a self, compact: bool, editor: &MidiEditor) -> impl Content<TuiOut> + 'a { pub fn list <'a> (&'a self, compact: bool, editor: &MidiEditor) -> impl Content<TuiOut> + 'a {
let note_lo = editor.note_lo().load(Relaxed); let note_lo = editor.note_lo().load(Relaxed);
let note_pt = editor.note_point(); let note_pt = editor.note_pos();
let note_hi = editor.note_hi(); let note_hi = editor.note_hi();
Outer(Style::default().fg(TuiTheme::g(96))).enclose(Map::new(move||(note_lo..=note_hi).rev(), move|note, i| { Outer(Style::default().fg(TuiTheme::g(96))).enclose(Map::new(move||(note_lo..=note_hi).rev(), move|note, i| {
let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a)))); let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a))));

View file

@ -144,7 +144,7 @@ has_clips!(|self: Tek|self.pool.as_ref().expect("no clip pool").clips);
has_jack!(|self: Tek|&self.jack); has_jack!(|self: Tek|&self.jack);
has_sampler!(|self: Tek|{ has_sampler!(|self: Tek|{
sampler = self.sampler; sampler = self.sampler;
index = self.editor.as_ref().map(|e|e.note_point()).unwrap_or(0); }); index = self.editor.as_ref().map(|e|e.note_pos()).unwrap_or(0); });
has_editor!(|self: Tek|{ has_editor!(|self: Tek|{
editor = self.editor; editor = self.editor;
editor_w = { editor_w = {
@ -502,20 +502,23 @@ impl Tek {
} else { } else {
("".to_string(), TuiTheme::g(64), TuiTheme::g(32)) ("".to_string(), TuiTheme::g(64), TuiTheme::g(32))
}; };
let selected = selected_scene == Some(s) && selected_track == Some(t);
let active = editing && selected;
let label = move||Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string())));
let neighbor = selected_track == Some(t) && selected_scene.map(|s|s+1) == Some(s);
map_south(y1 as u16, h, Push::y(1, Fixed::y(h, Either::new( map_south(y1 as u16, h, Push::y(1, Fixed::y(h, Either::new(
editing && selected_scene == Some(s) && selected_track == Some(t), active,
Thunk::new(||self.editor()), Thunk::new(||self.editor()),
Thunk::new(move||phat_sel_3( Thunk::new(move||Bsp::a(
selected_track == Some(t) && selected_scene == Some(s), When::new(selected, Fill::y(Align::n(button(" Tab ".into(), "edit".into())))),
Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))), phat_sel_3(
Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))), selected,
if selected_track == Some(t) && selected_scene.map(|s|s+1) == Some(s) { label(),
None label(),
} else { if neighbor { None } else { Some(bg.into()) },
Some(bg.into()) bg.into(),
}, bg.into(),
bg.into(), )
bg.into(),
)), )),
)))) ))))
}))).boxed() }))).boxed()
@ -1108,14 +1111,14 @@ audio!(|self: Tek, client, scope|{
match event { match event {
(time, Ok(LiveEvent::Midi {message, ..})) => match message { (time, Ok(LiveEvent::Midi {message, ..})) => match message {
MidiMessage::NoteOn {ref key, ..} if let Some(editor) = self.editor.as_ref() => { MidiMessage::NoteOn {ref key, ..} if let Some(editor) = self.editor.as_ref() => {
editor.set_note_point(key.as_int() as usize); editor.set_note_pos(key.as_int() as usize);
}, },
MidiMessage::Controller {controller, value} if let (Some(editor), Some(sampler)) = ( MidiMessage::Controller {controller, value} if let (Some(editor), Some(sampler)) = (
self.editor.as_ref(), self.editor.as_ref(),
self.sampler.as_ref(), self.sampler.as_ref(),
) => { ) => {
// TODO: give sampler its own cursor // TODO: give sampler its own cursor
if let Some(sample) = &sampler.mapped[editor.note_point()] { if let Some(sample) = &sampler.mapped[editor.note_pos()] {
sample.write().unwrap().handle_cc(*controller, *value) sample.write().unwrap().handle_cc(*controller, *value)
} }
} }