wip: figuring out sane layout for midi routings

This commit is contained in:
🪞👃🪞 2025-01-25 14:07:58 +01:00
parent 923568d7f9
commit 4eff4316c6
5 changed files with 117 additions and 83 deletions

View file

@ -48,6 +48,9 @@ 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

View file

@ -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),

View file

@ -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<Item=(usize, &'a Track, usize, usize)> + 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<str> {
format!("Track{:02}", self.tracks().len() + 1).into()
}

View file

@ -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<TuiOut> + 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(
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▞▞▞▞"),
)))))});
self.view_row(w, h as u16, header, cells)
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<TuiOut> + 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(
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)
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<TuiOut>) -> impl Content<TuiOut> {
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<Item=(usize, &'a Track, usize, usize)> + 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<TuiOut> + 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<TuiOut> + 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<Item = (usize, &Scene, usize, usize)> + 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<TuiOut> + 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<TuiOut> + 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<TuiOut> + 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<TuiOut> + 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<TuiOut> + 'a,

View file

@ -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)))))))