editor keycodes work

This commit is contained in:
🪞👃🪞 2025-01-16 15:43:44 +01:00
parent 525923d057
commit fc3ecfb241
4 changed files with 94 additions and 86 deletions

View file

@ -1,20 +1,22 @@
(:enter add :false)
(:shift-enter add :true)
(:del del :false)
(:shift-del del :true)
(:comma note/length :prev-note-length)
(:period note/length :next-note-length)
(:plus note/range :next-note-range)
(:underscore note/range :prev-note-range)
(:up note/point :next-note-point)
(:down note/point :prev-note-point)
(:left time/point :next-time-point)
(:right time/point :prev-time-point)
(:z time/lock :next-time-lock)
(:equal time/zoom :next-time-zoom)
(:minus time/zoom :prev-time-zoom)
(:space clock/play :play-current)
(:shift-space clock/play :play-start)
(:u history :history-prev)
(:r history :history-next)
(:tab compact :next-compact)
(@up note/pos :note-pos-next)
(@down note/pos :note-pos-prev)
(@left time/pos :time-pos-next)
(@right time/pos :time-pos-prev)
(@enter add :false)
(@shift-enter add :true)
(@del del :false)
(@shift-del del :true)
(@comma note/length :prev-note-length)
(@period note/length :next-note-length)
(@plus note/range :next-note-range)
(@underscore note/range :prev-note-range)
(@z time/lock :next-time-lock)
(@equal time/zoom :next-time-zoom)
(@minus time/zoom :prev-time-zoom)
(@space clock/play :play-current)
(@shift-space clock/play :play-start)
(@u history :history-prev)
(@r history :history-next)
(@tab compact :next-compact)

View file

@ -56,10 +56,17 @@ edn_provide!(bool: |self: MidiEditor| {
":false" => false
});
edn_provide!(usize: |self: MidiEditor| {
":note-length" => self.note_len(),
":note-point" => self.note_point(),
":time-point" => self.time_point(),
":time-zoom" => self.time_zoom().get(),
":note-length" => self.note_len(),
":note-pos" => self.note_point(),
":note-pos-next" => self.note_point() + 1,
":note-pos-prev" => self.note_point().saturating_sub(1),
":time-pos" => self.time_point(),
":time-pos-next" => self.time_point() + self.time_zoom().get(),
":time-pos-prev" => self.time_point().saturating_sub(self.time_zoom().get()),
":time-zoom" => self.time_zoom().get(),
});
impl std::fmt::Debug for MidiEditor {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {

View file

@ -5,4 +5,4 @@
(@e editor show :pool-clip)
(@ctrl-a scene add)
(@ctrl-t track add)
(@tab compact)
(@tab edit)

View file

@ -5,7 +5,7 @@
#![feature(if_let_guard)]
#![feature(impl_trait_in_assoc_type)]
#![feature(type_alias_impl_trait)]
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::{self, *}}};
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::Relaxed}};
/// Standard result type.
pub type Usually<T> = std::result::Result<T, Box<dyn std::error::Error>>;
/// Standard optional result type.
@ -119,7 +119,6 @@ impl TekCli {
pub edn: String,
pub clock: Clock,
pub color: ItemPalette,
pub editing: AtomicBool,
pub pool: Option<MidiPool>,
pub editor: Option<MidiEditor>,
pub player: Option<MidiPlayer>,
@ -136,7 +135,7 @@ impl TekCli {
pub splits: Vec<u16>,
pub size: Measure<TuiOut>,
pub perf: PerfModel,
pub compact: bool,
pub editing: AtomicBool,
pub history: Vec<TekCommand>,
}
has_size!(<TuiOut>|self: Tek|&self.size);
@ -174,10 +173,10 @@ edn_view!(TuiOut: |self: Tek| self.size.of(EdnView::from_source(self, self.edn.a
// Provide sizes:
u16 {
":sidebar-w" => self.sidebar_w(),
":sample-h" => if self.compact() { 0 } else { 5 },
":samples-w" => if self.compact() { 4 } else { 11 },
":samples-y" => if self.compact() { 1 } else { 0 },
":pool-w" => if self.compact() { 5 } else {
":sample-h" => if self.is_editing() { 0 } else { 5 },
":samples-w" => if self.is_editing() { 4 } else { 11 },
":samples-y" => if self.is_editing() { 1 } else { 0 },
":pool-w" => if self.is_editing() { 5 } else {
let w = self.size.w();
if w > 60 { 20 } else if w > 40 { 15 } else { 10 } } };
// Provide components:
@ -193,7 +192,7 @@ edn_view!(TuiOut: |self: Tek| self.size.of(EdnView::from_source(self, self.edn.a
":outputs" => self.view_row(self.w(), 3, self.output_header(), self.output_cells()).boxed(),
":scenes" => Outer(Style::default().fg(TuiTheme::g(0))).enclose_bg(self.view_row(
self.w(), self.size.h().saturating_sub(12) as u16,
self.scene_header(), self.scene_cells()
self.scene_header(), self.clip_columns()
)).boxed() }});
impl Tek {
fn new_arranger (
@ -289,8 +288,6 @@ impl Tek {
fn sync_follow (&self) {
// TODO
}
fn compact (&self) -> bool { self.compact }
fn is_editing (&self) -> bool { self.editing.load(Relaxed) }
fn editor (&self) -> impl Content<TuiOut> + '_ { &self.editor }
fn view_clock (&self) -> impl Content<TuiOut> + use<'_> {
Outer(Style::default().fg(TuiTheme::g(0))).enclose(row!(
@ -300,14 +297,14 @@ impl Tek {
))
}
fn view_beat_stats (&self) -> impl Content<TuiOut> + use<'_> {
let Self { compact, ref clock, .. } = self;
let clock = self.clock();
let now = clock.started.read().unwrap().as_ref().map(|start|clock.global.usec.get() - start.usec.get());
let beat = ||now.map(|now|clock.timebase.format_beats_1(clock.timebase.usecs_to_pulse(now)))
.unwrap_or("-.-.--".into());
let time = ||now.map(|now|format!("{:.3}s", now/1000000.))
.unwrap_or("-.---s".into());
let bpm = ||format!("{:.3}", clock.timebase.bpm.get());
Either::new(self.compact,
Either::new(self.is_editing(),
row!(FieldV(TuiTheme::g(128).into(), "BPM", bpm()),
FieldV(TuiTheme::g(128).into(), "Beat", beat()),
FieldV(TuiTheme::g(128).into(), "Time", time()),),
@ -316,13 +313,14 @@ impl Tek {
Bsp::e("Time ", Tui::fg(TuiTheme::g(255), time()))))
}
fn view_engine_stats (&self) -> impl Content<TuiOut> + use<'_> {
let Self { compact, ref clock, .. } = self;
let clock = self.clock();
let compact = self.is_editing();
let rate = clock.timebase.sr.get();
let chunk = clock.chunk.load(Relaxed);
let sr = move||format!("{}", if *compact {format!("{:.1}kHz", rate / 1000.)} else {format!("{:.0}Hz", rate)});
let sr = move||format!("{}", if compact {format!("{:.1}kHz", rate / 1000.)} else {format!("{:.0}Hz", rate)});
let buffer_size = move||format!("{chunk}");
let latency = move||format!("{:.1}ms", chunk as f64 / rate * 1000.);
Either::new(self.compact,
Either::new(compact,
row!(FieldV(TuiTheme::g(128).into(), "SR", sr()),
FieldV(TuiTheme::g(128).into(), "Buf", buffer_size()),
FieldV(TuiTheme::g(128).into(), "Lat", latency())),
@ -360,7 +358,7 @@ impl Tek {
let playing = self.clock.is_rolling();
Tui::bg(
if playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
Either::new(self.compact,
Either::new(self.is_editing(),
Thunk::new(move||Fixed::x(9, Either::new(playing,
Tui::fg(Color::Rgb(0, 255, 0), " PLAYING "),
Tui::fg(Color::Rgb(255, 128, 0), " STOPPED ")))),
@ -372,11 +370,11 @@ impl Tek {
self.editor.as_ref().map(|e|Bsp::e(e.clip_status(), e.edit_status()))
}
fn view_pool (&self) -> impl Content<TuiOut> + use<'_> {
self.pool.as_ref().map(|pool|PoolView(self.compact(), pool))
self.pool.as_ref().map(|pool|PoolView(self.is_editing(), pool))
}
fn pool (&self) -> impl Content<TuiOut> + use<'_> {
self.pool.as_ref()
.map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool))))
.map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.is_editing(), pool))))
}
fn w (&self) -> u16 {
self.tracks_sizes(self.is_editing(), self.editor_w())
@ -406,6 +404,12 @@ impl Tek {
clip.write().unwrap().toggle_loop()
}
}
fn track_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(||Tui::bg(TuiTheme::g(32), Bsp::s(
Fill::x(Align::w(button(" C-a ".to_string(), format!(" add scene ({})", self.scenes().len())))),
Fill::x(Align::w(button(" C-t ".to_string(), format!(" add track ({})", self.tracks().len())))),
)).boxed()).into()
}
fn track_add (
&mut self,
name: Option<&str>,
@ -478,7 +482,7 @@ impl Tek {
}
Ok(())
}
fn scene_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
fn clip_columns <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let editing = self.is_editing();
let tracks = move||self.tracks_sizes(editing, self.editor_w());
let scenes = move||self.scenes_sizes(editing, 2, 15);
@ -498,24 +502,22 @@ impl Tek {
} else {
("".to_string(), TuiTheme::g(64), TuiTheme::g(32))
};
let last = last_color.read().unwrap().clone();
let active = editing && selected_scene == Some(s) && selected_track == Some(t);
let editor = Thunk::new(||self.editor());
let cell = Thunk::new(move||phat_sel_3(
selected_track == Some(t) && selected_scene == Some(s),
Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))),
Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))),
if selected_track == Some(t) && selected_scene.map(|s|s+1) == Some(s) {
None
} else {
Some(bg.into())
},
bg.into(),
bg.into(),
));
let cell = Either::new(active, editor, cell);
*last_color.write().unwrap() = bg.into();
map_south(y1 as u16, h, Push::y(1, Fixed::y(h, cell)))
map_south(y1 as u16, h, Push::y(1, Fixed::y(h, Either::new(
editing && selected_scene == Some(s) && selected_track == Some(t),
Thunk::new(||self.editor()),
Thunk::new(move||phat_sel_3(
selected_track == Some(t) && selected_scene == Some(s),
Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))),
Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))),
if selected_track == Some(t) && selected_scene.map(|s|s+1) == Some(s) {
None
} else {
Some(bg.into())
},
bg.into(),
bg.into(),
)),
))))
}))).boxed()
})).boxed()).into()
}
@ -553,6 +555,12 @@ const KEYS_MIX: &str = include_str!("keys_mix.edn");
handle!(TuiIn: |self: Tek, input|Ok({
use EdnItem::*;
let mut command: Option<TekCommand> = None;
// If editing, editor keys take priority
if self.is_editing() {
if self.editor.handle(input)? == Some(true) {
return Ok(Some(true))
}
}
// Handle from root keymap
if let Some(command) = EdnKeyMapToCommand::new(KEYS_APP)?.from::<_, TekCommand>(self, input) {
if let Some(undo) = command.execute(self)? { self.history.push(undo); }
@ -576,7 +584,7 @@ handle!(TuiIn: |self: Tek, input|Ok({
Clip(ClipCommand),
Clock(ClockCommand),
Color(ItemPalette),
Compact(Option<bool>),
Edit(Option<bool>),
Editor(MidiEditCommand),
Enqueue(Option<Arc<RwLock<MidiClip>>>),
History(isize),
@ -589,8 +597,8 @@ handle!(TuiIn: |self: Tek, input|Ok({
Zoom(Option<usize>),
}
edn_command!(TekCommand: |app: Tek| {
("compact" [] Self::Compact(None))
("compact" [c: bool] Self::Compact(c))
("edit" [] Self::Edit(None))
("edit" [c: bool] Self::Edit(c))
("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default()))
@ -661,19 +669,16 @@ command!(|self: TekCommand, app: Tek|match self {
} else {
None
},
Self::Compact(compact) => match compact {
Some(compact) => {
if app.compact != compact {
app.compact = compact;
Some(Self::Compact(Some(!compact)))
} else {
None
}
},
None => {
app.compact = !app.compact;
Some(Self::Compact(Some(!app.compact)))
Self::Edit(value) => if let Some(value) = value {
if app.is_editing() != value {
app.editing.store(value, Relaxed);
Some(Self::Edit(Some(!value)))
} else {
None
}
} else {
app.editing.store(!app.is_editing(), Relaxed);
Some(Self::Edit(Some(!app.is_editing())))
}
});
/// Represents the current user selection in the arranger
@ -817,12 +822,6 @@ trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
fn track_mut (&mut self) -> Option<&mut Track> {
self.selected().track().and_then(|s|self.tracks_mut().get_mut(s))
}
fn track_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(||Tui::bg(TuiTheme::g(32), Bsp::s(
button(" C-t ", " add track "),
button(" C-a ", " add scene "),
)).boxed()).into()
}
fn track_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let iter = ||self.tracks_sizes(self.is_editing(), self.editor_w());
(move||Align::x(Map::new(iter, move|(_, track, x1, x2), i| {
@ -841,7 +840,7 @@ trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
fn input_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let fg = TuiTheme::g(224);
let bg = TuiTheme::g(64);
(move||Bsp::s(button(" I ", " midi ins "), self.midi_ins().get(0).map(|inp|Bsp::s(
(move||Bsp::s(Fill::x(Align::w(button(" I ".to_string(), format!(" midi ins ({})", self.midi_ins().len())))), self.midi_ins().get(0).map(|inp|Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(inp.name.clone())))),
inp.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
Tui::fg_bg(fg, bg, connect.info()))))),
@ -869,7 +868,7 @@ trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
fn output_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let fg = TuiTheme::g(224);
let bg = TuiTheme::g(64);
(move||Bsp::s(button(" O ", " midi outs "), self.midi_outs().get(0).map(|out|Bsp::s(
(move||Bsp::s(Fill::x(Align::w(button(" O ".to_string(), format!(" midi outs ({}) ", self.midi_outs().len())))), self.midi_outs().get(0).map(|out|Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(out.name.clone())))),
out.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
Tui::fg_bg(fg, bg, connect.info()))))),
@ -1138,7 +1137,7 @@ audio!(|self: Tek, client, scope|{
self.perf.update(t0, scope);
Control::Continue
});
fn button <'a> (key: &'a str, label: &'a str) -> impl Content<TuiOut> + 'a {
fn button (key: String, label: String) -> impl Content<TuiOut> {
Tui::bold(true, Bsp::e(
Margin::x(1, Tui::fg_bg(TuiTheme::g(0), TuiTheme::orange(), key)),
Margin::x(1, Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(96), label)),