From 0b365e05c8fd56072098d58348757c81c053c966 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 7 Jan 2025 18:15:50 +0100 Subject: [PATCH] wip: saner rendering on arranger header --- bin/cli_arranger.rs | 4 +- layout/src/map.rs | 10 ++ src/arranger.rs | 37 +---- src/arranger/arranger_audio.rs | 38 +++++ src/arranger/arranger_track.rs | 3 +- src/arranger/arranger_tui.rs | 277 +++++++++++++++++---------------- 6 files changed, 195 insertions(+), 174 deletions(-) create mode 100644 src/arranger/arranger_audio.rs diff --git a/bin/cli_arranger.rs b/bin/cli_arranger.rs index dab6bdd6..99d106a6 100644 --- a/bin/cli_arranger.rs +++ b/bin/cli_arranger.rs @@ -13,7 +13,7 @@ pub struct ArrangerCli { #[arg(short, long, default_value_t = true)] transport: bool, /// Number of tracks - #[arg(short = 'x', long, default_value_t = 4)] + #[arg(short = 'x', long, default_value_t = 16)] tracks: usize, /// Number of scenes #[arg(short, long, default_value_t = 8)] @@ -51,7 +51,7 @@ fn add_tracks (jack: &JackConnection, app: &mut Arranger, cli: &ArrangerCli) -> let track = app.track_add(None, Some( track_color_1.mix(track_color_2, i as f32 / n as f32).into() ))?; - track.width = 8; + track.width = 4; let name = track.name.read().unwrap(); track.player.midi_ins.push( jack.register_port(&format!("{}I", &name), MidiIn::default())? diff --git a/layout/src/map.rs b/layout/src/map.rs index 7f1158f2..b6ece522 100644 --- a/layout/src/map.rs +++ b/layout/src/map.rs @@ -10,6 +10,16 @@ pub fn map_south( Fill::x(item)))) } +pub fn map_east( + item_offset: O::Unit, + item_width: O::Unit, + item: impl Content +) -> impl Content { + Push::x(item_offset, + Align::w(Fixed::y(item_width, + Fill::y(item)))) +} + pub struct Map<'a, A, B, I, F, G>(pub PhantomData<&'a()>, pub F, pub G) where I: Iterator + Send + Sync, F: Fn() -> I + Send + Sync + 'a, diff --git a/src/arranger.rs b/src/arranger.rs index a4b4562d..0a25b3d1 100644 --- a/src/arranger.rs +++ b/src/arranger.rs @@ -1,5 +1,6 @@ use crate::*; +mod arranger_audio; pub(crate) use self::arranger_audio::*; mod arranger_command; pub(crate) use self::arranger_command::*; mod arranger_scene; pub(crate) use self::arranger_scene::*; mod arranger_select; pub(crate) use self::arranger_select::*; @@ -110,42 +111,6 @@ from_jack!(|jack| Arranger { //Self::render_mode(self))))), Fill::y(&"fixme: self.editor")))))); //self.size.of(layout) //}); -audio!(|self: Arranger, client, scope|{ - // Start profiling cycle - let t0 = self.perf.get_t0(); - // Update transport clock - if Control::Quit == ClockAudio(self).process(client, scope) { - return Control::Quit - } - // Update MIDI sequencers - let tracks = &mut self.tracks; - let note_buf = &mut self.note_buf; - let midi_buf = &mut self.midi_buf; - if Control::Quit == TracksAudio(tracks, note_buf, midi_buf).process(client, scope) { - return Control::Quit - } - // FIXME: one of these per playing track - //self.now.set(0.); - //if let ArrangerSelection::Clip(t, s) = self.selected { - //let phrase = self.scenes.get(s).map(|scene|scene.clips.get(t)); - //if let Some(Some(Some(phrase))) = phrase { - //if let Some(track) = self.tracks().get(t) { - //if let Some((ref started_at, Some(ref playing))) = track.player.play_phrase { - //let phrase = phrase.read().unwrap(); - //if *playing.read().unwrap() == *phrase { - //let pulse = self.current().pulse.get(); - //let start = started_at.pulse.get(); - //let now = (pulse - start) % phrase.length as f64; - //self.now.set(now); - //} - //} - //} - //} - //} - // End profiling cycle - self.perf.update(t0, scope); - return Control::Continue -}); has_clock!(|self: Arranger|&self.clock); has_phrases!(|self: Arranger|self.pool.phrases); has_editor!(|self: Arranger|self.editor); diff --git a/src/arranger/arranger_audio.rs b/src/arranger/arranger_audio.rs new file mode 100644 index 00000000..3662af6a --- /dev/null +++ b/src/arranger/arranger_audio.rs @@ -0,0 +1,38 @@ +use crate::*; + +audio!(|self: Arranger, client, scope|{ + // Start profiling cycle + let t0 = self.perf.get_t0(); + // Update transport clock + //if Control::Quit == ClockAudio(self).process(client, scope) { + //return Control::Quit + //} + //// Update MIDI sequencers + //let tracks = &mut self.tracks; + //let note_buf = &mut self.note_buf; + //let midi_buf = &mut self.midi_buf; + //if Control::Quit == TracksAudio(tracks, note_buf, midi_buf).process(client, scope) { + //return Control::Quit + //} + // FIXME: one of these per playing track + //self.now.set(0.); + //if let ArrangerSelection::Clip(t, s) = self.selected { + //let phrase = self.scenes.get(s).map(|scene|scene.clips.get(t)); + //if let Some(Some(Some(phrase))) = phrase { + //if let Some(track) = self.tracks().get(t) { + //if let Some((ref started_at, Some(ref playing))) = track.player.play_phrase { + //let phrase = phrase.read().unwrap(); + //if *playing.read().unwrap() == *phrase { + //let pulse = self.current().pulse.get(); + //let start = started_at.pulse.get(); + //let now = (pulse - start) % phrase.length as f64; + //self.now.set(now); + //} + //} + //} + //} + //} + // End profiling cycle + self.perf.update(t0, scope); + return Control::Continue +}); diff --git a/src/arranger/arranger_track.rs b/src/arranger/arranger_track.rs index d2865137..54cbe5d7 100644 --- a/src/arranger/arranger_track.rs +++ b/src/arranger/arranger_track.rs @@ -56,12 +56,11 @@ impl ArrangerTrack { fn longest_name (tracks: &[Self]) -> usize { tracks.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max) } - pub const MIN_WIDTH: usize = 6; fn width_inc (&mut self) { *self.width_mut() += 1; } fn width_dec (&mut self) { - if self.width() > Self::MIN_WIDTH { + if self.width() > Arranger::TRACK_MIN_WIDTH { *self.width_mut() -= 1; } } diff --git a/src/arranger/arranger_tui.rs b/src/arranger/arranger_tui.rs index 0b01390e..4893d40a 100644 --- a/src/arranger/arranger_tui.rs +++ b/src/arranger/arranger_tui.rs @@ -1,73 +1,42 @@ use crate::*; -//impl Arranger { - //fn render_mode (state: &Self) -> impl Content + use<'_> { - //match state.mode { - //ArrangerMode::H => todo!("horizontal arranger"), - //ArrangerMode::V(factor) => Self::render_mode_v(state, factor), - //} - //} -//} -//render!(TuiOut: (self: Arranger) => { - //let pool_w = if self.pool.visible { self.splits[1] } else { 0 }; - //let color = self.color; - //let layout = Bsp::a(Fill::xy(ArrangerStatus::from(self)), - //Bsp::n(Fixed::x(pool_w, PoolView(self.pool.visible, &self.pool)), - //Bsp::n(TransportView::new(true, &self.clock), - //Bsp::s(Fixed::y(1, MidiEditStatus(&self.editor)), - //Bsp::n(Fill::x(Fixed::y(20, - //Bsp::a(Fill::xy(Tui::bg(color.darkest.rgb, "background")), - //Bsp::a( - //Fill::xy(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))), - //Self::render_mode(self))))), Fill::y(&"fixme: self.editor")))))); - //self.size.of(layout) -//}); render!(TuiOut: (self: Arranger) => { let scenes = &self.scenes; let scene_heights = Arranger::scene_heights(scenes, 1); - Fill::xy(self.size.of( + let border_style_1 = Style::default().fg(Color::Rgb(48,48,48)); + let border_style_2 = Style::default().fg(Color::Rgb(72,72,72)); + self.size.of( Bsp::s(self.toolbar_view(), Bsp::n(self.status_view(), Bsp::w(self.pool_view(), - Bsp::n(self.selector_view(), - Bsp::n(Fixed::y(20, &self.editor), - Bsp::s( - Align::w(Fill::x( - Bsp::s(Fixed::y(3, Align::w(self.header())), - Bsp::s(Fixed::y(1, self.ins()), Fill::x(Fixed::y(1, self.outs())))) - )), - Bsp::a( - Bsp::a( - Map::new( - move||scenes.iter(),//.zip(scene_heights.iter().map(|row|row.0)), - move|scene, i|Arranger::format_scene(&self.tracks, scene, scene_heights[i].0) - ), - self.cursor(), - ), - Bsp::a( - Fill::xy(self.scene_row_sep()), - "" - //Fill::xy(ArrangerVColSep::from(self)) - ) - )))))))))}); - //Align::n(Fill::xy(lay!( - //Align::n(Fill::xy(Tui::bg(self.color.darkest.rgb, " "))), - //Align::n(Fill::xy(ArrangerVRowSep::from((self, 1)))), - //Align::n(Fill::xy(ArrangerVColSep::from(self))), - //Align::n(Fill::xy(ArrangerVClips::new(self, 1))), - //Align::n(Fill::xy(ArrangerVCursor::from((self, 1)))))))))))))))); - //Align::n(Fill::xy(":"))))))))))))); - //"todo:")))))))); - //Bsp::s( - //Align::n(Fixed::y(1, Fill::x(ArrangerVIns::from(self)))), - //Bsp::s( - //Fixed::y(20, Align::n(ArrangerVClips::new(self, 1))), - //Fill::x(Fixed::y(1, ArrangerVOuts::from(self))))))))))))); - //Bsp::s( - //Bsp::s( - //Bsp::s( - //Fill::xy(ArrangerVClips::new(self, 1)), - //Fill::x(ArrangerVOuts::from(self))))) - + Bsp::n(self.editor_status_view(), + Bsp::n( + Fixed::y(20, Outer(border_style_2).enclose(&self.editor)), + Outer(border_style_1).enclose( + Bsp::s(self.track_column_headers(), + Bsp::s(self.track_column_inputs(), + Bsp::s(self.track_column_outputs(), ())))))))))) + //Outer(border_style).enclose(Tui::bg(Color::Rgb(0,32,0), self.track_column_headers())))))))) [>Align::nw(Fill::xy(Bsp::s( + //Align::w(Fill::x( + //Bsp::s(Align::w(Fixed::y(3, self.track_column_headers())), + //Bsp::s(Fixed::y(1, self.ins()), Fill::x(Fixed::y(1, self.outs())))) + //)),*/ + //"" + //Bsp::a( + //Bsp::a( + //Map::new( + //move||scenes.iter(),//.zip(scene_heights.iter().map(|row|row.0)), + //move|scene, i|Arranger::cell_scene(&self.tracks, scene, scene_heights[i].0) + //), + //self.cursor(), + //), + //Bsp::a( + //Fill::xy(self.scene_row_sep()), + //"" + ////Fill::xy(ArrangerVColSep::from(self)) + //) + //) + //))))))))))) +}); pub struct ArrangerVClips<'a> { size: &'a Measure, scenes: &'a Vec, @@ -88,35 +57,77 @@ fn row > (color: ItemPalette, field: T) -> impl Content impl Content + use<'_> { + pub const TRACK_MIN_WIDTH: usize = 4; + fn track_column_headers (&self) -> impl Content + use<'_> { let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; - Push::x(scenes_w, Map::new(||Arranger::tracks_with_widths(self.tracks.as_slice()), |(_, track, x1, x2), i| { - let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); - let color = track.color(); - let title = format!("{} {} {}", track.name.read().unwrap(), x1, x2); - Push::x(x1 as u16, Tui::bg(Color::Rgb(50,0,0), title)) })) - //Bsp::s(row(color, Self::format_name(track, w)), - //Bsp::s(row(color, Self::format_elapsed(track, self.clock().timebase())), - //Bsp::s(row(color, Self::format_until_next(track, &self.clock().playhead)), "test1")))))}) + Fixed::y(3, Push::x(scenes_w, Map::new( + ||Arranger::tracks_with_widths(self.tracks.as_slice()), + |(_, track, x1, x2), i| { + let color = track.color(); + map_east(x1 as u16, (x2 - x1) as u16, Tui::fg_bg(color.lightest.rgb, color.base.rgb, + Bsp::s(format!("{}", track.name.read().unwrap()), + Bsp::s( + Self::cell_elapsed(track, self.clock().timebase()), + Self::cell_until_next(track, &self.clock().playhead)))))}))) } - fn ins (&self) -> impl Content + use<'_> { + fn track_column_inputs (&self) -> impl Content + use<'_> { let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; - Push::x(scenes_w, Map::new(||Arranger::tracks_with_widths(self.tracks.as_slice()), |(_, track, x1, x2), i| { - let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); - let color = track.color(); - let input = Self::format_input(track); - Fill::xy(Align::n(Push::x(x1 as u16, Tui::bg(color.base.rgb, Min::xy(w as u16, h, - Fixed::xy(w as u16, 5, row(color, Self::format_input(track).ok()))))))) - })) + Fixed::y(1, Push::x(scenes_w, Map::new( + ||Arranger::tracks_with_widths(self.tracks.as_slice()), + |(_, track, x1, x2), i| { + let color = track.color(); + let input = Self::cell_input(track); + map_east(x1 as u16, (x2 - x1) as u16, Tui::bg(color.base.rgb, + row(color, Self::cell_input(track).ok()))) }))) } - fn outs (&self) -> impl Content + use<'_> { + fn track_column_outputs (&self) -> impl Content + use<'_> { let scenes_w = SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16; - Push::x(scenes_w, Map::new(||Arranger::tracks_with_widths(self.tracks.as_slice()), |(_, track, x1, x2), i| { - let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); - let color = track.color(); - Fill::xy(Align::n(Push::x(x2 as u16, Tui::bg(color.base.rgb, Min::xy(w as u16, h, - Fixed::xy(w as u16, 5, row(color, Self::format_output(track).ok()))))))) - })) + Fixed::y(1, Push::x(scenes_w, Map::new( + ||Arranger::tracks_with_widths(self.tracks.as_slice()), + |(_, track, x1, x2), i| { + let (w, h) = (Arranger::TRACK_MIN_WIDTH.max(x2 - x1), HEADER_H); + let color = track.color(); + map_east(x1 as u16, (x2 - x1) as u16, Tui::bg(color.base.rgb, + row(color, Self::cell_output(track).ok()))) }))) + } + /// name and width of track + fn cell_name (track: &ArrangerTrack, _w: usize) -> impl Content { + let name = track.name().read().unwrap().clone(); + Tui::bold(true, Tui::fg(track.color.lightest.rgb, name)) + } + /// beats elapsed + fn cell_elapsed (track: &ArrangerTrack, timebase: &Arc) -> impl Content { + let mut result = String::new(); + 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() as usize; + result = format!("+{:>}", timebase.format_beats_1_short((elapsed % length) as f64)) + } + result + } + /// beats until switchover + fn cell_until_next (track: &ArrangerTrack, current: &Arc) + -> Option> + { + let timebase = ¤t.timebase; + let mut result = String::new(); + if let Some((t, _)) = track.player.next_phrase().as_ref() { + let target = t.pulse.get(); + let current = current.pulse.get(); + if target > current { + result = format!("-{:>}", timebase.format_beats_0_short(target - current)) + } + } + Some(result) + } + fn cell_input (track: &ArrangerTrack) -> Usually> { + Ok(format!(">{}", track.player.midi_ins().first().map(|port|port.short_name()) + .transpose()?.unwrap_or("?".into()))) + } + /// output port + fn cell_output (track: &ArrangerTrack) -> Usually> { + Ok(format!("<{}", track.player.midi_outs().first().map(|port|port.short_name()) + .transpose()?.unwrap_or("?".into()))) } pub fn scene_heights (scenes: &[ArrangerScene], factor: usize) -> Vec<(usize, usize)> { let mut total = 0; @@ -133,7 +144,7 @@ impl Arranger { } } - fn format_scene <'a> ( + fn cell_scene <'a> ( tracks: &'a [ArrangerTrack], scene: &'a ArrangerScene, pulses: usize ) -> impl Content + use<'a> { let height = 1.max((pulses / PPQ) as u16); @@ -145,12 +156,12 @@ impl Arranger { Expand::x(1, Tui::bold(true, scene.name.read().unwrap().clone())) ); let clips = Map::new(||Arranger::tracks_with_widths(tracks), move|(index, track, x1, x2), _| - Push::x((x2 - x1) as u16, Self::format_clip(scene, index, track, (x2 - x1) as u16, height)) + Push::x((x2 - x1) as u16, Self::cell_clip(scene, index, track, (x2 - x1) as u16, height)) ); Fixed::y(height, Bsp::e(icon, Bsp::e(name, clips))) } - fn format_clip <'a> ( + fn cell_clip <'a> ( scene: &'a ArrangerScene, index: usize, track: &'a ArrangerTrack, w: u16, h: u16 ) -> impl Content + use<'a> { scene.clips.get(index).map(|clip|clip.as_ref().map(|phrase|{ @@ -177,7 +188,7 @@ impl Arranger { ////let selectors = When(false, Bsp::e(ClipSelected::play_phrase(&self.player), ClipSelected::next_phrase(&self.player))); //row!([>selectors,<] edit_clip, MidiEditStatus(&self.editor)) } - fn selector_view (&self) -> impl Content + use<'_> { + fn editor_status_view (&self) -> impl Content + use<'_> { Bsp::e( //ClipSelected::play_phrase(&self.player), //ClipSelected::next_phrase(&self.player), @@ -192,49 +203,6 @@ impl Arranger { let pool = Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool)))); Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool)))) } - /// name and width of track - fn format_name (track: &ArrangerTrack, _w: usize) -> impl Content { - let name = track.name().read().unwrap().clone(); - Tui::bold(true, Tui::fg(track.color.lightest.rgb, name)) - } - /// beats elapsed - fn format_elapsed (track: &ArrangerTrack, timebase: &Arc) -> impl Content { - let mut result = String::new(); - 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 - ); - result = format!("+{elapsed:>}") - } - result - } - /// beats until switchover - fn format_until_next (track: &ArrangerTrack, current: &Arc) - -> Option> - { - let timebase = ¤t.timebase; - let mut result = String::new(); - if let Some((t, _)) = track.player.next_phrase().as_ref() { - let target = t.pulse.get(); - let current = current.pulse.get(); - if target > current { - let remaining = target - current; - result = format!("-{:>}", timebase.format_beats_0_short(remaining)) - } - } - Some(result) - } - fn format_input (track: &ArrangerTrack) -> Usually> { - Ok(format!(">{}", track.player.midi_ins().first().map(|port|port.short_name()) - .transpose()?.unwrap_or("?".into()))) - } - /// output port - fn format_output (track: &ArrangerTrack) -> Usually> { - Ok(format!("<{}", track.player.midi_outs().first().map(|port|port.short_name()) - .transpose()?.unwrap_or("?".into()))) - } fn track_col_sep <'a> (&'a self) -> impl Content + 'a { let fg = TuiTheme::separator_fg(false); @@ -441,3 +409,44 @@ impl Arranger { //}; //} //} +//impl Arranger { + //fn render_mode (state: &Self) -> impl Content + use<'_> { + //match state.mode { + //ArrangerMode::H => todo!("horizontal arranger"), + //ArrangerMode::V(factor) => Self::render_mode_v(state, factor), + //} + //} +//} +//render!(TuiOut: (self: Arranger) => { + //let pool_w = if self.pool.visible { self.splits[1] } else { 0 }; + //let color = self.color; + //let layout = Bsp::a(Fill::xy(ArrangerStatus::from(self)), + //Bsp::n(Fixed::x(pool_w, PoolView(self.pool.visible, &self.pool)), + //Bsp::n(TransportView::new(true, &self.clock), + //Bsp::s(Fixed::y(1, MidiEditStatus(&self.editor)), + //Bsp::n(Fill::x(Fixed::y(20, + //Bsp::a(Fill::xy(Tui::bg(color.darkest.rgb, "background")), + //Bsp::a( + //Fill::xy(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))), + //Self::render_mode(self))))), Fill::y(&"fixme: self.editor")))))); + //self.size.of(layout) +//}); + //Align::n(Fill::xy(lay!( + //Align::n(Fill::xy(Tui::bg(self.color.darkest.rgb, " "))), + //Align::n(Fill::xy(ArrangerVRowSep::from((self, 1)))), + //Align::n(Fill::xy(ArrangerVColSep::from(self))), + //Align::n(Fill::xy(ArrangerVClips::new(self, 1))), + //Align::n(Fill::xy(ArrangerVCursor::from((self, 1)))))))))))))))); + //Align::n(Fill::xy(":"))))))))))))); + //"todo:")))))))); + //Bsp::s( + //Align::n(Fixed::y(1, Fill::x(ArrangerVIns::from(self)))), + //Bsp::s( + //Fixed::y(20, Align::n(ArrangerVClips::new(self, 1))), + //Fill::x(Fixed::y(1, ArrangerVOuts::from(self))))))))))))); + //Bsp::s( + //Bsp::s( + //Bsp::s( + //Fill::xy(ArrangerVClips::new(self, 1)), + //Fill::x(ArrangerVOuts::from(self))))) +