diff --git a/crates/tek/src/tui/app_groovebox.rs b/crates/tek/src/tui/app_groovebox.rs index d565f33c..92241815 100644 --- a/crates/tek/src/tui/app_groovebox.rs +++ b/crates/tek/src/tui/app_groovebox.rs @@ -9,6 +9,13 @@ pub struct GrooveboxTui { from_jack!(|jack|GrooveboxTui { let mut sequencer = SequencerTui::try_from(jack)?; sequencer.status = false; + let midi_in_1 = jack.read().unwrap().register_port("in1", MidiIn::default())?; + let midi_out = jack.read().unwrap().register_port("out", MidiOut::default())?; + let midi_in_2 = jack.read().unwrap().register_port("in2", MidiIn::default())?; + let audio_in_1 = jack.read().unwrap().register_port("inL", AudioIn::default())?; + let audio_in_2 = jack.read().unwrap().register_port("inR", AudioIn::default())?; + let audio_out_1 = jack.read().unwrap().register_port("out1", AudioOut::default())?; + let audio_out_2 = jack.read().unwrap().register_port("out2", AudioOut::default())?; Self { sequencer, sampler: SamplerTui::try_from(jack)?, diff --git a/crates/tek/src/tui/arranger_mode_v.rs b/crates/tek/src/tui/arranger_mode_v.rs index d4980ece..354b4260 100644 --- a/crates/tek/src/tui/arranger_mode_v.rs +++ b/crates/tek/src/tui/arranger_mode_v.rs @@ -1,4 +1,90 @@ use crate::*; +pub struct ArrangerVHead<'a> { + focused: bool, + selected: ArrangerSelection, + scenes_w: u16, + header_h: u16, + timebase: &'a Arc, + current: &'a Arc, + tracks: Vec<(&'a ArrangerTrack, usize, usize)>, +} +from!(<'a>|state: &'a ArrangerTui|ArrangerVHead<'a> = Self { // A + focused: true, + selected: state.selected, + scenes_w: 3 + ArrangerScene::longest_name(state.scenes()) as u16, + header_h: 3, + timebase: state.clock().timebase(), + current: &state.clock().playhead, + tracks: track_widths(state.tracks()) + .iter() + .enumerate() + .map(|(i, (a, b))|(&state.tracks()[i], *a, *b)) + .collect(), +}); +render!(|self: ArrangerVHead<'a>|row!( + (track, w, _) in self.tracks.iter() => { + let name = Self::format_name(track, *w); + let input = Self::format_input(track)?; + let elapsed = Self::format_elapsed(track, &self.timebase); + let until_next = Self::format_until_next(track, &self.timebase, &self.current); + let output = Self::format_output(track)?; + Tui::push_x(self.scenes_w, + Tui::bg(track.color().base.rgb, + Tui::min_xy(*w as u16, self.header_h, row!([ + col!(!["▎", "▎", "▎", "▎", "▎", "▎",]), + col!(![name, input, output, elapsed, until_next, output])])))) + } +)); +impl<'a> ArrangerVHead<'a> { + /// name and width of track + fn format_name (track: &ArrangerTrack, w: usize) -> impl Render { + let name = track.name().read().unwrap(); + let max_w = w.saturating_sub(1).min(name.len()).max(2); + Tui::bold(true, Tui::fg(track.color.lightest.rgb, format!("▎{}", &name[0..max_w]))); + } + /// input port + fn format_input (track: &ArrangerTrack) -> Usually> { + Ok(format!(">{}", track.player.midi_ins().get(0) + .map(|port|port.short_name()) + .transpose()? + .unwrap_or("(none)".into()))) + } + /// output port + fn format_output (track: &ArrangerTrack) -> Usually> { + Ok(format!("<{}", track.player.midi_outs().get(0) + .map(|port|port.short_name()) + .transpose()? + .unwrap_or("(none)".into()))) + } + /// beats elapsed + fn format_elapsed (track: &ArrangerTrack, timebase: &Arc) -> impl Render { + if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() { + let length = phrase.read().unwrap().length; + let elapsed = track.player.pulses_since_start().unwrap(); + let elapsed = timebase.format_beats_1_short( + (elapsed as usize % length) as f64 + ); + format!("+{elapsed:>}") + } else { + String::new() + } + } + /// beats until switchover + fn format_until_next (track: &ArrangerTrack, timebase: &Arc, current: &Arc) + -> Option> + { + track.player.next_phrase().as_ref().map(|(t, _)|{ + let target = t.pulse.get(); + let current = current.pulse.get(); + if target > current { + let remaining = target - current; + format!("-{:>}", timebase.format_beats_0_short(remaining)) + } else { + String::new() + } + }) + } +} pub struct ArrangerVColSep { cols: Vec<(usize, usize)>, scenes_w: u16, @@ -114,76 +200,6 @@ render!(|self: ArrangerVCursor|render(move|to: &mut TuiOutput|{ }) })); -pub struct ArrangerVHead<'a> { - tracks: &'a Vec, - cols: Vec<(usize, usize)>, - focused: bool, - selected: ArrangerSelection, - scenes_w: u16, - header_h: u16, - timebase: &'a Arc, - current: &'a Arc, -} -from!(<'a>|state: &'a ArrangerTui|ArrangerVHead<'a> = Self { // A - tracks: &state.tracks, - cols: track_widths(state.tracks()), - focused: true, - selected: state.selected, - scenes_w: 3 + ArrangerScene::longest_name(state.scenes()) as u16, - header_h: 3, - timebase: state.clock().timebase(), - current: &state.clock().playhead, -}); -render!(|self: ArrangerVHead<'a>|row!( - (track, w) in self.tracks.iter().zip(self.cols.iter().map(|col|col.0)) => { - - // name and width of track - let name = track.name().read().unwrap(); - let max_w = w.saturating_sub(1).min(name.len()).max(2); - let name = Tui::bold(true, Tui::fg(track.color.lightest.rgb, format!("▎{}", &name[0..max_w]))); - - // name of active MIDI input - let input = format!(">{}", track.player.midi_ins().get(0) - .map(|port|port.short_name()) - .transpose()? - .unwrap_or("(none)".into())); - - // beats elapsed - let elapsed = if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() { - let length = phrase.read().unwrap().length; - let elapsed = track.player.pulses_since_start().unwrap(); - let elapsed = self.timebase.format_beats_1_short( - (elapsed as usize % length) as f64 - ); - format!("+{elapsed:>}") - } else { - String::new() - }; - - // beats until switchover - let until_next = track.player.next_phrase().as_ref().map(|(t, _)|{ - let target = t.pulse.get(); - let current = self.current.pulse.get(); - if target > current { - let remaining = target - current; - format!("-{:>}", self.timebase.format_beats_0_short(remaining)) - } else { - String::new() - } - }); - - // name of active MIDI output - let output = format!("<{}", track.player.midi_outs().get(0) - .map(|port|port.short_name()) - .transpose()? - .unwrap_or("(none)".into())); - Tui::push_x(self.scenes_w, - Tui::bg(track.color().base.rgb, - Tui::min_xy(w as u16, self.header_h, row!([ - col!(!["▎", "▎", "▎", "▎", "▎", "▎",]), - col!(![name, input, output, elapsed, until_next, output])])))) - } -)); pub struct ArrangerVBody<'a> { size: &'a Measure,