mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
wip: move tracks to bottom
This commit is contained in:
parent
f7d9d2e107
commit
d9f1875c03
6 changed files with 212 additions and 386 deletions
16
README.md
16
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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)))))))
|
||||
|
|
|
|||
|
|
@ -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<TuiOut> 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<Item = (usize, &ArrangerScene, usize, usize)> {
|
||||
pub fn scenes_with_sizes (&self, h: usize)
|
||||
-> impl Iterator<Item = (usize, &ArrangerScene, usize, usize)>
|
||||
{
|
||||
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<TuiOut> + '_ {
|
||||
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<TuiOut> {
|
||||
fn play_header (&self) -> BoxThunk<TuiOut> {
|
||||
(||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<TuiOut> + '_ {
|
||||
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<TuiOut> + '_ {
|
||||
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<TuiOut> + '_ {
|
||||
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(
|
||||
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<TuiOut> {
|
||||
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<TuiOut> + '_ {
|
||||
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(
|
||||
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<TuiOut> {
|
||||
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<TuiOut> + '_ {
|
||||
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,15 +733,102 @@ keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand {
|
|||
None
|
||||
})?);
|
||||
|
||||
fn clip_keymap (state: &Arranger, input: &Event, t: usize, s: usize) -> Option<ArrangerCommand> {
|
||||
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<ArrangerCommand> {
|
||||
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<ArrangerCommand> {
|
||||
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::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::Zoom(_) => { todo!(); },
|
||||
Self::Select(selected) => {
|
||||
*state.selected_mut() = selected;
|
||||
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) => {
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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<Arc<RwLock<MidiClip>>>),
|
||||
Enqueue(usize, usize),
|
||||
Edit(Option<Arc<RwLock<MidiClip>>>),
|
||||
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
|
||||
});
|
||||
|
|
@ -27,14 +27,14 @@ command!(|self: ArrangerTrackCommand, state: Arranger|match self {
|
|||
});
|
||||
impl Arranger {
|
||||
pub fn track_next_name (&self) -> Arc<str> {
|
||||
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<ItemPalette>)
|
||||
-> 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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue