From 4eff4316c6284deaf7b466f8fb206c92af820840 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 25 Jan 2025 14:07:58 +0100 Subject: [PATCH] wip: figuring out sane layout for midi routings --- Justfile | 36 ++++------- tek/src/cli.rs | 18 +++++- tek/src/model.rs | 16 ----- tek/src/view.rs | 126 ++++++++++++++++++++++++++------------ tek/src/view_arranger.edn | 4 +- 5 files changed, 117 insertions(+), 83 deletions(-) diff --git a/Justfile b/Justfile index 5b469fff..4d557045 100644 --- a/Justfile +++ b/Justfile @@ -42,12 +42,15 @@ fpush: ftpush: git push --tags -fu codeberg && git push --tags -fu origin -debug := "reset && cargo run --" -release := "reset && cargo run --release --" -name := "-n tek" -bpm := "-b 174" -midi-in := "-i 'Midi-Bridge:.*nanoKEY.*:.*capture.*'" -midi-out := "-o 'Midi-Bridge:.*playback.*'" +debug := "reset && cargo run --" +release := "reset && cargo run --release --" +name := "-n tek" +bpm := "-b 174" +midi-in := "-i 'Midi-Bridge:.*nanoKEY.*:.*capture.*'" +midi-out := "-o 'Midi-Bridge:.*playback.*'" +audio-in := "-l 'Komplete Audio 6 Pro:capture_AUX1' -r 'Komplete Audio 6 Pro:capture_AUX1'" +audio-out := "-L 'Komplete Audio 6 Pro:playback_AUX1' -R 'Komplete Audio 6 Pro:playback_AUX1'" +firefox-in := "-l 'Firefox:output_FL' -r 'Firefox:output_FR'" # TODO: arranger track mappings #-i "1=Midi-Bridge:nanoKEY Studio 2:(capture_0) nanoKEY Studio nanoKEY Studio _" @@ -82,28 +85,13 @@ groovebox: {{debug}} {{name}} {{bpm}} groovebox groovebox-ext: reset - {{debug}} {{name}} {{bpm}} {{midi-in}} {{midi-out}} groovebox \ - -l "Komplete Audio 6 Pro:capture_AUX1" \ - -r "Komplete Audio 6 Pro:capture_AUX1" \ - -L "Komplete Audio 6 Pro:playback_AUX1" \ - -R "Komplete Audio 6 Pro:playback_AUX1" + {{debug}} {{name}} {{bpm}} {{midi-in}} {{midi-out}} {{audio-in}} {{audio-out}} groovebox groovebox-release: {{release}} {{name}} {{bpm}} groovebox groovebox-release-ext: - {{release}} {{name}} {{bpm}} {{midi-in}} {{midi-out}} groovebox \ - -l "Komplete Audio 6 Pro:capture_AUX1" \ - -r "Komplete Audio 6 Pro:capture_AUX1" \ - -L "Komplete Audio 6 Pro:playback_AUX1" \ - -R "Komplete Audio 6 Pro:playback_AUX2" + {{release}} {{name}} {{bpm}} {{midi-in}} {{midi-out}} {{audio-in}} {{audio-out}} groovebox groovebox-release-ext-browser: - reset - cargo run --release -- groovebox -n tek \ - -b 112 \ - -i "Midi-Bridge:nanoKEY Studio 1:(capture_0) nanoKEY Studio nanoKEY Studio _" \ - -l "Firefox:output_FL" \ - -r "Firefox:output_FR" \ - -L "Komplete Audio 6 Pro:playback_AUX1" \ - -R "Komplete Audio 6 Pro:playback_AUX2" + {{release}} {{name}} {{bpm}} {{midi-in}} {{firefox-in}} {{audio-out}} groovebox sequencer: {{debug}} {{name}} {{bpm}} sequencer diff --git a/tek/src/cli.rs b/tek/src/cli.rs index 16191600..65bd4d74 100644 --- a/tek/src/cli.rs +++ b/tek/src/cli.rs @@ -104,8 +104,22 @@ impl Tek { jack: jack.clone(), color: ItemPalette::random(), clock: Clock::new(jack, bpm)?, - midi_ins: vec![], - midi_outs: vec![], + midi_ins: { + let mut midi_ins = vec![]; + for (index, connect) in midi_froms.iter().enumerate() { + let port = JackMidiIn::new(jack, &format!("m/{index}"), &[connect.clone()])?; + midi_ins.push(port); + } + midi_ins + }, + midi_outs: { + let mut midi_outs = vec![]; + for (index, connect) in midi_tos.iter().enumerate() { + let port = JackMidiOut::new(jack, &format!("{index}/m"), &[connect.clone()])?; + midi_outs.push(port); + } + midi_outs + }, keys: SourceIter(KEYS_APP), keys_clip: SourceIter(KEYS_CLIP), keys_track: SourceIter(KEYS_TRACK), diff --git a/tek/src/model.rs b/tek/src/model.rs index 1015f0c2..325b6585 100644 --- a/tek/src/model.rs +++ b/tek/src/model.rs @@ -269,22 +269,6 @@ pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max) } const WIDTH_OFFSET: usize = 1; - fn tracks_sizes <'a> (&'a self, editing: bool, bigger: usize) - -> impl Iterator + Send + Sync + 'a - { - let mut x = 0; - let active = match self.selected() { - Selection::Track(t) if editing => Some(t.saturating_sub(1)), - Selection::Clip(t, _) if editing => Some(t.saturating_sub(1)), - _ => None - }; - self.tracks().iter().enumerate().map(move |(index, track)|{ - let width = if Some(index) == active { bigger } else { track.width.max(8) }; - let data = (index, track, x, x + width); - x += width + Self::WIDTH_OFFSET; - data - }) - } fn track_next_name (&self) -> Arc { format!("Track{:02}", self.tracks().len() + 1).into() } diff --git a/tek/src/view.rs b/tek/src/view.rs index c8de8231..e81254b1 100644 --- a/tek/src/view.rs +++ b/tek/src/view.rs @@ -63,11 +63,14 @@ view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); { ":scene-add" => self.view_scene_add().boxed(), }); provide_num!(u16: |self: Tek| { - ":sidebar-w" => self.w_sidebar(), - ":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 }, - ":outs-y" => self.size.h().saturating_sub(self.midi_outs.len() + 2) as u16, + ":w-sidebar-w" => self.w_sidebar(), + ":h-sample-h" => if self.is_editing() { 0 } else { 5 }, + ":w-samples-w" => if self.is_editing() { 4 } else { 11 }, + ":y-samples-y" => if self.is_editing() { 1 } else { 0 }, + ":h-ins" => self.h_inputs(), + ":h-outs" => self.h_outputs(), + ":y-ins" => (self.size.h() as u16).saturating_sub(self.h_inputs()), + ":y-outs" => (self.size.h() as u16).saturating_sub(self.h_outputs()), }); macro_rules! per_track { ($area:expr;|$self:ident,$track:ident,$index:ident|$content:expr) => {{ @@ -187,18 +190,14 @@ impl Tek { )) } fn view_inputs (&self) -> impl Content + use<'_> { - let w = self.w(); let fg = Tui::g(224); let bg = Tui::g(64); - let mut h = 2.max(1 + self.midi_ins.len()); - for midi_in in self.midi_ins.iter() { h += midi_in.conn().len() } - let conn = move|conn: &PortConnect|{ - Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, conn.info())))) - }; + let conn = move|conn: &PortConnect|Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, conn.info())))); let header: ThunkBox<_> = io_header!(self, " I ", " midi ins", self.midi_ins.len(), - Map::new(||self.midi_ins.iter(), move|input, index|map_south(index as u16, 1u16, Bsp::s( + Map::new(||self.midi_ins.iter(), move|input, index|map_south(index as u16, 1, Bsp::s( Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(input.name())))), - input.conn().get(0).map(conn))))); + Map::new(||input.conn().iter(), move|connect, index|map_south(index as u16, 0, + Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, connect.info())))))))))); let cells: ThunkBox<_> = per_track!(self.size.w();|self, track, t|{ let rec = track.player.recording; let mon = track.player.monitoring; @@ -209,22 +208,27 @@ impl Tek { Tui::fg_bg(if rec { White } else { track.color.darkest.rgb }, bg, "Recrd"), Tui::fg_bg(if mon { White } else { track.color.darkest.rgb }, bg, "Monit"), ))), - Map::new(||self.midi_outs.iter(), move|output, index|map_south(index as u16, 1u16, Self::wrap(bg, fg, Bsp::e( - Tui::fg_bg(if rec { White } else { track.color.darkest.rgb }, bg, "R▞▞▞▞"), - Tui::fg_bg(if mon { White } else { track.color.darkest.rgb }, bg, "M▞▞▞▞"), - )))))}); - self.view_row(w, h as u16, header, cells) + Map::new(||self.midi_ins.iter(), move|input, index|map_south(index as u16, 1, + Self::wrap(bg, fg, Bsp::e( + Tui::fg_bg(if rec { White } else { track.color.darkest.rgb }, bg, "R▞▞▞▞"), + Tui::fg_bg(if mon { White } else { track.color.darkest.rgb }, bg, "M▞▞▞▞"), + )))))}); + Tui::bg(Black, self.view_row(self.w(), self.h_inputs(), header, cells)) + } + fn h_inputs (&self) -> u16 { + let mut h = 1; + for midi_in in self.midi_ins.iter() { h += 1 + midi_in.conn().len() as u16 } + h } fn view_outputs (&self) -> impl Content + use<'_> { let fg = Tui::g(224); let bg = Tui::g(64); - let mut h = 2.max(1 + self.midi_outs.len()); - for midi_out in self.midi_outs.iter() { h += midi_out.conn().len() } + let conn = move|conn: &PortConnect|Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, conn.info())))); let header: ThunkBox<_> = io_header!(self, " O ", " midi outs", self.midi_outs.len(), - Map::new(||self.midi_outs.iter(), move|output, index|map_south(index as u16, 1u16, Bsp::s( + Map::new(||self.midi_outs.iter(), move|output, index|map_south(index as u16, 0, Bsp::s( Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(output.name())))), - output.conn().get(0).map(|connect|Fill::x(Align::w(Tui::bold(false, - Tui::fg_bg(fg, bg, connect.info()))))))))); + Map::new(||output.conn().iter(), move|connect, index|map_south(index as u16, 0, + Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, connect.info())))))))))); let mute = false; let solo = false; let cells: ThunkBox<_> = per_track!(self.size.w();|self, track, t|{ @@ -234,14 +238,39 @@ impl Tek { Self::wrap(bg, fg, Tui::bold(true, Bsp::e( Tui::fg_bg(if mute { White } else { track.color.darkest.rgb }, bg, "Play "), Tui::fg_bg(if solo { White } else { track.color.darkest.rgb }, bg, "Solo "),))), - Map::new(||self.midi_outs.iter(), move|output, index|map_south(index as u16, 1u16, Self::wrap(bg, fg, Bsp::e( - Tui::fg_bg(if mute { White } else { track.color.darkest.rgb }, bg, "P▞▞▞▞"), - Tui::fg_bg(if solo { White } else { track.color.darkest.rgb }, bg, "S▞▞▞▞"))))))}); - self.view_row(self.w(), h as u16, header, cells) + Map::new(||self.midi_outs.iter(), move|output, index|map_south(index as u16, 1u16, + Self::wrap(bg, fg, Bsp::e( + Tui::fg_bg(if mute { White } else { track.color.darkest.rgb }, bg, "P▞▞▞▞"), + Tui::fg_bg(if solo { White } else { track.color.darkest.rgb }, bg, "S▞▞▞▞"))))))}); + Tui::bg(Black, self.view_row(self.w(), self.h_outputs(), header, cells)) + } + fn h_outputs (&self) -> u16 { + let mut h = 1; + for midi_out in self.midi_outs.iter() { h += 1 + midi_out.conn().len() as u16 } + h } fn wrap (bg: Color, fg: Color, content: impl Content) -> impl Content { Bsp::e(Tui::fg_bg(bg, Reset, "▐"), Bsp::w(Tui::fg_bg(bg, Reset, "▌"), Tui::fg_bg(fg, bg, content))) } + fn tracks_sizes <'a> (&'a self, editing: bool, bigger: usize) + -> impl Iterator + Send + Sync + 'a + { + let mut x = 0; + let active = match self.selected() { + Selection::Track(t) if editing => Some(t.saturating_sub(1)), + Selection::Clip(t, _) if editing => Some(t.saturating_sub(1)), + _ => None + }; + self.tracks().iter().enumerate().map(move |(index, track)|{ + let width = if Some(index) == active { bigger } else { track.width.max(8) }; + let data = (index, track, x, x + width); + x += width + Self::WIDTH_OFFSET; + data + }) + } + fn w_tracks (&self, editing: bool, bigger: usize) -> u16 { + self.tracks_sizes(editing, bigger).last().map(|(_, _, _, x)|x as u16).unwrap_or(0) + } fn view_tracks (&self) -> impl Content + use<'_> { let height = 1; let header: ThunkBox<_> = @@ -258,19 +287,24 @@ impl Tek { }); self.view_row(self.w(), height, header, content) } - fn view_track_add (&self) -> impl Content + use<'_> { - let data = (self.selected.track().unwrap_or(0), self.tracks().len()); - self.fmtd.write().unwrap().trks.update(Some(data), - rewrite!(buf, "({}/{})", data.0, data.1)); - button(" T ", Bsp::e(" track ", - self.fmtd.read().unwrap().trks.view.clone())) + fn scenes_sizes (&self, editing: bool, height: usize, larger: usize,) + -> impl Iterator + Send + Sync + { + let mut y = 0; + let (selected_track, selected_scene) = match self.selected() { + Selection::Clip(t, s) => (Some(t.saturating_sub(1)), Some(s.saturating_sub(1))), + _ => (None, None) + }; + self.scenes().iter().enumerate().map(move|(s, scene)|{ + let active = editing && selected_track.is_some() && selected_scene == Some(s); + let height = if active { larger } else { height }; + let data = (s, scene, y, y + height); + y += height; + data + }) } - fn view_scene_add (&self) -> impl Content + use<'_> { - let data = (self.selected().scene().unwrap_or(0), self.scenes().len()); - self.fmtd.write().unwrap().scns.update(Some(data), - rewrite!(buf, "({}/{})", data.0, data.1)); - button(" C-a ", Bsp::e(" add scene ", - self.fmtd.read().unwrap().scns.view.clone())) + fn h_scenes (&self, editing: bool, height: usize, larger: usize) -> u16 { + self.scenes_sizes(editing, height, larger).last().map(|(_, _, _, y)|y as u16).unwrap_or(0) } fn view_scenes (&self) -> impl Content + use<'_> { let bstyle = Style::default().fg(Tui::g(0)); @@ -377,6 +411,20 @@ impl Tek { When::new(compact, Margin::x(1, Tui::fg_bg(Tui::g(255), Tui::g(96), label))), )) } + fn view_track_add (&self) -> impl Content + use<'_> { + let data = (self.selected.track().unwrap_or(0), self.tracks().len()); + self.fmtd.write().unwrap().trks.update(Some(data), + rewrite!(buf, "({}/{})", data.0, data.1)); + button(" T ", Bsp::e(" track ", + self.fmtd.read().unwrap().trks.view.clone())) + } + fn view_scene_add (&self) -> impl Content + use<'_> { + let data = (self.selected().scene().unwrap_or(0), self.scenes().len()); + self.fmtd.write().unwrap().scns.update(Some(data), + rewrite!(buf, "({}/{})", data.0, data.1)); + button(" C-a ", Bsp::e(" add scene ", + self.fmtd.read().unwrap().scns.view.clone())) + } } fn button <'a> ( key: impl Content + 'a, diff --git a/tek/src/view_arranger.edn b/tek/src/view_arranger.edn index 91a81360..111f0031 100644 --- a/tek/src/view_arranger.edn +++ b/tek/src/view_arranger.edn @@ -1,5 +1,5 @@ (bsp/s (max/y 1 :toolbar) (fill/x (align/c (bsp/a (fill/xy (align/e :pool)) - (bsp/a (fill/xy (align/n (bsp/s :tracks :outputs))) - (bsp/a (fill/x (fixed/y :outs-y (align/s :inputs))) + (bsp/a (fill/xy (align/n (bsp/s :tracks :inputs))) + (bsp/a (fill/x (fixed/y :y-outs (align/s :outputs))) (bsp/s :scenes :scene-add)))))))