From d9f1875c03bc21504bbaefb52978f750a06e8707 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 11 Jan 2025 15:48:03 +0100 Subject: [PATCH] wip: move tracks to bottom --- README.md | 16 +- cli/tek.rs | 2 +- tek/src/arranger.edn | 2 +- tek/src/arranger.rs | 327 ++++++++++++++++----------- tek/src/arranger/arranger_command.rs | 247 -------------------- tek/src/arranger/arranger_track.rs | 4 +- 6 files changed, 212 insertions(+), 386 deletions(-) delete mode 100644 tek/src/arranger/arranger_command.rs diff --git a/README.md b/README.md index f108a437..6d5f471a 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,21 @@ Options: -V, --version Print version ``` -the project roadmap is at https://codeberg.org/unspeaker/tek/milestones +## keymaps -## getting started +* [x] `arrows/wasd`: navigate +* [x] `tab`: toggle editor +* [x] `enter`: write note +* [x] `z`: unlock zoom +* [x] `-` / `=`: zoom midi editor +* [x] space: play/pause +* [ ] esc: options +* [ ] f1: help/command list +* [ ] f2: rename +* [ ] f6: save +* [ ] f9: load + +## installation * **requirements:** linux; jack or pipewire; 24-bit terminal (i use `kitty`) * **recommended:** midi controller; samples in wav format; lv2 plugins. diff --git a/cli/tek.rs b/cli/tek.rs index 109d8a04..05e4d51f 100644 --- a/cli/tek.rs +++ b/cli/tek.rs @@ -52,7 +52,7 @@ pub enum TekMode { /// Number of tracks #[arg(short = 'x', long, default_value_t = 1)] tracks: usize, /// Width of tracks - #[arg(short = 'w', long, default_value_t = 8)] track_width: usize, + #[arg(short = 'w', long, default_value_t = 9)] track_width: usize, }, /// TODO: A MIDI-controlled audio mixer Mixer, diff --git a/tek/src/arranger.edn b/tek/src/arranger.edn index 3cf6b1c3..1d1ac948 100644 --- a/tek/src/arranger.edn +++ b/tek/src/arranger.edn @@ -1,3 +1,3 @@ (bsp/s :toolbar (fill/x (align/c (bsp/w :pool - (bsp/n :inputs (bsp/s :outputs (bsp/s :tracks :scenes))))))) + (bsp/n :outputs (bsp/n :inputs (bsp/n :tracks :scenes))))))) diff --git a/tek/src/arranger.rs b/tek/src/arranger.rs index 781615dd..cd6d6d05 100644 --- a/tek/src/arranger.rs +++ b/tek/src/arranger.rs @@ -25,8 +25,6 @@ pub struct Arranger { pub perf: PerfModel, pub compact: bool, } -pub(crate) const HEADER_H: u16 = 0; // 5 -pub(crate) const SCENES_W_OFFSET: u16 = 0; render!(TuiOut: (self: Arranger) => self.size.of(EdnView::from_source(self, Self::EDN))); impl EdnViewData for &Arranger { fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> { @@ -79,7 +77,9 @@ impl Arranger { //self.editor.time_axis().get().max(16) //50 } - pub fn scenes_with_sizes (&self, h: usize) -> impl Iterator { + pub fn scenes_with_sizes (&self, h: usize) + -> impl Iterator + { let mut y = 0; let editing = self.is_editing(); let (selected_track, selected_scene) = match self.selected { @@ -115,14 +115,14 @@ impl Arranger { fn play_row (&self, tracks_w: u16) -> impl Content + '_ { let h = 2; Fixed::y(h, Bsp::e( - Fixed::xy(self.sidebar_w() as u16, h, self.play_row_header()), - Fill::x(Align::c(Fixed::xy(tracks_w, h, self.play_row_cells()))) + Fixed::xy(self.sidebar_w() as u16, h, self.play_header()), + Fill::x(Align::c(Fixed::xy(tracks_w, h, self.play_cells()))) )) } - fn play_row_header (&self) -> BoxThunk { + fn play_header (&self) -> BoxThunk { (||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Playing")).boxed()).into() } - fn play_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + fn play_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { (move||Align::x(Map::new(||self.tracks_with_sizes(), move|(_, track, x1, x2), i| { //let color = track.color(); let color: ItemPalette = track.color().dark.into(); @@ -143,14 +143,14 @@ impl Arranger { fn next_row (&self, tracks_w: u16) -> impl Content + '_ { let h = 2; Fixed::y(h, Bsp::e( - Fixed::xy(self.sidebar_w() as u16, h, self.next_row_header()), - Fill::x(Align::c(Fixed::xy(tracks_w, h, self.next_row_cells()))) + Fixed::xy(self.sidebar_w() as u16, h, self.next_header()), + Fill::x(Align::c(Fixed::xy(tracks_w, h, self.next_cells()))) )) } - fn next_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + fn next_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { (||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Next")).boxed()).into() } - fn next_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + fn next_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { (move||Align::x(Map::new(||self.tracks_with_sizes(), move|(_, track, x1, x2), i| { let color: ItemPalette = track.color(); let color: ItemPalette = track.color().dark.into(); @@ -190,16 +190,26 @@ impl Arranger { fn track_row (&self, tracks_w: u16) -> impl Content + '_ { let h = 3; - let border = |x|Skinny(Style::default().fg(Color::Rgb(0,0,0)).bg(Color::Reset)).enclose2(x); + let border = |x|x;//Rugged(Style::default().fg(Color::Rgb(0,0,0)).bg(Color::Reset)).enclose2(x); Fixed::y(h, Bsp::e( - Fixed::xy(self.sidebar_w() as u16, h, self.track_row_header()), - Fill::x(Align::c(Fixed::xy(tracks_w, h, border(self.track_row_cells())))) + Fixed::xy(self.sidebar_w() as u16, h, self.track_header()), + Fill::x(Align::c(Fixed::xy(tracks_w, h, border(self.track_cells())))) )) } - fn track_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { - (||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Track")).boxed()).into() + fn track_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + (||Tui::bold(true, Bsp::s( + row!( + Tui::fg(TuiTheme::g(128), "add "), + Tui::fg(TuiTheme::orange(), "t"), + Tui::fg(TuiTheme::g(128), "rack"), + ), + row!( + Tui::fg(TuiTheme::orange(), "a"), + Tui::fg(TuiTheme::g(128), "dd scene"), + ), + ).boxed())).into() } - fn track_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + fn track_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { let iter = ||self.tracks_with_sizes(); (move||Align::x(Map::new(iter, move|(_, track, x1, x2), i| { let name = Push::x(1, &track.name); @@ -213,66 +223,100 @@ impl Arranger { } fn input_row (&self, tracks_w: u16) -> impl Content + '_ { - let h = 1 + self.midi_ins[0].connect.len() as u16; + let h = 2 + self.midi_ins[0].connect.len() as u16; Fixed::y(h, Bsp::e( - Fixed::xy(self.sidebar_w() as u16, h, self.input_row_header()), - Fill::x(Align::c(Fixed::xy(tracks_w, h, self.input_row_cells()))) + Fixed::xy(self.sidebar_w() as u16, h, self.input_header()), + Fill::x(Align::c(Fixed::xy(tracks_w, h, self.input_cells()))) )) } - fn input_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + fn input_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { (||Bsp::s( - Fill::x(Tui::bold(true, Tui::fg_bg(TuiTheme::g(224), TuiTheme::g(64), - Align::w(&self.midi_ins[0].name)))), - self.midi_ins.get(0) - .and_then(|midi_in|midi_in.connect.get(0)) - .map(|connect|Fill::x(Align::w( - Tui::bold(false, Tui::fg_bg(TuiTheme::g(224), TuiTheme::g(64), connect.info()))))) + Tui::bold(true, row!( + Tui::fg(TuiTheme::g(128), "midi "), + Tui::fg(TuiTheme::orange(), "I"), + Tui::fg(TuiTheme::g(128), "ns"), + )), + Bsp::s( + Fill::x(Tui::bold(true, Tui::fg_bg(TuiTheme::g(224), TuiTheme::g(64), + Align::w(&self.midi_ins[0].name)))), + self.midi_ins.get(0) + .and_then(|midi_in|midi_in.connect.get(0)) + .map(|connect|Fill::x(Align::w( + Tui::bold(false, Tui::fg_bg(TuiTheme::g(224), TuiTheme::g(64), connect.info()))))) + ) ).boxed()).into() } - fn input_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + fn input_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { (move||Align::x(Map::new(||self.tracks_with_sizes(), move|(_, track, x1, x2), i| { let w = (x2 - x1) as u16; - let cell = Bsp::s("[Rec]", "[Mon]"); let color: ItemPalette = track.color().dark.into(); - map_east(x1 as u16, w, Fixed::x(w, Self::cell(color, cell))) + map_east(x1 as u16, w, Fixed::x(w, Self::cell(color, Bsp::n( + Self::rec_mon(color.base.rgb, false, false), + phat_hi(color.base.rgb, color.dark.rgb) + )))) })).boxed()).into() } + fn rec_mon (bg: Color, rec: bool, mon: bool) -> impl Content { + row!( + Tui::fg_bg(if rec { Color::Red } else { bg }, bg, "▐"), + Tui::fg_bg(if rec { Color::White } else { Color::Rgb(0,0,0) }, bg, "REC"), + Tui::fg_bg(if rec { Color::White } else { bg }, bg, "▐"), + Tui::fg_bg(if mon { Color::White } else { Color::Rgb(0,0,0) }, bg, "MON"), + Tui::fg_bg(if mon { Color::White } else { bg }, bg, "▌"), + ) + } fn output_row (&self, tracks_w: u16) -> impl Content + '_ { - let h = 1 + self.midi_outs[0].connect.len() as u16; + let h = 2 + self.midi_outs[0].connect.len() as u16; Fixed::y(h, Bsp::e( - Fixed::xy(self.sidebar_w() as u16, h, self.output_row_header()), - Fill::x(Align::c(Fixed::xy(tracks_w, h, self.output_row_cells()))) + Fixed::xy(self.sidebar_w() as u16, h, self.output_header()), + Fill::x(Align::c(Fixed::xy(tracks_w, h, self.output_cells()))) )) } - fn output_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + fn output_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { (||Bsp::s( - Fill::x(Tui::bold(true, Tui::fg_bg(TuiTheme::g(224), TuiTheme::g(64), - Align::w(&self.midi_outs[0].name)))), - self.midi_outs.get(0) - .and_then(|midi_out|midi_out.connect.get(0)) - .map(|connect|Fill::x(Align::w( - Tui::bold(false, Tui::fg_bg(TuiTheme::g(224), TuiTheme::g(64), connect.info()))))) + Tui::bold(true, row!( + Tui::fg(TuiTheme::g(128), "midi "), + Tui::fg(TuiTheme::orange(), "O"), + Tui::fg(TuiTheme::g(128), "uts"), + )), + Bsp::s( + Fill::x(Tui::bold(true, Tui::fg_bg(TuiTheme::g(224), TuiTheme::g(64), + Align::w(&self.midi_outs[0].name)))), + self.midi_outs.get(0) + .and_then(|midi_out|midi_out.connect.get(0)) + .map(|connect|Fill::x(Align::w( + Tui::bold(false, Tui::fg_bg(TuiTheme::g(224), TuiTheme::g(64), connect.info()))))) + ), ).boxed()).into() } - fn output_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + fn output_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { (move||Align::x(Map::new(||self.tracks_with_sizes(), move|(_, track, x1, x2), i| { let w = (x2 - x1) as u16; let color: ItemPalette = track.color().dark.into(); - let cell = Bsp::s(format!(" M S "), phat_hi(color.dark.rgb, color.darker.rgb)); - map_east(x1 as u16, w, Fixed::x(w, Self::cell(color, cell))) + map_east(x1 as u16, w, Fixed::x(w, Self::cell(color, Bsp::n( + Self::mute_solo(color.base.rgb, false, false), + phat_hi(color.dark.rgb, color.darker.rgb) + )))) })).boxed()).into() } + fn mute_solo (bg: Color, mute: bool, solo: bool) -> impl Content { + row!( + Tui::fg_bg(if mute { Color::White } else { Color::Rgb(0,0,0) }, bg, "MUTE"), + Tui::fg_bg(if mute { Color::White } else { bg }, bg, "▐"), + Tui::fg_bg(if solo { Color::White } else { Color::Rgb(0,0,0) }, bg, "SOLO"), + ) + } fn scene_row (&self, tracks_w: u16) -> impl Content + '_ { let h = self.size.h() as u16; let border = |x|Skinny(Style::default().fg(Color::Rgb(0,0,0)).bg(Color::Reset)).enclose2(x); Fill::y(Bsp::e( - Tui::bg(Color::Reset, Fixed::x(self.sidebar_w() as u16, self.scene_row_headers())), - Tui::bg(Color::Reset, Fill::x(Align::c(Fixed::xy(tracks_w, h, border(self.scene_row_cells()))))) + Tui::bg(Color::Reset, Fixed::x(self.sidebar_w() as u16, self.scene_headers())), + Tui::bg(Color::Reset, Fill::x(Align::c(Fixed::xy(tracks_w, h, border(self.scene_cells()))))) )) } - fn scene_row_headers <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + fn scene_headers <'a> (&'a self) -> BoxThunk<'a, TuiOut> { (||{ let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0)))); let selected_scene = match self.selected { @@ -300,7 +344,7 @@ impl Arranger { }))).boxed() }).into() } - fn scene_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + fn scene_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { let editing = self.is_editing(); let (selected_track, selected_scene) = match self.selected { ArrangerSelection::Clip(t, s) => (Some(t), Some(s)), @@ -574,12 +618,6 @@ has_clips!(|self: Arranger|self.pool.clips); has_editor!(|self: Arranger|self.editor); handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event())); impl Arranger { - pub fn selected (&self) -> ArrangerSelection { - self.selected - } - pub fn selected_mut (&mut self) -> &mut ArrangerSelection { - &mut self.selected - } pub fn activate (&mut self) -> Usually<()> { if let ArrangerSelection::Scene(s) = self.selected { for (t, track) in self.tracks.iter_mut().enumerate() { @@ -666,68 +704,10 @@ keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand { use ArrangerClipCommand as Clip; let t_len = state.tracks.len(); let s_len = state.scenes.len(); - match state.selected() { - Selected::Clip(t, s) => match input { - kpat!(Char('g')) => Some(Cmd::Phrases(PoolCommand::Select(0))), - kpat!(Char('q')) => Some(Cmd::Clip(Clip::Enqueue(t, s))), - kpat!(Char(',')) => Some(Cmd::Clip(Clip::Put(t, s, None))), - kpat!(Char('.')) => Some(Cmd::Clip(Clip::Put(t, s, None))), - kpat!(Char('<')) => Some(Cmd::Clip(Clip::Put(t, s, None))), - kpat!(Char('>')) => Some(Cmd::Clip(Clip::Put(t, s, None))), - kpat!(Char('p')) => Some(Cmd::Clip(Clip::Put(t, s, Some(state.pool.clip().clone())))), - kpat!(Char('l')) => Some(Cmd::Clip(ArrangerClipCommand::SetLoop(t, s, false))), - kpat!(Delete) => Some(Cmd::Clip(Clip::Put(t, s, None))), - - kpat!(Up) => Some(Cmd::Select( - if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) })), - kpat!(Down) => Some(Cmd::Select( - Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1))))), - kpat!(Left) => Some(Cmd::Select( - if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) })), - kpat!(Right) => Some(Cmd::Select( - Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s))), - - _ => None - }, - Selected::Scene(s) => match input { - kpat!(Char(',')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))), - kpat!(Char('.')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))), - kpat!(Char('<')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))), - kpat!(Char('>')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))), - kpat!(Char('q')) => Some(Cmd::Scene(Scene::Enqueue(s))), - kpat!(Delete) => Some(Cmd::Scene(Scene::Delete(s))), - kpat!(Char('c')) => Some(Cmd::Scene(Scene::SetColor(s, ItemPalette::random()))), - - kpat!(Up) => Some( - Cmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix })), - kpat!(Down) => Some( - Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1))))), - kpat!(Left) => - return None, - kpat!(Right) => Some( - Cmd::Select(Selected::Clip(0, s))), - - _ => None - }, - Selected::Track(t) => match input { - kpat!(Char(',')) => Some(Cmd::Track(Track::Swap(t, t - 1))), - kpat!(Char('.')) => Some(Cmd::Track(Track::Swap(t, t + 1))), - kpat!(Char('<')) => Some(Cmd::Track(Track::Swap(t, t - 1))), - kpat!(Char('>')) => Some(Cmd::Track(Track::Swap(t, t + 1))), - kpat!(Delete) => Some(Cmd::Track(Track::Delete(t))), - kpat!(Char('c')) => Some(Cmd::Track(Track::SetColor(t, ItemPalette::random()))), - - kpat!(Up) => - return None, - kpat!(Down) => Some( - Cmd::Select(Selected::Clip(t, 0))), - kpat!(Left) => Some( - Cmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix })), - kpat!(Right) => Some( - Cmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1))))), - - _ => None - }, + match state.selected { + Selected::Clip(t, s) => clip_keymap(state, input, t, s), + Selected::Scene(s) => scene_keymap(state, input, s), + Selected::Track(t) => track_keymap(state, input, t), Selected::Mix => match input { kpat!(Delete) => Some(Cmd::Clear), kpat!(Char('0')) => Some(Cmd::StopAll), @@ -753,23 +733,110 @@ keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand { None })?); +fn clip_keymap (state: &Arranger, input: &Event, t: usize, s: usize) -> Option { + use ArrangerSelection as Selected; + use ArrangerSceneCommand as Scene; + use ArrangerTrackCommand as Track; + use ArrangerClipCommand as Clip; + let t_len = state.tracks.len(); + let s_len = state.scenes.len(); + Some(match input { + kpat!(Char('g')) => Cmd::Phrases(PoolCommand::Select(0)), + kpat!(Char('q')) => Cmd::Clip(Clip::Enqueue(t, s)), + kpat!(Char('l')) => Cmd::Clip(Clip::SetLoop(t, s, false)), + kpat!(Delete) => Cmd::Clip(Clip::Put(t, s, None)), + kpat!(Char('p')) => Cmd::Clip(Clip::Put(t, s, Some(state.pool.clip().clone()))), + kpat!(Char(',')) => Cmd::Clip(Clip::Put(t, s, None)), + kpat!(Char('.')) => Cmd::Clip(Clip::Put(t, s, None)), + kpat!(Char('<')) => Cmd::Clip(Clip::Put(t, s, None)), + kpat!(Char('>')) => Cmd::Clip(Clip::Put(t, s, None)), + kpat!(Up) => + Cmd::Select(if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) }), + kpat!(Down) => + Cmd::Select(Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1)))), + kpat!(Left) => + Cmd::Select(if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) }), + kpat!(Right) => + Cmd::Select(Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s)), + _ => return None + }) +} +fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option { + use ArrangerSelection as Selected; + use ArrangerSceneCommand as Scene; + use ArrangerTrackCommand as Track; + use ArrangerClipCommand as Clip; + let t_len = state.tracks.len(); + let s_len = state.scenes.len(); + Some(match input { + kpat!(Char(',')) => Cmd::Scene(Scene::Swap(s, s - 1)), + kpat!(Char('.')) => Cmd::Scene(Scene::Swap(s, s + 1)), + kpat!(Char('<')) => Cmd::Scene(Scene::Swap(s, s - 1)), + kpat!(Char('>')) => Cmd::Scene(Scene::Swap(s, s + 1)), + kpat!(Char('q')) => Cmd::Scene(Scene::Enqueue(s)), + kpat!(Delete) => Cmd::Scene(Scene::Delete(s)), + kpat!(Char('c')) => Cmd::Scene(Scene::SetColor(s, ItemPalette::random())), + + kpat!(Up) => + Cmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix }), + kpat!(Down) => + Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1)))), + kpat!(Left) => + return None, + kpat!(Right) => + Cmd::Select(Selected::Clip(0, s)), + + _ => return None + }) +} +fn track_keymap (state: &Arranger, input: &Event, t: usize) -> Option { + use ArrangerSelection as Selected; + use ArrangerSceneCommand as Scene; + use ArrangerTrackCommand as Track; + use ArrangerClipCommand as Clip; + let t_len = state.tracks.len(); + let s_len = state.scenes.len(); + Some(match input { + kpat!(Char(',')) => Cmd::Track(Track::Swap(t, t - 1)), + kpat!(Char('.')) => Cmd::Track(Track::Swap(t, t + 1)), + kpat!(Char('<')) => Cmd::Track(Track::Swap(t, t - 1)), + kpat!(Char('>')) => Cmd::Track(Track::Swap(t, t + 1)), + kpat!(Delete) => Cmd::Track(Track::Delete(t)), + kpat!(Char('c')) => Cmd::Track(Track::SetColor(t, ItemPalette::random())), + + kpat!(Up) => + return None, + kpat!(Down) => + Cmd::Select(Selected::Clip(t, 0)), + kpat!(Left) => + Cmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix }), + kpat!(Right) => + Cmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1)))), + + _ => return None + }) +} + command!(|self: ArrangerCommand, state: Arranger|match self { - Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, - Self::Clip(cmd) => cmd.delegate(state, Self::Clip)?, - Self::Scene(cmd) => cmd.delegate(state, Self::Scene)?, - Self::Track(cmd) => cmd.delegate(state, Self::Track)?, - Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?, - Self::Zoom(_) => { todo!(); }, - Self::Select(selected) => { - *state.selected_mut() = selected; + Self::Clear => { todo!() }, + Self::History(_) => { todo!() }, + Self::Zoom(_) => { todo!(); }, + Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, + Self::Clip(cmd) => cmd.delegate(state, Self::Clip)?, + Self::Scene(cmd) => cmd.delegate(state, Self::Scene)?, + Self::Track(cmd) => cmd.delegate(state, Self::Track)?, + Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?, + Self::Select(selected) => { state.selected = selected; None }, + Self::StopAll => { + for track in 0..state.tracks.len() { state.tracks[track].player.enqueue_next(None); } None }, - Self::Color(palette) => { + Self::Color(palette) => { let old = state.color; state.color = palette; Some(Self::Color(old)) }, - Self::Phrases(cmd) => { + Self::Phrases(cmd) => { match cmd { // autoselect: automatically load selected clip in editor PoolCommand::Select(_) => { @@ -786,14 +853,6 @@ command!(|self: ArrangerCommand, state: Arranger|match self { _ => cmd.delegate(&mut state.pool, Self::Phrases)? } }, - Self::History(_) => { todo!() }, - Self::StopAll => { - for track in 0..state.tracks.len() { - state.tracks[track].player.enqueue_next(None); - } - None - }, - Self::Clear => { todo!() }, }); command!(|self: ArrangerClipCommand, state: Arranger|match self { Self::Get(track, scene) => { todo!() }, @@ -818,6 +877,8 @@ command!(|self: ArrangerClipCommand, state: Arranger|match self { //scenes_w: u16, //} +//pub(crate) const HEADER_H: u16 = 0; // 5 +//pub(crate) const SCENES_W_OFFSET: u16 = 0; //from!(|args:(&Arranger, usize)|ArrangerVCursor = Self { //cols: Arranger::track_widths(&args.0.tracks), //rows: Arranger::scene_heights(&args.0.scenes, args.1), diff --git a/tek/src/arranger/arranger_command.rs b/tek/src/arranger/arranger_command.rs deleted file mode 100644 index 316e9655..00000000 --- a/tek/src/arranger/arranger_command.rs +++ /dev/null @@ -1,247 +0,0 @@ -use crate::*; -use ClockCommand::{Play, Pause}; -use ArrangerCommand as Cmd; - -#[derive(Clone, Debug)] pub enum ArrangerCommand { - History(isize), - Color(ItemPalette), - Clock(ClockCommand), - Scene(ArrangerSceneCommand), - Track(ArrangerTrackCommand), - Clip(ArrangerClipCommand), - Select(ArrangerSelection), - Zoom(usize), - Phrases(PoolCommand), - Editor(MidiEditCommand), - StopAll, - Clear, -} - -#[derive(Clone, Debug)] -pub enum ArrangerTrackCommand { - Add, - Delete(usize), - Stop(usize), - Swap(usize, usize), - SetSize(usize), - SetZoom(usize), - SetColor(usize, ItemPalette), -} - -#[derive(Clone, Debug)] -pub enum ArrangerSceneCommand { - Enqueue(usize), - Add, - Delete(usize), - Swap(usize, usize), - SetSize(usize), - SetZoom(usize), - SetColor(usize, ItemPalette), -} - -#[derive(Clone, Debug)] -pub enum ArrangerClipCommand { - Get(usize, usize), - Put(usize, usize, Option>>), - Enqueue(usize, usize), - Edit(Option>>), - SetLoop(usize, usize, bool), - SetColor(usize, usize, ItemPalette), -} - -//handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event())); -//input_to_command!(ArrangerCommand: |state: Arranger, input: Event|{KEYS_ARRANGER.handle(state, input)?}); - -keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand { - key(Char('u')) => Cmd::History(-1), - key(Char('U')) => Cmd::History(1), - // TODO: k: toggle on-screen keyboard - ctrl(key(Char('k'))) => { todo!("keyboard") }, - // Transport: Play/pause - key(Char(' ')) => Cmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }), - // Transport: Play from start or rewind to start - shift(key(Char(' '))) => Cmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }), - key(Char('e')) => Cmd::Editor(MidiEditCommand::Show(Some(state.pool.clip().clone()))), - ctrl(key(Char('a'))) => Cmd::Scene(ArrangerSceneCommand::Add), - ctrl(key(Char('t'))) => Cmd::Track(ArrangerTrackCommand::Add), - // Tab: Toggle visibility of clip pool column - key(Tab) => Cmd::Phrases(PoolCommand::Show(!state.pool.visible)), -}, { - use ArrangerSelection as Selected; - use ArrangerSceneCommand as Scene; - use ArrangerTrackCommand as Track; - use ArrangerClipCommand as Clip; - let t_len = state.tracks.len(); - let s_len = state.scenes.len(); - match state.selected() { - Selected::Clip(t, s) => match input { - kpat!(Char('g')) => Some(Cmd::Phrases(PoolCommand::Select(0))), - kpat!(Char('q')) => Some(Cmd::Clip(Clip::Enqueue(t, s))), - kpat!(Char(',')) => Some(Cmd::Clip(Clip::Put(t, s, None))), - kpat!(Char('.')) => Some(Cmd::Clip(Clip::Put(t, s, None))), - kpat!(Char('<')) => Some(Cmd::Clip(Clip::Put(t, s, None))), - kpat!(Char('>')) => Some(Cmd::Clip(Clip::Put(t, s, None))), - kpat!(Char('p')) => Some(Cmd::Clip(Clip::Put(t, s, Some(state.pool.clip().clone())))), - kpat!(Char('l')) => Some(Cmd::Clip(ArrangerClipCommand::SetLoop(t, s, false))), - kpat!(Delete) => Some(Cmd::Clip(Clip::Put(t, s, None))), - - kpat!(Up) => Some(Cmd::Select( - if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) })), - kpat!(Down) => Some(Cmd::Select( - Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1))))), - kpat!(Left) => Some(Cmd::Select( - if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) })), - kpat!(Right) => Some(Cmd::Select( - Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s))), - - _ => None - }, - Selected::Scene(s) => match input { - kpat!(Char(',')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))), - kpat!(Char('.')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))), - kpat!(Char('<')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))), - kpat!(Char('>')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))), - kpat!(Char('q')) => Some(Cmd::Scene(Scene::Enqueue(s))), - kpat!(Delete) => Some(Cmd::Scene(Scene::Delete(s))), - kpat!(Char('c')) => Some(Cmd::Scene(Scene::SetColor(s, ItemPalette::random()))), - - kpat!(Up) => Some( - Cmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix })), - kpat!(Down) => Some( - Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1))))), - kpat!(Left) => - return None, - kpat!(Right) => Some( - Cmd::Select(Selected::Clip(0, s))), - - _ => None - }, - Selected::Track(t) => match input { - kpat!(Char(',')) => Some(Cmd::Track(Track::Swap(t, t - 1))), - kpat!(Char('.')) => Some(Cmd::Track(Track::Swap(t, t + 1))), - kpat!(Char('<')) => Some(Cmd::Track(Track::Swap(t, t - 1))), - kpat!(Char('>')) => Some(Cmd::Track(Track::Swap(t, t + 1))), - kpat!(Delete) => Some(Cmd::Track(Track::Delete(t))), - kpat!(Char('c')) => Some(Cmd::Track(Track::SetColor(t, ItemPalette::random()))), - - kpat!(Up) => - return None, - kpat!(Down) => Some( - Cmd::Select(Selected::Clip(t, 0))), - kpat!(Left) => Some( - Cmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix })), - kpat!(Right) => Some( - Cmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1))))), - - _ => None - }, - Selected::Mix => match input { - kpat!(Delete) => Some(Cmd::Clear), - kpat!(Char('0')) => Some(Cmd::StopAll), - kpat!(Char('c')) => Some(Cmd::Color(ItemPalette::random())), - - kpat!(Up) => - return None, - kpat!(Down) => Some( - Cmd::Select(Selected::Scene(0))), - kpat!(Left) => - return None, - kpat!(Right) => Some( - Cmd::Select(Selected::Track(0))), - - _ => None - }, - } -}.or_else(||if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) { - Some(Cmd::Editor(command)) -} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { - Some(Cmd::Phrases(command)) -} else { - None -})?); - -command!(|self: ArrangerCommand, state: Arranger|match self { - Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, - Self::Clip(cmd) => cmd.delegate(state, Self::Clip)?, - Self::Scene(cmd) => cmd.delegate(state, Self::Scene)?, - Self::Track(cmd) => cmd.delegate(state, Self::Track)?, - Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?, - Self::Zoom(_) => { todo!(); }, - Self::Select(selected) => { - *state.selected_mut() = selected; - None - }, - Self::Color(palette) => { - let old = state.color; - state.color = palette; - Some(Self::Color(old)) - }, - Self::Phrases(cmd) => { - match cmd { - // autoselect: automatically load selected clip in editor - PoolCommand::Select(_) => { - let undo = cmd.delegate(&mut state.pool, Self::Phrases)?; - state.editor.set_clip(Some(state.pool.clip())); - undo - }, - // reload clip in editor to update color - PoolCommand::Phrase(MidiPoolCommand::SetColor(index, _)) => { - let undo = cmd.delegate(&mut state.pool, Self::Phrases)?; - state.editor.set_clip(Some(state.pool.clip())); - undo - }, - _ => cmd.delegate(&mut state.pool, Self::Phrases)? - } - }, - Self::History(_) => { todo!() }, - Self::StopAll => { - for track in 0..state.tracks.len() { - state.tracks[track].player.enqueue_next(None); - } - None - }, - Self::Clear => { todo!() }, -}); -command!(|self: ArrangerTrackCommand, state: Arranger|match self { - Self::SetColor(index, color) => { - let old = state.tracks[index].color; - state.tracks[index].color = color; - Some(Self::SetColor(index, old)) - }, - Self::Stop(track) => { - state.tracks[track].player.enqueue_next(None); - None - }, - _ => None -}); -command!(|self: ArrangerSceneCommand, state: Arranger|match self { - Self::Delete(index) => { - state.scene_del(index); - None - }, - Self::SetColor(index, color) => { - let old = state.scenes[index].color; - state.scenes[index].color = color; - Some(Self::SetColor(index, old)) - }, - Self::Enqueue(scene) => { - for track in 0..state.tracks.len() { - state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref()); - } - None - }, - _ => None -}); -command!(|self: ArrangerClipCommand, state: Arranger|match self { - Self::Get(track, scene) => { todo!() }, - Self::Put(track, scene, clip) => { - let old = state.scenes[scene].clips[track].clone(); - state.scenes[scene].clips[track] = clip; - Some(Self::Put(track, scene, old)) - }, - Self::Enqueue(track, scene) => { - state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref()); - None - }, - _ => None -}); diff --git a/tek/src/arranger/arranger_track.rs b/tek/src/arranger/arranger_track.rs index 64a55f5c..c3e5c2ff 100644 --- a/tek/src/arranger/arranger_track.rs +++ b/tek/src/arranger/arranger_track.rs @@ -27,14 +27,14 @@ command!(|self: ArrangerTrackCommand, state: Arranger|match self { }); impl Arranger { pub fn track_next_name (&self) -> Arc { - format!("Tr{:02}", self.tracks.len() + 1).into() + format!("Trk{:02}", self.tracks.len() + 1).into() } pub fn track_add (&mut self, name: Option<&str>, color: Option) -> Usually<&mut ArrangerTrack> { let name = name.map_or_else(||self.track_next_name(), |x|x.to_string().into()); let track = ArrangerTrack { - width: name.len() + 2, + width: (name.len() + 2).max(9), color: color.unwrap_or_else(ItemPalette::random), player: MidiPlayer::from(&self.clock), name,