From c7e7c9f68cf71e89cce3d4e6e9616ce01bfa7139 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 13:23:31 +0300 Subject: [PATCH 01/17] switch around ownership of pool and editort --- crates/app/src/api.rs | 46 ++++---- crates/app/src/audio.rs | 2 +- crates/app/src/model.rs | 65 ++++++------ crates/app/src/view.rs | 18 ++-- crates/cli/tek.rs | 12 +-- crates/device/src/arranger/arranger_model.rs | 35 +++---- crates/device/src/arranger/arranger_view.rs | 105 ++++++++++--------- crates/device/src/editor/editor_model.rs | 24 +++-- 8 files changed, 157 insertions(+), 150 deletions(-) diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index 3a4516dd..acdadca6 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -24,9 +24,10 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm Selection::TrackClip { track, scene } => { let clip = &mut app.scenes_mut()[scene].clips[track]; if clip.is_none() { + //app.clip_auto_create(); *clip = Some(Default::default()); } - app.editor = clip.as_ref().map(|c|c.into()); + app.project.editor = clip.as_ref().map(|c|c.into()); None } _ => None @@ -62,18 +63,19 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm //} fn select (app: &mut App, selection: Selection) -> Perhaps { *app.project.selection_mut() = selection; - if let Some(ref mut editor) = app.editor { - editor.set_clip(match app.project.selection() { - Selection::TrackClip { track, scene } if let Some(Some(Some(clip))) = app - .project - .scenes.get(*scene) - .map(|s|s.clips.get(*track)) - => - Some(clip), - _ => - None - }); - } + //todo! + //if let Some(ref mut editor) = app.editor_mut() { + //editor.set_clip(match selection { + //Selection::TrackClip { track, scene } if let Some(Some(Some(clip))) = app + //.project + //.scenes.get(scene) + //.map(|s|s.clips.get(track)) + //=> + //Some(clip), + //_ => + //None + //}); + //} Ok(None) //("select" [t: usize, s: usize] Some(match (t.expect("no track"), s.expect("no scene")) { //(0, 0) => Self::Select(Selection::Mix), @@ -102,7 +104,7 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm Ok(command.delegate(app, |command|Self::Message{command})?) } fn editor (app: &mut App, command: MidiEditCommand) -> Perhaps { - Ok(if let Some(editor) = app.editor.as_mut() { + Ok(if let Some(editor) = app.editor_mut() { let undo = command.clone().delegate(editor, |command|AppCommand::Editor{command})?; // update linked sampler after editor action app.project.sampler_mut().map(|sampler|match command { @@ -117,18 +119,20 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm } fn pool (app: &mut App, command: PoolCommand) -> Perhaps { let undo = command.clone().delegate( - &mut app.project.pool, + &mut app.pool, |command|AppCommand::Pool{command} )?; // update linked editor after pool action - app.editor.as_mut().map(|editor|match command { + match command { // autoselect: automatically load selected clip in editor PoolCommand::Select { .. } | // autocolor: update color in all places simultaneously - PoolCommand::Clip { command: PoolClipCommand::SetColor { .. } } => - editor.set_clip(app.project.pool.clip().as_ref()), - _ => {} - }); + PoolCommand::Clip { command: PoolClipCommand::SetColor { .. } } => { + let clip = app.pool.clip().clone(); + app.editor_mut().map(|editor|editor.set_clip(clip.as_ref())) + }, + _ => None + }; Ok(undo) } } @@ -147,7 +151,7 @@ impl<'state> Context<'state, MidiEditCommand> for App { impl<'state> Context<'state, PoolCommand> for App { fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option { - Context::get(&self.project.pool, iter) + Context::get(&self.pool, iter) } } diff --git a/crates/app/src/audio.rs b/crates/app/src/audio.rs index dd65d0af..b8d7be61 100644 --- a/crates/app/src/audio.rs +++ b/crates/app/src/audio.rs @@ -5,7 +5,7 @@ audio!( let t0 = self.perf.get_t0(); self.clock().update_from_scope(scope).unwrap(); let midi_in = self.project.midi_input_collect(scope); - if let Some(editor) = &self.editor { + if let Some(editor) = &self.editor() { let mut pitch: Option = None; for port in midi_in.iter() { for event in port.iter() { diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index df8b5ce8..b5e6ce76 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -20,23 +20,22 @@ pub struct App { pub history: Vec, // Dialog overlay pub dialog: Option, - /// Contains the currently edited MIDI clip - pub editor: Option, // Cache of formatted strings pub view_cache: Arc>, /// Base color. pub color: ItemTheme, } -has!(Jack: |self: App|self.jack); -has!(Pool: |self: App|self.pool); -has!(Clock: |self: App|self.project.clock); -has!(Selection: |self: App|self.project.selection); -has!(Vec: |self: App|self.project.midi_ins); -has!(Vec: |self: App|self.project.midi_outs); -has!(Vec: |self: App|self.project.scenes); -has!(Vec: |self: App|self.project.tracks); -has!(Measure: |self: App|self.size); +has!(Jack: |self: App|self.jack); +has!(Pool: |self: App|self.pool); +has!(Clock: |self: App|self.project.clock); +has!(Option: |self: App|self.project.editor); +has!(Selection: |self: App|self.project.selection); +has!(Vec: |self: App|self.project.midi_ins); +has!(Vec: |self: App|self.project.midi_outs); +has!(Vec: |self: App|self.project.scenes); +has!(Vec: |self: App|self.project.tracks); +has!(Measure: |self: App|self.size); maybe_has!(Track: |self: App| { MaybeHas::::get(&self.project) }; { MaybeHas::::get_mut(&mut self.project) }); @@ -49,19 +48,19 @@ maybe_has!(Scene: |self: App| impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } } -has_clips!(|self: App|self.project.pool.clips); -has_editor!(|self: App|{ - editor = self.editor; - editor_w = { - let size = self.size.w(); - let editor = self.editor.as_ref().expect("missing editor"); - let time_len = editor.time_len().get(); - let time_zoom = editor.time_zoom().get().max(1); - (5 + (time_len / time_zoom)).min(size.saturating_sub(20)).max(16) - }; - editor_h = 15; - is_editing = self.editor.is_some(); -}); +has_clips!(|self: App|self.pool.clips); +//has_editor!(|self: App|{ + //editor = self.editor; + //editor_w = { + //let size = self.size.w(); + //let editor = self.editor.as_ref().expect("missing editor"); + //let time_len = editor.time_len().get(); + //let time_zoom = editor.time_zoom().get().max(1); + //(5 + (time_len / time_zoom)).min(size.saturating_sub(20)).max(16) + //}; + //editor_h = 15; + //is_editing = self.editor.is_some(); +//}); impl App { pub fn toggle_dialog (&mut self, mut dialog: Option) -> Option { @@ -70,7 +69,7 @@ impl App { } pub fn toggle_editor (&mut self, value: Option) { //FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed); - let value = value.unwrap_or_else(||!self.editor.is_some()); + let value = value.unwrap_or_else(||!self.editor().is_some()); if value { self.clip_auto_create(); } else { @@ -130,11 +129,11 @@ impl App { && slot.is_none() && let Some(track) = self.project.tracks.get_mut(track) { - let (index, mut clip) = self.project.pool.add_new_clip(); + let (index, mut clip) = self.pool.add_new_clip(); // autocolor: new clip colors from scene and track color let color = track.color.base.mix(scene.color.base, 0.5); clip.write().unwrap().color = ItemColor::random_near(color, 0.2).into(); - if let Some(ref mut editor) = self.editor { + if let Some(ref mut editor) = &mut self.project.editor { editor.set_clip(Some(&clip)); } *slot = Some(clip.clone()); @@ -155,7 +154,7 @@ impl App { std::mem::swap(&mut swapped, slot); } if let Some(clip) = swapped { - self.project.pool.delete_clip(&clip.read().unwrap()); + self.pool.delete_clip(&clip.read().unwrap()); } } } @@ -199,7 +198,7 @@ impl App { todo!() } fn w_sidebar (&self) -> u16 { - self.project.w_sidebar(self.editor.is_some()) + self.project.w_sidebar(self.editor().is_some()) } fn h_sample_detail (&self) -> u16 { 6.max(self.height() as u16 * 3 / 9) @@ -232,16 +231,16 @@ impl App { !self.is_editing() && self.selection().is_mix() } fn focus_pool_import (&self) -> bool { - matches!(self.project.pool.mode, Some(PoolMode::Import(..))) + matches!(self.pool.mode, Some(PoolMode::Import(..))) } fn focus_pool_export (&self) -> bool { - matches!(self.project.pool.mode, Some(PoolMode::Export(..))) + matches!(self.pool.mode, Some(PoolMode::Export(..))) } fn focus_pool_rename (&self) -> bool { - matches!(self.project.pool.mode, Some(PoolMode::Rename(..))) + matches!(self.pool.mode, Some(PoolMode::Rename(..))) } fn focus_pool_length (&self) -> bool { - matches!(self.project.pool.mode, Some(PoolMode::Length(..))) + matches!(self.pool.mode, Some(PoolMode::Length(..))) } fn dialog_none (&self) -> Option { None diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 4ea5322c..2878985e 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -83,7 +83,7 @@ impl App { self.project.view_scenes_names() } pub fn view_arranger_scenes_clips (&self) -> impl Content + use<'_> { - self.project.view_scenes_clips(&self.editor) + self.project.view_scenes_clips() } pub fn view_arranger_track_names (&self) -> impl Content + use<'_> { self.project.view_track_names(self.color) @@ -117,7 +117,7 @@ impl App { Fixed::x(20, Bsp::s( Fill::x(Align::w(FieldH(self.color, "Clip pool:", ""))), Fill::y(Align::n(Tui::bg(Rgb(0, 0, 0), Outer(true, Style::default().fg(Tui::g(96))) - .enclose(PoolView(&self.project.pool))))))) + .enclose(PoolView(&self.pool))))))) } pub fn view_samples_keys (&self) -> impl Content + use<'_> { self.project.sampler().map(|s|s.view_list(true, self.editor().unwrap())) @@ -243,20 +243,14 @@ impl ScenesView for App { fn arrangement (&self) -> &Arrangement { &self.project } - fn scenes_height (&self) -> u16 { + fn h_scenes (&self) -> u16 { (self.height() as u16).saturating_sub(20) } - fn width_side (&self) -> u16 { + fn w_side (&self) -> u16 { 20 } - fn width_mid (&self) -> u16 { - (self.width() as u16).saturating_sub(self.width_side()) - } - fn scene_selected (&self) -> Option { - self.project.selection.scene() - } - fn track_selected (&self) -> Option { - self.project.selection.track() + fn w_mid (&self) -> u16 { + (self.width() as u16).saturating_sub(self.w_side()) } } diff --git a/crates/cli/tek.rs b/crates/cli/tek.rs index a99f86db..1cb5dc7e 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -122,9 +122,9 @@ impl Cli { jack: jack.clone(), config, color: ItemTheme::random(), - editor: match self.mode { - LaunchMode::Sequencer | LaunchMode::Groovebox => Some((&clip).into()), - _ => None + pool: match self.mode { + LaunchMode::Sequencer | LaunchMode::Groovebox => (&clip).into(), + _ => Default::default() }, project: Arrangement { name: Default::default(), @@ -136,9 +136,9 @@ impl Cli { selection: Selection::TrackClip { track: 0, scene: 0 }, midi_ins, midi_outs, - pool: match self.mode { - LaunchMode::Sequencer | LaunchMode::Groovebox => (&clip).into(), - _ => Default::default() + editor: match self.mode { + LaunchMode::Sequencer | LaunchMode::Groovebox => Some((&clip).into()), + _ => None }, ..Default::default() }, diff --git a/crates/device/src/arranger/arranger_model.rs b/crates/device/src/arranger/arranger_model.rs index a1c8a407..4bc39ecd 100644 --- a/crates/device/src/arranger/arranger_model.rs +++ b/crates/device/src/arranger/arranger_model.rs @@ -10,6 +10,8 @@ pub struct Arrangement { pub jack: Jack, /// Source of time pub clock: Clock, + /// Allows one MIDI clip to be edited + pub editor: Option, /// List of global midi inputs pub midi_ins: Vec, /// List of global midi outputs @@ -35,18 +37,17 @@ pub struct Arrangement { pub arranger: Arc>, /// Display size pub size: Measure, - /// Contains all clips in arrangement - pub pool: Pool, } -has!(Jack: |self: Arrangement|self.jack); -has!(Clock: |self: Arrangement|self.clock); -has!(Selection: |self: Arrangement|self.selection); -has!(Vec: |self: Arrangement|self.midi_ins); -has!(Vec: |self: Arrangement|self.midi_outs); -has!(Vec: |self: Arrangement|self.scenes); -has!(Vec: |self: Arrangement|self.tracks); -has!(Measure: |self: Arrangement|self.size); +has!(Jack: |self: Arrangement|self.jack); +has!(Clock: |self: Arrangement|self.clock); +has!(Selection: |self: Arrangement|self.selection); +has!(Vec: |self: Arrangement|self.midi_ins); +has!(Vec: |self: Arrangement|self.midi_outs); +has!(Vec: |self: Arrangement|self.scenes); +has!(Vec: |self: Arrangement|self.tracks); +has!(Measure: |self: Arrangement|self.size); +has!(Option: |self: Arrangement|self.editor); maybe_has!(Track: |self: Arrangement| { Has::::get(self).track().map(|index|Has::>::get(self).get(index)).flatten() }; { Has::::get(self).track().map(|index|Has::>::get_mut(self).get_mut(index)).flatten() }); @@ -215,19 +216,13 @@ impl ScenesView for Arrangement { fn arrangement (&self) -> &Arrangement { self } - fn scenes_height (&self) -> u16 { + fn h_scenes (&self) -> u16 { (self.height() as u16).saturating_sub(20) } - fn width_side (&self) -> u16 { + fn w_side (&self) -> u16 { (self.width() as u16 * 2 / 10).max(20) } - fn width_mid (&self) -> u16 { - (self.width() as u16).saturating_sub(2 * self.width_side()).max(40) - } - fn scene_selected (&self) -> Option { - self.selection().scene() - } - fn track_selected (&self) -> Option { - self.selection().track() + fn w_mid (&self) -> u16 { + (self.width() as u16).saturating_sub(2 * self.w_side()).max(40) } } diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index ac132a9f..8a1713ce 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -9,7 +9,7 @@ impl Content for Arrangement { let bg = |x|Tui::bg(Reset, x); //let track_scroll = |x|Bsp::s(&self.track_scroll, x); //let scene_scroll = |x|Bsp::e(&self.scene_scroll, x); - self.size.of(outs(tracks(devices(ins(bg(self.view_scenes_clips(&None))))))) + self.size.of(outs(tracks(devices(ins(bg(self.view_scenes_clips())))))) } } @@ -34,7 +34,7 @@ impl Arrangement { let mon = track.sequencer.monitoring; let rec = if rec { White } else { track.color.darkest.rgb }; let mon = if mon { White } else { track.color.darkest.rgb }; - let bg = if self.track_selected() == Some(t) { + let bg = if self.arrangement().selection().track() == Some(t) { track.color.light.rgb } else { track.color.base.rgb @@ -48,18 +48,18 @@ impl Arrangement { } fn view_input_routes (&self) -> impl Content + '_ { Tryptich::top(self.h_inputs()) - .left(self.width_side(), + .left(self.w_side(), io_ports(Tui::g(224), Tui::g(32), ||self.midi_ins_with_sizes())) - .middle(self.width_mid(), + .middle(self.w_mid(), per_track_top(||self.tracks_with_sizes_scrolled(), move|_, &Track { color, .. }|io_conns( color.dark.rgb, color.darker.rgb, ||self.midi_ins_with_sizes()))) } fn view_input_intos (&self) -> impl Content + '_ { Tryptich::top(2) - .left(self.width_side(), + .left(self.w_side(), Bsp::s(Align::e("Input:"), Align::e("Into clip:"))) - .middle(self.width_mid(), + .middle(self.w_mid(), per_track_top(||self.tracks_with_sizes_scrolled(), |_, _|Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ "))))) } @@ -72,9 +72,9 @@ impl Arrangement { } fn view_output_ports (&self) -> impl Content + '_ { Tryptich::top(1) - .left(self.width_side(), self.view_output_count()) - .right(self.width_side(), self.view_output_add()) - .middle(self.width_mid(), self.view_output_map()) + .left(self.w_side(), self.view_output_count()) + .right(self.w_side(), self.view_output_add()) + .middle(self.w_mid(), self.view_output_map()) } fn view_output_count (&self) -> impl Content { button_3( @@ -93,7 +93,7 @@ impl Arrangement { let solo = false; let mute = if mute { White } else { t.color.darkest.rgb }; let solo = if solo { White } else { t.color.darkest.rgb }; - let bg_1 = if self.track_selected() == Some(i) { + let bg_1 = if self.arrangement().selection().track() == Some(i) { t.color.light.rgb } else { t.color.base.rgb @@ -106,9 +106,9 @@ impl Arrangement { } fn view_output_conns (&self) -> impl Content + '_ { Tryptich::top(self.h_outputs()) - .left(self.width_side(), io_ports( + .left(self.w_side(), io_ports( Tui::g(224), Tui::g(32), ||self.midi_outs_with_sizes())) - .middle(self.width_mid(), per_track_top(||self.tracks_with_sizes_scrolled(), + .middle(self.w_mid(), per_track_top(||self.tracks_with_sizes_scrolled(), |_, t|io_conns( t.color.dark.rgb, t.color.darker.rgb, @@ -116,14 +116,14 @@ impl Arrangement { ))) } fn view_output_nexts (&self) -> impl Content + '_ { - Tryptich::top(2).left(self.width_side(), Align::ne("From clip:")) - .middle(self.width_mid(), per_track_top(||self.tracks_with_sizes_scrolled(), + Tryptich::top(2).left(self.w_side(), Align::ne("From clip:")) + .middle(self.w_mid(), per_track_top(||self.tracks_with_sizes_scrolled(), |_, _|Tui::bg(Reset, Align::c(Bsp::s(" ------ ", OctaveVertical::default()))))) } fn view_output_froms (&self) -> impl Content + '_ { let label = Align::ne("Next clip:"); - Tryptich::top(2).left(self.width_side(), label) - .middle(self.width_mid(), per_track_top( + Tryptich::top(2).left(self.w_side(), label) + .middle(self.w_mid(), per_track_top( ||self.tracks_with_sizes_scrolled(), |t, track|{ let queued = track.sequencer.next_clip.is_some(); let queued_blank = Thunk::new(||Tui::bg(Reset, " ------ ")); @@ -143,15 +143,15 @@ impl Arrangement { } /// Render track headers fn view_tracks_0 (&self) -> impl Content + '_ { - let width_side = self.width_side(); - let width_mid = self.width_mid(); + let w_side = self.w_side(); + let w_mid = self.w_mid(); let is_editing = false; // FIXME - let track_selected = self.track_selected(); + let track_selected = self.arrangement().selection().track(); Tryptich::center(3) - .left(width_side, + .left(w_side, button_3("t", "track", format!("{}", self.tracks().len()), is_editing)) - .right(width_side, button_2("T", "add track", is_editing)) - .middle(width_mid, per_track(||self.tracks_with_sizes_scrolled(), + .right(w_side, button_2("T", "add track", is_editing)) + .middle(w_mid, per_track(||self.tracks_with_sizes_scrolled(), move|index, track|wrap( if track_selected == Some(index) { track.color.light @@ -164,14 +164,14 @@ impl Arrangement { } /// Render device switches. fn view_devices_0 (&self) -> impl Content + '_ { - let width_side = self.width_side(); - let width_mid = self.width_mid(); + let w_side = self.w_side(); + let w_mid = self.w_mid(); let is_editing = false; // FIXME - let track_selected = self.track_selected(); + let track_selected = self.arrangement().selection().track(); Tryptich::top(1) - .left(width_side, button_3("d", "devices", format!("{}", 0), is_editing)) - .right(width_side, button_2("D", "add device", is_editing)) - .middle(width_mid, per_track_top(||self.tracks_with_sizes_scrolled(), + .left(w_side, button_3("d", "devices", format!("{}", 0), is_editing)) + .right(w_side, button_2("D", "add device", is_editing)) + .middle(w_mid, per_track_top(||self.tracks_with_sizes_scrolled(), move|index, track|{ let bg = if track_selected == Some(index) { track.color.light @@ -185,12 +185,13 @@ impl Arrangement { } impl TracksView for T -where T: HasSize + HasTrackScroll + HasSelection + HasMidiIns {} +where T: HasSize + HasTrackScroll + HasSelection + HasMidiIns + HasEditor {} impl ClipsView for Arrangement {} -pub trait TracksView: HasSize + HasTrackScroll + HasSelection + HasMidiIns { - fn is_editing (&self) -> bool { false } +pub trait TracksView: + HasSize + HasTrackScroll + HasSelection + HasMidiIns + HasEditor +{ fn tracks_width_available (&self) -> u16 { (self.width() as u16).saturating_sub(40) } @@ -230,19 +231,23 @@ pub trait TracksView: HasSize + HasTrackScroll + HasSelection + HasMidiI for track in self.tracks().iter() { max_outputs = max_outputs.max(track.sequencer.midi_outs.len() as u16); } - let content = Align::w(Fixed::y(max_outputs + 1, + let content = Align::w(Fixed::y(1 + max_outputs*2, Tui::bg(theme.darker.rgb, Align::w(Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self .tracks_with_sizes(&self.selection(), None) .skip(self.track_scroll()) { - (add)(&Fixed::x(track.width as u16, Align::nw(Bsp::s( - Tui::bg(track.color.base.rgb, Fill::x(Align::w(format!("[mut] [sol]")))), - Map::south(1, ||track.sequencer.midi_outs.iter(), + add(&Fixed::x(track.width as u16, Align::nw(Bsp::s( + Tui::bg(if self.selection().track() == Some(index) { + track.color.light.rgb + } else { + track.color.base.rgb + }, Fill::x(Align::w(format!("[mut] [sol]")))), + Map::south(2, ||track.sequencer.midi_outs.iter(), |port, index|Tui::fg(Rgb(255, 255, 255), - Tui::bg(track.color.dark.rgb, Fill::x(Align::w( - format!("{index}: {}", port.name())))))))))); + Fixed::y(2, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( + format!("o{index}: {}", port.name()))))))))))); } })))))); Bsp::w( @@ -270,7 +275,7 @@ pub trait TracksView: HasSize + HasTrackScroll + HasSelection + HasMidiI Fill::x(Align::w(format!("[rec] [mon]")))), Map::south(1, ||track.sequencer.midi_ins.iter(), |port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb, - Fill::x(Align::w(format!("{index}: {}", port.name()))))))))); + Fill::x(Align::w(format!("i{index}: {}", port.name()))))))))); } })))); @@ -300,28 +305,26 @@ pub trait TracksView: HasSize + HasTrackScroll + HasSelection + HasMidiI { add(&Fixed::xy(track.width as u16, h + 1, Tui::bg(track.color.dark.rgb, Align::nw(Map::south(1, move||0..h, - |_, index|format!("{index}: {}", "--------")))))); + |_, index|format!("d{index}: {}", "--------")))))); } })))))) } fn view_track_header <'a, T: Content> ( &'a self, theme: ItemTheme, content: T ) -> impl Content { - Fixed::x(20, Tui::bg(theme.darker.rgb, Fill::x(Align::e(content)))) + Fixed::x(12, Tui::bg(theme.darker.rgb, Fill::x(Align::e(content)))) } } pub trait ScenesView: HasSelection + HasSceneScroll + Send + Sync { /// Default scene height. - const H_SCENE: usize = 2; + const H_SCENE: usize = 2; /// Default editor height. const H_EDITOR: usize = 15; - fn arrangement (&self) -> &Arrangement; - fn scene_selected (&self) -> Option; - fn track_selected (&self) -> Option; - fn scenes_height (&self) -> u16; - fn width_side (&self) -> u16; - fn width_mid (&self) -> u16; + fn arrangement (&self) -> &Arrangement; + fn h_scenes (&self) -> u16; + fn w_side (&self) -> u16; + fn w_mid (&self) -> u16; fn view_scenes_names (&self) -> impl Content { Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, scene) in self.scenes().iter().enumerate().skip(self.scene_scroll()) { @@ -338,7 +341,7 @@ pub trait ScenesView: HasSelection + HasSceneScroll + Send + Sync { format!(" {index:2} "), Tui::fg(Rgb(255, 255, 255), Tui::bold(true, format!("{}", scene.name))))))) - //let height = (1 + y2 - y1) as u16; + //let height = (1 + y2 - y1 as u16; //let name = Some(scene.name.clone()); //let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⯈ ", name)))); //let selected = self.scene_selected() == Some(s); @@ -387,11 +390,11 @@ pub trait ScenesView: HasSelection + HasSceneScroll + Send + Sync { false, // FIXME self.is_editing(), Self::H_SCENE, Self::H_EDITOR, selection.track(), selection.scene(), - ).map_while(|(s, scene, y1, y2)|(y2<=self.scenes_height() as usize) + ).map_while(|(s, scene, y1, y2)|(y2<=self.h_scenes() as usize) .then_some((s, scene, y1, y2))) } /// Height required to display all scenes. - fn scenes_height_total (&self, is_editing: bool) -> u16 { + fn h_scenes_total (&self, is_editing: bool) -> u16 { self.scenes_with_sizes( is_editing, Self::H_SCENE, @@ -405,7 +408,7 @@ pub trait ScenesView: HasSelection + HasSceneScroll + Send + Sync { } pub trait ClipsView: TracksView + ScenesView + Send + Sync { - fn view_scenes_clips <'a> (&'a self, editor: &'a Option) + fn view_scenes_clips <'a> (&'a self) -> impl Content + 'a { Fill::xy(Stack::::east(move|column: &mut dyn FnMut(&dyn Render)|{ diff --git a/crates/device/src/editor/editor_model.rs b/crates/device/src/editor/editor_model.rs index f7495032..0082725c 100644 --- a/crates/device/src/editor/editor_model.rs +++ b/crates/device/src/editor/editor_model.rs @@ -103,12 +103,24 @@ impl MidiViewer for MidiEditor { fn set_clip (&mut self, p: Option<&Arc>>) { self.mode.set_clip(p) } } -pub trait HasEditor { - fn editor (&self) -> Option<&MidiEditor>; - fn editor_mut (&mut self) -> Option<&mut MidiEditor>; - fn is_editing (&self) -> bool { self.editor().is_some() } - fn editor_w (&self) -> usize { 0 } - fn editor_h (&self) -> usize { 0 } +impl>> HasEditor for T {} + +pub trait HasEditor: Has> { + fn editor (&self) -> Option<&MidiEditor> { + self.get().as_ref() + } + fn editor_mut (&mut self) -> Option<&mut MidiEditor> { + self.get_mut().as_mut() + } + fn is_editing (&self) -> bool { + self.editor().is_some() + } + fn editor_w (&self) -> usize { + self.editor().map(|e|e.size.w()).unwrap_or(0) + } + fn editor_h (&self) -> usize { + self.editor().map(|e|e.size.h()).unwrap_or(0) + } } #[macro_export] macro_rules! has_editor { From 5ff6868a172cbaf1a5a3df99419583c997a85dee Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 13:32:53 +0300 Subject: [PATCH 02/17] replug is_editing() --- crates/device/src/arranger/arranger_model.rs | 8 --- crates/device/src/arranger/arranger_view.rs | 51 +++++++++++--------- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/crates/device/src/arranger/arranger_model.rs b/crates/device/src/arranger/arranger_model.rs index 4bc39ecd..24eb1449 100644 --- a/crates/device/src/arranger/arranger_model.rs +++ b/crates/device/src/arranger/arranger_model.rs @@ -77,14 +77,6 @@ impl Arrangement { pub fn h (&self) -> u16 { self.size.h() as u16 } - /// Height taken by all inputs. - pub fn h_inputs (&self) -> u16 { - self.midi_ins_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } - /// Height taken by all outputs. - pub fn h_outputs (&self) -> u16 { - self.midi_outs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } /// Height taken by visible device slots. pub fn h_devices (&self) -> u16 { 2 diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index 8a1713ce..93909a30 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -13,7 +13,20 @@ impl Content for Arrangement { } } -impl Arrangement { +impl TracksView for T +where T: ScenesView + HasMidiIns + HasMidiOuts + HasSize + HasTrackScroll + HasSelection + HasMidiIns + HasEditor {} + +pub trait TracksView: + ScenesView + HasMidiIns + HasMidiOuts + HasSize + HasTrackScroll + HasSelection + HasMidiIns + HasEditor +{ + /// Height taken by all inputs. + fn h_inputs (&self) -> u16 { + self.midi_ins_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) + } + /// Height taken by all outputs. + fn h_outputs (&self) -> u16 { + self.midi_outs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) + } /// Render input matrix. fn view_inputs_0 (&self) -> impl Content + '_ { Tui::bg(Reset, Bsp::s( @@ -22,7 +35,7 @@ impl Arrangement { )) } fn view_input_ports (&self) -> impl Content + '_ { - let is_editing = false; //FIXME + let is_editing = self.is_editing(); Tryptich::top(1) .left(20, button_3("i", "midi ins", format!("{}", self.midi_ins().len()), is_editing)) @@ -77,15 +90,12 @@ impl Arrangement { .middle(self.w_mid(), self.view_output_map()) } fn view_output_count (&self) -> impl Content { - button_3( - "o", - "midi outs", - format!("{}", self.midi_outs().len()), - false // self.is_editing() + button_3("o", "midi outs", format!("{}", self.midi_outs().len()), + self.is_editing() ) } fn view_output_add (&self) -> impl Content { - button_2("O", "add midi out", false /* is_editing */) + button_2("O", "add midi out", self.is_editing()) } fn view_output_map (&self) -> impl Content + '_ { per_track_top(||self.tracks_with_sizes_scrolled(), move|i, t|{ @@ -145,7 +155,7 @@ impl Arrangement { fn view_tracks_0 (&self) -> impl Content + '_ { let w_side = self.w_side(); let w_mid = self.w_mid(); - let is_editing = false; // FIXME + let is_editing = self.is_editing(); let track_selected = self.arrangement().selection().track(); Tryptich::center(3) .left(w_side, @@ -166,7 +176,7 @@ impl Arrangement { fn view_devices_0 (&self) -> impl Content + '_ { let w_side = self.w_side(); let w_mid = self.w_mid(); - let is_editing = false; // FIXME + let is_editing = self.is_editing(); let track_selected = self.arrangement().selection().track(); Tryptich::top(1) .left(w_side, button_3("d", "devices", format!("{}", 0), is_editing)) @@ -182,16 +192,6 @@ impl Arrangement { track.devices.get(0).map(|device|wrap(bg.rgb, fg, device.name())) })) } -} - -impl TracksView for T -where T: HasSize + HasTrackScroll + HasSelection + HasMidiIns + HasEditor {} - -impl ClipsView for Arrangement {} - -pub trait TracksView: - HasSize + HasTrackScroll + HasSelection + HasMidiIns + HasEditor -{ fn tracks_width_available (&self) -> u16 { (self.width() as u16).saturating_sub(40) } @@ -316,7 +316,7 @@ pub trait TracksView: } } -pub trait ScenesView: HasSelection + HasSceneScroll + Send + Sync { +pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + Send + Sync { /// Default scene height. const H_SCENE: usize = 2; /// Default editor height. @@ -394,9 +394,9 @@ pub trait ScenesView: HasSelection + HasSceneScroll + Send + Sync { .then_some((s, scene, y1, y2))) } /// Height required to display all scenes. - fn h_scenes_total (&self, is_editing: bool) -> u16 { + fn h_scenes_total (&self) -> u16 { self.scenes_with_sizes( - is_editing, + self.is_editing(), Self::H_SCENE, Self::H_EDITOR, self.selection().track(), @@ -407,7 +407,10 @@ pub trait ScenesView: HasSelection + HasSceneScroll + Send + Sync { } } +impl ClipsView for T {} + pub trait ClipsView: TracksView + ScenesView + Send + Sync { + fn view_scenes_clips <'a> (&'a self) -> impl Content + 'a { @@ -498,6 +501,7 @@ pub trait ClipsView: TracksView + ScenesView + Send + Sync { Align::w(Fill::x(Map::new(||self.scenes().iter().skip(self.scene_scroll()), move|scene: &'a Scene, index|self.track_scenes(index, scene)))))) } + fn track_scenes <'a> ( &'a self, scene_index: usize, @@ -508,6 +512,7 @@ pub trait ClipsView: TracksView + ScenesView + Send + Sync { move|clip: &'a Option>>, track_index| self.track_scene_clip(scene_index, scene, track_index, clip)))) } + fn track_scene_clip ( &self, scene_index: usize, From d3d60d69c7a376e4783b8b9a26955e5520aa1602 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 13:38:27 +0300 Subject: [PATCH 03/17] scene: enlarge height --- crates/device/src/arranger/arranger_view.rs | 22 +++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index 93909a30..ed74c752 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -333,7 +333,11 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + Send + Sync { }) } fn view_scene_name (&self, index: usize, scene: &Scene) -> impl Content { - Fixed::xy(20, 2, Tui::bg(if self.selection().scene() == Some(index) { + Fixed::xy(20, if self.selection().scene() == Some(index) && let Some(editor) = self.editor () { + (editor.height() as u16).max(12) + } else { + Self::H_SCENE as u16 + }, Tui::bg(if self.selection().scene() == Some(index) { scene.color.light.rgb } else { scene.color.base.rgb @@ -452,9 +456,19 @@ pub trait ClipsView: TracksView + ScenesView + Send + Sync { } else { theme.dark.rgb }; - cell(&Fixed::xy(track.width as u16, 2, Bsp::b( - Fill::xy(Outer(true, Style::default().fg(outline))), - Fill::xy(Align::nw(Tui::fg_bg(fg, bg, Align::nw(name.unwrap_or(" ---- ".into())))))))); + cell(&Fixed::xy( + track.width as u16, + if self.selection().scene() == Some(scene_index) + && let Some(editor) = self.editor () + { + (editor.height() as u16).max(12) + } else { + Self::H_SCENE as u16 + }, + Bsp::b( + Fill::xy(Outer(true, Style::default().fg(outline))), + Fill::xy(Align::nw(Tui::fg_bg(fg, bg, Align::nw(name.unwrap_or(" ---- ".into()))))) + ))); //let (name, theme) = if let Some(clip) = &scene.clips.get(track_index).flatten() { //let clip = clip.read().unwrap(); //(Some(clip.name.clone()), clip.color) From b0393184fa2458972ac5fe732f3c4ffb5c89efd1 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 13:41:49 +0300 Subject: [PATCH 04/17] expand trackwards --- crates/device/src/arranger/arranger_view.rs | 22 ++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index ed74c752..47a1a64e 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -425,7 +425,13 @@ pub trait ClipsView: TracksView + ScenesView + Send + Sync { { //column(&Fixed::x(5, Fill::xy(Tui::bg(Green, "kyp")))); column(&Fixed::x( - track.width as u16, + if self.selection().track() == Some(track_index) + && let Some(editor) = self.editor () + { + editor.width().max(24).max(track.width) + } else { + track.width + } as u16, Fill::y(self.view_track_clips(track_index, track)) )) } @@ -457,14 +463,20 @@ pub trait ClipsView: TracksView + ScenesView + Send + Sync { theme.dark.rgb }; cell(&Fixed::xy( - track.width as u16, + if self.selection().track() == Some(track_index) + && let Some(editor) = self.editor () + { + editor.width().max(24).max(track.width) + } else { + track.width + } as u16, if self.selection().scene() == Some(scene_index) && let Some(editor) = self.editor () { - (editor.height() as u16).max(12) + editor.height().max(12) } else { - Self::H_SCENE as u16 - }, + Self::H_SCENE + } as u16, Bsp::b( Fill::xy(Outer(true, Style::default().fg(outline))), Fill::xy(Align::nw(Tui::fg_bg(fg, bg, Align::nw(name.unwrap_or(" ---- ".into()))))) From 4f3a50f2d65978dab98371734a42626a3c208d35 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 13:47:07 +0300 Subject: [PATCH 05/17] arranger: editor now toggles --- crates/app/src/api.rs | 28 +++++++++++++-------- crates/device/src/arranger/arranger_view.rs | 1 + 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index acdadca6..23e584b3 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -19,19 +19,25 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm #[tengri_proc::command(App)] impl AppCommand { fn edit (app: &mut App) -> Perhaps { - let selection = app.selection().clone(); - Ok(match selection { - Selection::TrackClip { track, scene } => { - let clip = &mut app.scenes_mut()[scene].clips[track]; - if clip.is_none() { - //app.clip_auto_create(); - *clip = Some(Default::default()); + app.project.editor = if app.project.editor.is_some() { + None + } else { + let selection = app.selection().clone(); + match selection { + Selection::TrackClip { track, scene } => { + let clip = &mut app.scenes_mut()[scene].clips[track]; + if clip.is_none() { + //app.clip_auto_create(); + *clip = Some(Default::default()); + } + clip.as_ref().map(|c|c.into()) + } + _ => { + None } - app.project.editor = clip.as_ref().map(|c|c.into()); - None } - _ => None - }) + }; + Ok(None) } fn dialog (app: &mut App, dialog: Option) -> Perhaps { app.toggle_dialog(dialog); diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index 47a1a64e..a08ceddb 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -2,6 +2,7 @@ use crate::*; impl Content for Arrangement { fn content (&self) -> impl Render { + panic!(); let ins = |x|Bsp::n(self.view_inputs_0(), x); let tracks = |x|Bsp::s(self.view_tracks_0(), x); let devices = |x|Bsp::s(self.view_devices_0(), x); From 5ed69edd028adf75c407e7a2c8c3e819870209d6 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 13:54:05 +0300 Subject: [PATCH 06/17] editor: 19x11 wat? but shows --- crates/app/src/api.rs | 4 +++- crates/app/src/view.rs | 3 --- crates/device/src/arranger/arranger_view.rs | 23 ++++++--------------- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index 23e584b3..4ee8b868 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -28,7 +28,9 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm let clip = &mut app.scenes_mut()[scene].clips[track]; if clip.is_none() { //app.clip_auto_create(); - *clip = Some(Default::default()); + *clip = Some(Arc::new(RwLock::new(MidiClip::new( + "", false, 384, None, Some(ItemTheme::random()) + )))); } clip.as_ref().map(|c|c.into()) } diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 2878985e..30665305 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -76,9 +76,6 @@ impl App { pub fn view_audio_outs_status (&self) -> impl Content + use<'_> { self.project.view_audio_outs_status(self.color) } - pub fn view_arranger (&self) -> impl Content + use<'_> { - &self.project - } pub fn view_arranger_scenes_names (&self) -> impl Content + use<'_> { self.project.view_scenes_names() } diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index a08ceddb..04977aac 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -1,19 +1,5 @@ use crate::*; -impl Content for Arrangement { - fn content (&self) -> impl Render { - panic!(); - let ins = |x|Bsp::n(self.view_inputs_0(), x); - let tracks = |x|Bsp::s(self.view_tracks_0(), x); - let devices = |x|Bsp::s(self.view_devices_0(), x); - let outs = |x|Bsp::s(self.view_outputs_0(), x); - let bg = |x|Tui::bg(Reset, x); - //let track_scroll = |x|Bsp::s(&self.track_scroll, x); - //let scene_scroll = |x|Bsp::e(&self.scene_scroll, x); - self.size.of(outs(tracks(devices(ins(bg(self.view_scenes_clips())))))) - } -} - impl TracksView for T where T: ScenesView + HasMidiIns + HasMidiOuts + HasSize + HasTrackScroll + HasSelection + HasMidiIns + HasEditor {} @@ -439,7 +425,7 @@ pub trait ClipsView: TracksView + ScenesView + Send + Sync { })) } - fn view_track_clips <'a> (&'a self, track_index: usize, track: &Track) -> impl Content { + fn view_track_clips <'a> (&'a self, track_index: usize, track: &'a Track) -> impl Content + 'a { Stack::south(move|cell: &mut dyn FnMut(&dyn Render)|{ for (scene_index, scene) in self.scenes().iter().enumerate().skip(self.scene_scroll()) { let (name, theme) = if let Some(Some(clip)) = &scene.clips.get(track_index) { @@ -480,8 +466,11 @@ pub trait ClipsView: TracksView + ScenesView + Send + Sync { } as u16, Bsp::b( Fill::xy(Outer(true, Style::default().fg(outline))), - Fill::xy(Align::nw(Tui::fg_bg(fg, bg, Align::nw(name.unwrap_or(" ---- ".into()))))) - ))); + Fill::xy(Bsp::b( + Fill::xy(Align::nw(Tui::fg_bg(fg, bg, Align::nw(name.unwrap_or(" ---- ".into()))))), + Fill::xy(When(self.selection().track() == Some(track_index) + && self.selection().scene() == Some(scene_index) + && self.is_editing(), self.editor()))))))); //let (name, theme) = if let Some(clip) = &scene.clips.get(track_index).flatten() { //let clip = clip.read().unwrap(); //(Some(clip.name.clone()), clip.color) From ef6aa9ab0733aa6e3bfe8f1bd9696ea26321e2be Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 15:02:38 +0300 Subject: [PATCH 07/17] arranger: tweaks, incl. remove unused rendering code --- config/config_arranger.edn | 2 +- config/keys_arranger.edn | 1 + crates/app/src/model.rs | 4 +- crates/app/src/view.rs | 3 - crates/device/src/arranger/arranger_model.rs | 9 +- crates/device/src/arranger/arranger_view.rs | 334 +++---------------- crates/device/src/editor/editor_view.rs | 12 +- 7 files changed, 65 insertions(+), 300 deletions(-) diff --git a/config/config_arranger.edn b/config/config_arranger.edn index 2b67db1c..6249a06a 100644 --- a/config/config_arranger.edn +++ b/config/config_arranger.edn @@ -26,5 +26,5 @@ (bsp/s :view-arranger-track-outputs (bsp/s :view-arranger-track-devices :view-arranger-track-inputs))))))) (fill/xy (bsp/e - (bsp/n (max/y 9 :view-editor-status) (fixed/x 20 (align/nw :view-arranger-scenes-names))) + (fixed/x 20 (align/nw :view-arranger-scenes-names)) :view-arranger-scenes-clips))))) diff --git a/config/keys_arranger.edn b/config/keys_arranger.edn index 0199a07b..23da3a49 100644 --- a/config/keys_arranger.edn +++ b/config/keys_arranger.edn @@ -1,6 +1,7 @@ (@c color) (@q launch) (@t select :select-track-header) +(@s select :select-scene-header) (@tab edit :clip-selected) (@shift-I input add) (@shift-O output add) diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index b5e6ce76..58c8eeab 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -204,10 +204,10 @@ impl App { 6.max(self.height() as u16 * 3 / 9) } fn focus_editor (&self) -> bool { - self.is_editing() + self.project.editor.is_some() } fn is_editing (&self) -> bool { - HasEditor::is_editing(self) + self.project.editor.is_some() } fn focus_message (&self) -> bool { matches!(self.dialog, Some(Dialog::Message(..))) diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 30665305..8801a26f 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -237,9 +237,6 @@ impl App { } impl ScenesView for App { - fn arrangement (&self) -> &Arrangement { - &self.project - } fn h_scenes (&self) -> u16 { (self.height() as u16).saturating_sub(20) } diff --git a/crates/device/src/arranger/arranger_model.rs b/crates/device/src/arranger/arranger_model.rs index 24eb1449..d0a4698e 100644 --- a/crates/device/src/arranger/arranger_model.rs +++ b/crates/device/src/arranger/arranger_model.rs @@ -205,16 +205,13 @@ impl Arrangement { } impl ScenesView for Arrangement { - fn arrangement (&self) -> &Arrangement { - self - } - fn h_scenes (&self) -> u16 { + fn h_scenes (&self) -> u16 { (self.height() as u16).saturating_sub(20) } - fn w_side (&self) -> u16 { + fn w_side (&self) -> u16 { (self.width() as u16 * 2 / 10).max(20) } - fn w_mid (&self) -> u16 { + fn w_mid (&self) -> u16 { (self.width() as u16).saturating_sub(2 * self.w_side()).max(40) } } diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index 04977aac..0488314b 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -6,179 +6,6 @@ where T: ScenesView + HasMidiIns + HasMidiOuts + HasSize + HasTrackScrol pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasSize + HasTrackScroll + HasSelection + HasMidiIns + HasEditor { - /// Height taken by all inputs. - fn h_inputs (&self) -> u16 { - self.midi_ins_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } - /// Height taken by all outputs. - fn h_outputs (&self) -> u16 { - self.midi_outs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } - /// Render input matrix. - fn view_inputs_0 (&self) -> impl Content + '_ { - Tui::bg(Reset, Bsp::s( - self.view_input_intos(), - Bsp::s(self.view_input_routes(), self.view_input_ports()), - )) - } - fn view_input_ports (&self) -> impl Content + '_ { - let is_editing = self.is_editing(); - Tryptich::top(1) - .left(20, button_3("i", "midi ins", format!("{}", - self.midi_ins().len()), is_editing)) - .right(20, button_2("I", "add midi in", is_editing)) - .middle(self.width().saturating_sub(40) as u16, - per_track_top(||self.tracks_with_sizes_scrolled(), - move|t, track|{ - let rec = track.sequencer.recording; - let mon = track.sequencer.monitoring; - let rec = if rec { White } else { track.color.darkest.rgb }; - let mon = if mon { White } else { track.color.darkest.rgb }; - let bg = if self.arrangement().selection().track() == Some(t) { - track.color.light.rgb - } else { - track.color.base.rgb - }; - //let bg2 = if t > 0 { track.color.base.rgb } else { Reset }; - wrap(bg, Tui::g(224), Tui::bold(true, Fill::x(Bsp::e( - Tui::fg_bg(rec, bg, "Rec "), - Tui::fg_bg(mon, bg, "Mon "), - )))) - })) - } - fn view_input_routes (&self) -> impl Content + '_ { - Tryptich::top(self.h_inputs()) - .left(self.w_side(), - io_ports(Tui::g(224), Tui::g(32), ||self.midi_ins_with_sizes())) - .middle(self.w_mid(), - per_track_top(||self.tracks_with_sizes_scrolled(), - move|_, &Track { color, .. }|io_conns( - color.dark.rgb, color.darker.rgb, ||self.midi_ins_with_sizes()))) - } - fn view_input_intos (&self) -> impl Content + '_ { - Tryptich::top(2) - .left(self.w_side(), - Bsp::s(Align::e("Input:"), Align::e("Into clip:"))) - .middle(self.w_mid(), - per_track_top(||self.tracks_with_sizes_scrolled(), - |_, _|Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ "))))) - } - /// Render output matrix. - fn view_outputs_0 (&self) -> impl Content + '_ { - Tui::bg(Reset, Align::n(Bsp::s( - Bsp::s(self.view_output_ports(), self.view_output_conns()), - Bsp::s(self.view_output_nexts(), self.view_output_froms()), - ))) - } - fn view_output_ports (&self) -> impl Content + '_ { - Tryptich::top(1) - .left(self.w_side(), self.view_output_count()) - .right(self.w_side(), self.view_output_add()) - .middle(self.w_mid(), self.view_output_map()) - } - fn view_output_count (&self) -> impl Content { - button_3("o", "midi outs", format!("{}", self.midi_outs().len()), - self.is_editing() - ) - } - fn view_output_add (&self) -> impl Content { - button_2("O", "add midi out", self.is_editing()) - } - fn view_output_map (&self) -> impl Content + '_ { - per_track_top(||self.tracks_with_sizes_scrolled(), move|i, t|{ - let mute = false; - let solo = false; - let mute = if mute { White } else { t.color.darkest.rgb }; - let solo = if solo { White } else { t.color.darkest.rgb }; - let bg_1 = if self.arrangement().selection().track() == Some(i) { - t.color.light.rgb - } else { - t.color.base.rgb - }; - let bg_2 = if i > 0 { t.color.base.rgb } else { Reset }; - let mute = Tui::fg_bg(mute, bg_1, "Play "); - let solo = Tui::fg_bg(solo, bg_1, "Solo "); - wrap(bg_1, Tui::g(224), Tui::bold(true, Fill::x(Bsp::e(mute, solo)))) - }) - } - fn view_output_conns (&self) -> impl Content + '_ { - Tryptich::top(self.h_outputs()) - .left(self.w_side(), io_ports( - Tui::g(224), Tui::g(32), ||self.midi_outs_with_sizes())) - .middle(self.w_mid(), per_track_top(||self.tracks_with_sizes_scrolled(), - |_, t|io_conns( - t.color.dark.rgb, - t.color.darker.rgb, - ||self.midi_outs_with_sizes() - ))) - } - fn view_output_nexts (&self) -> impl Content + '_ { - Tryptich::top(2).left(self.w_side(), Align::ne("From clip:")) - .middle(self.w_mid(), per_track_top(||self.tracks_with_sizes_scrolled(), - |_, _|Tui::bg(Reset, Align::c(Bsp::s(" ------ ", OctaveVertical::default()))))) - } - fn view_output_froms (&self) -> impl Content + '_ { - let label = Align::ne("Next clip:"); - Tryptich::top(2).left(self.w_side(), label) - .middle(self.w_mid(), per_track_top( - ||self.tracks_with_sizes_scrolled(), |t, track|{ - let queued = track.sequencer.next_clip.is_some(); - let queued_blank = Thunk::new(||Tui::bg(Reset, " ------ ")); - let queued_clip = Thunk::new(||{ - Tui::bg(Reset, if let Some((_, clip)) = track.sequencer.next_clip.as_ref() { - if let Some(clip) = clip { - clip.read().unwrap().name.clone() - } else { - "Stop".into() - } - } else { - "".into() - }) - }); - Either(queued, queued_clip, queued_blank) - })) - } - /// Render track headers - fn view_tracks_0 (&self) -> impl Content + '_ { - let w_side = self.w_side(); - let w_mid = self.w_mid(); - let is_editing = self.is_editing(); - let track_selected = self.arrangement().selection().track(); - Tryptich::center(3) - .left(w_side, - button_3("t", "track", format!("{}", self.tracks().len()), is_editing)) - .right(w_side, button_2("T", "add track", is_editing)) - .middle(w_mid, per_track(||self.tracks_with_sizes_scrolled(), - move|index, track|wrap( - if track_selected == Some(index) { - track.color.light - } else { - track.color.base - }.rgb, - track.color.lightest.rgb, - Tui::bold(true, Fill::xy(Align::nw(&track.name))) - ))) - } - /// Render device switches. - fn view_devices_0 (&self) -> impl Content + '_ { - let w_side = self.w_side(); - let w_mid = self.w_mid(); - let is_editing = self.is_editing(); - let track_selected = self.arrangement().selection().track(); - Tryptich::top(1) - .left(w_side, button_3("d", "devices", format!("{}", 0), is_editing)) - .right(w_side, button_2("D", "add device", is_editing)) - .middle(w_mid, per_track_top(||self.tracks_with_sizes_scrolled(), - move|index, track|{ - let bg = if track_selected == Some(index) { - track.color.light - } else { - track.color.base - }; - let fg = Tui::g(224); - track.devices.get(0).map(|device|wrap(bg.rgb, fg, device.name())) - })) - } fn tracks_width_available (&self) -> u16 { (self.width() as u16).saturating_sub(40) } @@ -188,6 +15,11 @@ pub trait TracksView: ((x2 as u16) < self.tracks_width_available()) .then_some((t, track, x1, x2))) } + fn view_track_header <'a, T: Content> ( + &'a self, theme: ItemTheme, content: T + ) -> impl Content { + Fixed::x(12, Tui::bg(theme.darker.rgb, Fill::x(Align::e(content)))) + } fn view_track_names (&self, theme: ItemTheme) -> impl Content { let content = Fixed::y(1, Align::w(Tui::bg(theme.darker.rgb, Align::w(Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ @@ -230,11 +62,11 @@ pub trait TracksView: track.color.light.rgb } else { track.color.base.rgb - }, Fill::x(Align::w(format!("[mut] [sol]")))), + }, Fill::x(Align::w(format!("·mute ·solo")))), Map::south(2, ||track.sequencer.midi_outs.iter(), |port, index|Tui::fg(Rgb(255, 255, 255), Fixed::y(2, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( - format!("o{index}: {}", port.name()))))))))))); + format!("·o{index}: {}", port.name()))))))))))); } })))))); Bsp::w( @@ -245,35 +77,6 @@ pub trait TracksView: content ) } - fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Content { - let mut h = 0u16; - for track in self.tracks().iter() { - h = h.max(track.sequencer.midi_ins.len() as u16); - } - let content = Tui::bg(theme.darker.rgb, Align::w(Fill::x( - Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ - for (index, track, x1, x2) in self - .tracks_with_sizes(&self.selection(), None) - .skip(self.track_scroll()) - { - add(&Fixed::xy(track.width as u16, h + 1, - Align::nw(Bsp::s( - Tui::bg(track.color.base.rgb, - Fill::x(Align::w(format!("[rec] [mon]")))), - Map::south(1, ||track.sequencer.midi_ins.iter(), - |port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb, - Fill::x(Align::w(format!("i{index}: {}", port.name()))))))))); - } - - })))); - Bsp::w( - self.view_track_header(theme, row!( - Tui::bold(true, button_2("i", "nputs", false)), - button_2("I", "+", false) - )), - Fixed::y(h, Fill::x(Align::w(Fixed::y(h + 1, content)))), - ) - } fn view_track_devices <'a> (&'a self, theme: ItemTheme) -> impl Content { let mut h = 2u16; for track in self.tracks().iter() { @@ -292,14 +95,38 @@ pub trait TracksView: { add(&Fixed::xy(track.width as u16, h + 1, Tui::bg(track.color.dark.rgb, Align::nw(Map::south(1, move||0..h, - |_, index|format!("d{index}: {}", "--------")))))); + |_, index|format!("·d{index}: {}", "--------")))))); } })))))) } - fn view_track_header <'a, T: Content> ( - &'a self, theme: ItemTheme, content: T - ) -> impl Content { - Fixed::x(12, Tui::bg(theme.darker.rgb, Fill::x(Align::e(content)))) + fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Content { + let mut h = 0u16; + for track in self.tracks().iter() { + h = h.max(track.sequencer.midi_ins.len() as u16); + } + let content = Tui::bg(theme.darker.rgb, Align::w(Fill::x( + Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ + for (index, track, x1, x2) in self + .tracks_with_sizes(&self.selection(), None) + .skip(self.track_scroll()) + { + add(&Fixed::xy(track.width as u16, h + 1, + Align::nw(Bsp::n( + Tui::bg(track.color.base.rgb, + Fill::x(Align::w(format!("·mon ·rec ·dub")))), + Map::south(1, ||track.sequencer.midi_ins.iter(), + |port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb, + Fill::x(Align::w(format!("·i{index}: {}", port.name()))))))))); + } + + })))); + Bsp::w( + self.view_track_header(theme, row!( + Tui::bold(true, button_2("i", "nputs", false)), + button_2("I", "+", false) + )), + Fixed::y(h, Fill::x(Align::w(Fixed::y(h + 1, content)))), + ) } } @@ -308,10 +135,9 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + Send + Sync { const H_SCENE: usize = 2; /// Default editor height. const H_EDITOR: usize = 15; - fn arrangement (&self) -> &Arrangement; - fn h_scenes (&self) -> u16; - fn w_side (&self) -> u16; - fn w_mid (&self) -> u16; + fn h_scenes (&self) -> u16; + fn w_side (&self) -> u16; + fn w_mid (&self) -> u16; fn view_scenes_names (&self) -> impl Content { Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, scene) in self.scenes().iter().enumerate().skip(self.scene_scroll()) { @@ -320,81 +146,25 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + Send + Sync { }) } fn view_scene_name (&self, index: usize, scene: &Scene) -> impl Content { - Fixed::xy(20, if self.selection().scene() == Some(index) && let Some(editor) = self.editor () { + let h = if self.selection().scene() == Some(index) && let Some(editor) = self.editor() { (editor.height() as u16).max(12) } else { Self::H_SCENE as u16 - }, Tui::bg(if self.selection().scene() == Some(index) { + }; + let bg = if self.selection().scene() == Some(index) { scene.color.light.rgb } else { scene.color.base.rgb - }, Align::nw(Bsp::e( - format!(" {index:2} "), - Tui::fg(Rgb(255, 255, 255), - Tui::bold(true, format!("{}", scene.name))))))) - //let height = (1 + y2 - y1 as u16; - //let name = Some(scene.name.clone()); - //let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⯈ ", name)))); - //let selected = self.scene_selected() == Some(s); - //let neighbor = s > 0 && self.scene_selected() == Some(s - 1); - //let is_last = self.scenes().len().saturating_sub(1) == s; - //let theme = scene.color; - //let fg = theme.lightest.rgb; - //let bg = if selected { theme.light } else { theme.base }.rgb; - //let hi = if let Some(previous) = previous { - //if neighbor { previous.light.rgb } else { previous.base.rgb } - //} else { - //Reset - //}; - //let lo = if is_last { Reset } else if selected { - //theme.light.rgb - //} else { - //theme.base.rgb - //}; - //add(&Fill::x(map_south(y1 as u16, height, Fixed::y(height, Phat { - //width: 0, height: 0, content, colors: [fg, bg, hi, lo] - //})))) - //} - } - fn scenes_with_prev_color (&self) -> impl Iterator>> + Send + Sync { - self.scenes_iter().map(|(s, scene, y1, y2)|(s, scene, y1, y2, - (s>0).then(||self.arrangement().scenes()[s-1].color))) - } - fn per_track <'a, T: Content + 'a, U: TracksSizes<'a>> ( - tracks: impl Fn() -> U + Send + Sync + 'a, - callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a - ) -> impl Content + 'a { - Map::new(tracks, move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{ - Tui::fg_bg(track.color.lightest.rgb, track.color.darker.rgb, - map_east(x1 as u16, (x2 - x1) as u16, callback(index, track))) }) - } - fn scenes_with_clip (&self, track_index: usize) -> impl Iterator>> + Send + Sync { - self.scenes_iter().map(move|(s, scene, y1, y2)|(s, scene, y1, y2, - (s>0).then(||self.arrangement().scenes()[s-1].clips[track_index].as_ref() - .map(|c|c.read().unwrap().color) - .unwrap_or(ItemTheme::G[32])))) - } - /// A scene with size and color. - fn scenes_iter (&self) -> impl Iterator + Send + Sync { - let selection = Has::::get(self.arrangement()); - self.arrangement().scenes_with_sizes( - false, // FIXME self.is_editing(), - Self::H_SCENE, Self::H_EDITOR, - selection.track(), selection.scene(), - ).map_while(|(s, scene, y1, y2)|(y2<=self.h_scenes() as usize) - .then_some((s, scene, y1, y2))) - } - /// Height required to display all scenes. - fn h_scenes_total (&self) -> u16 { - self.scenes_with_sizes( - self.is_editing(), - Self::H_SCENE, - Self::H_EDITOR, - self.selection().track(), - self.selection().scene(), - ) - .last() - .map(|(_, _, _, y)|y as u16).unwrap_or(0) + }; + Fixed::xy(20, h, Tui::bg(bg, Align::nw(Bsp::s( + Fill::x(Align::w(Bsp::e( + format!(" {index:2} "), + Tui::fg(Rgb(255, 255, 255), Tui::bold(true, format!("{}", scene.name))) + ))), + When(self.selection().scene() == Some(index) && self.is_editing(), + Fill::xy(Align::nw(Bsp::s( + self.editor().as_ref().map(|e|e.clip_status()), + self.editor().as_ref().map(|e|e.edit_status()))))))))) } } diff --git a/crates/device/src/editor/editor_view.rs b/crates/device/src/editor/editor_view.rs index 454290a9..7e0c9661 100644 --- a/crates/device/src/editor/editor_view.rs +++ b/crates/device/src/editor/editor_view.rs @@ -13,9 +13,9 @@ impl MidiEditor { (clip.color, clip.name.clone(), clip.length, clip.looped) } else { (ItemTheme::G[64], String::new().into(), 0, false) }; Fixed::x(20, col!( - Fill::x(Align::w(FieldV(color, "Clip ", format!("{name}")))), - Fill::x(Align::w(FieldH(color, "Length", format!("{length}")))), - Fill::x(Align::w(FieldH(color, "Loop ", looped.to_string()))), + Fill::x(Align::w(Bsp::e(" Clip ", format!("{name}")))), + Fill::x(Align::w(Bsp::e(" Length ", format!("{length}")))), + Fill::x(Align::w(Bsp::e(" Loop ", looped.to_string()))), )) } @@ -31,9 +31,9 @@ impl MidiEditor { let note_pos = format!("{:>3}", note_pos); let note_len = format!("{:>4}", self.get_note_len()); Fixed::x(20, col!( - Fill::x(Align::w(FieldH(color, "Time", format!("{length}/{time_zoom}+{time_pos}")))), - Fill::x(Align::w(FieldH(color, "Lock", format!("{time_lock}")))), - Fill::x(Align::w(FieldH(color, "Note", format!("{note_name} {note_pos} {note_len}")))), + Fill::x(Align::w(Bsp::e(" Time ", format!("{length}/{time_zoom}+{time_pos}")))), + Fill::x(Align::w(Bsp::e(" Lock ", format!("{time_lock}")))), + Fill::x(Align::w(Bsp::e(" Note ", format!("{note_name} {note_pos} {note_len}")))), )) } From 701ea3fc273d676da3d133b77d1b490740baba2c Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 16:23:13 +0300 Subject: [PATCH 08/17] arranger: almost look like somethin now --- config/config_arranger.edn | 15 ++-- crates/app/src/view.rs | 84 ++++++++++++++----- crates/device/src/arranger/arranger_model.rs | 5 -- crates/device/src/arranger/arranger_tracks.rs | 19 ----- crates/device/src/arranger/arranger_view.rs | 50 ++++++----- 5 files changed, 97 insertions(+), 76 deletions(-) diff --git a/config/config_arranger.edn b/config/config_arranger.edn index 6249a06a..fb40e339 100644 --- a/config/config_arranger.edn +++ b/config/config_arranger.edn @@ -19,12 +19,9 @@ (view (bsp/a :view-dialog (bsp/s - (fixed/y 8 (bsp/e - (fixed/x 20 (fill/y (align/n (bsp/s :view-status-v - (bsp/s :view-audio-ins-status :view-audio-outs-status))))) - (fill/xy (align/n (bsp/s :view-arranger-track-names - (bsp/s :view-arranger-track-outputs - (bsp/s :view-arranger-track-devices :view-arranger-track-inputs))))))) - (fill/xy (bsp/e - (fixed/x 20 (align/nw :view-arranger-scenes-names)) - :view-arranger-scenes-clips))))) + :view-status-h2 + (bsp/n (fill/x (align/w :view-arranger-inputs)) + (bsp/n (fill/x (align/w :view-arranger-devices)) + (bsp/s (fill/x (align/w :view-arranger-outputs)) + (bsp/s (fill/x (align/w :view-arranger-tracks)) + :view-arranger-scenes))))))) diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 8801a26f..fbb50f1b 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -14,12 +14,45 @@ impl App { pub fn view_nil (&self) -> impl Content + use<'_> { "nil" } + pub fn view_status_h2 (&self) -> impl Content + use<'_> { + self.update_clock(); + let theme = self.color; + let playing = self.clock().is_rolling(); + Fixed::y(2, Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ + add(&Fixed::x(5, Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) }, + Either::new(false, // TODO + Thunk::new(move||Fixed::x(9, Either::new(playing, + Tui::fg(Rgb(0, 255, 0), " PLAYING "), + Tui::fg(Rgb(255, 128, 0), " STOPPED "))) + ), + Thunk::new(move||Fixed::x(5, Either::new(playing, + Tui::fg(Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), + Tui::fg(Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",)))) + ) + ) + ))); + { + let cache = self.view_cache.read().unwrap(); + add(&Align::w(Bsp::s( + FieldH(theme, "Beat", cache.beat.view.clone()), + FieldH(theme, "Time", cache.time.view.clone()), + ))); + add(&Align::w(Bsp::s( + Fill::x(Align::w(FieldH(theme, "BPM", cache.bpm.view.clone()))), + Fill::x(Align::w(FieldH(theme, "SR ", cache.sr.view.clone()))), + ))); + add(&FieldH(theme, "Buf", + Bsp::e(cache.buf.view.clone(), Bsp::e(" = ", cache.lat.view.clone())) + )); + } + })) + } pub fn view_status_v (&self) -> impl Content + use<'_> { self.update_clock(); let cache = self.view_cache.read().unwrap(); let theme = self.color; let playing = self.clock().is_rolling(); - Tui::bg(theme.darkest.rgb, Fixed::xy(20, 6, Outer(true, Style::default().fg(Tui::g(96))).enclose( + Tui::bg(theme.darker.rgb, Fixed::xy(20, 5, Outer(true, Style::default().fg(Tui::g(96))).enclose( col!( Fill::x(Align::w(Bsp::e( Align::w(Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) }, @@ -41,8 +74,7 @@ impl App { ))), Fill::x(Align::w(FieldH(theme, "BPM", cache.bpm.view.clone()))), Fill::x(Align::w(FieldH(theme, "SR ", cache.sr.view.clone()))), - Fill::x(Align::w(FieldH(theme, "Buf", cache.buf.view.clone()))), - Fill::x(Align::w(FieldH(theme, "Lat", cache.lat.view.clone()))), + Fill::x(Align::w(FieldH(theme, "Buf", Bsp::e(cache.buf.view.clone(), Bsp::e(" = ", cache.lat.view.clone()))))), )))) } pub fn view_status (&self) -> impl Content + use<'_> { @@ -76,11 +108,35 @@ impl App { pub fn view_audio_outs_status (&self) -> impl Content + use<'_> { self.project.view_audio_outs_status(self.color) } - pub fn view_arranger_scenes_names (&self) -> impl Content + use<'_> { - self.project.view_scenes_names() + pub fn view_arranger_scenes (&self) -> impl Content + use<'_> { + Bsp::e( + Fixed::x(20, Align::nw(self.project.view_scenes_names())), + Bsp::w(self.view_pool(), self.project.view_scenes_clips()), + ) } - pub fn view_arranger_scenes_clips (&self) -> impl Content + use<'_> { - self.project.view_scenes_clips() + pub fn view_arranger_inputs <'a> (&'a self) -> impl Content + use<'a> { + Fixed::y(3, Bsp::e( + Fixed::x(20, Tui::bg(self.color.darker.rgb, Fill::xy(""))), + self.project.view_track_inputs(self.color) + )) + } + pub fn view_arranger_outputs <'a> (&'a self) -> impl Content + use<'a> { + Fixed::y(3, Bsp::e( + Fixed::x(20, Tui::bg(self.color.darker.rgb, Fill::xy(""))), + self.project.view_track_outputs(self.color) + )) + } + pub fn view_arranger_devices <'a> (&'a self) -> impl Content + use<'a> { + Fixed::y(3, Bsp::e( + Fixed::x(20, Tui::bg(self.color.darker.rgb, Fill::xy(""))), + self.project.view_track_devices(self.color) + )) + } + pub fn view_arranger_tracks <'a> (&'a self) -> impl Content + use<'a> { + Fixed::y(2, Bsp::e( + Fixed::x(20, Tui::bg(self.color.darker.rgb, Fill::xy(""))), + self.project.view_track_names(self.color) + )) } pub fn view_arranger_track_names (&self) -> impl Content + use<'_> { self.project.view_track_names(self.color) @@ -103,8 +159,7 @@ impl App { Fixed::x(20, Tui::bg(self.color.darkest.rgb, col!(Tui::bold(true, "Devices"), "[d] Select", "[D] Add"))), Fixed::y(max_devices + 1, Tui::bg(self.color.darker.rgb, Align::w(Fill::x(Map::new( - ||self.project.tracks_with_sizes(&self.project.selection, None) - .skip(self.project.track_scroll), + ||self.project.tracks_with_sizes_scrolled(), move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _| Push::x(x2 as u16, Fixed::xy(track.width as u16, max_devices + 1, Align::nw(Map::south(1, ||track.devices.iter(), @@ -248,17 +303,6 @@ impl ScenesView for App { } } -pub(crate) fn heading <'a> ( - key: &'a str, - label: &'a str, - count: usize, - content: impl Content + Send + Sync + 'a, - editing: bool, -) -> impl Content + 'a { - let count = format!("{count}"); - Fill::xy(Align::w(Bsp::s(Fill::x(Align::w(button_3(key, label, count, editing))), content))) -} - /// Clear a pre-allocated buffer, then write into it. #[macro_export] macro_rules! rewrite { ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } } diff --git a/crates/device/src/arranger/arranger_model.rs b/crates/device/src/arranger/arranger_model.rs index d0a4698e..a1717211 100644 --- a/crates/device/src/arranger/arranger_model.rs +++ b/crates/device/src/arranger/arranger_model.rs @@ -64,11 +64,6 @@ impl Arrangement { pub fn w_sidebar (&self, is_editing: bool) -> u16 { self.w() / if is_editing { 16 } else { 8 } as u16 } - /// Width taken by all tracks. - pub fn w_tracks (&self) -> u16 { - self.tracks_with_sizes(&self.selection(), None).last() - .map(|(_, _, _, x)|x as u16).unwrap_or(0) - } /// Width available to display tracks. pub fn w_tracks_area (&self, is_editing: bool) -> u16 { self.w().saturating_sub(self.w_sidebar(is_editing)) diff --git a/crates/device/src/arranger/arranger_tracks.rs b/crates/device/src/arranger/arranger_tracks.rs index 08d9a121..aeb7ed91 100644 --- a/crates/device/src/arranger/arranger_tracks.rs +++ b/crates/device/src/arranger/arranger_tracks.rs @@ -44,25 +44,6 @@ pub trait HasTracks: Has> + Send + Sync { } } } - /// Iterate over tracks with their corresponding sizes. - fn tracks_with_sizes (&self, selection: &Selection, editor_width: Option) - -> impl TracksSizes<'_> - { - let mut x = 0; - let active_track = if let Some(width) = editor_width { - selection.track() - } else { - None - }; - self.tracks().iter().enumerate().map(move |(index, track)|{ - let width = active_track - .and_then(|_|editor_width) - .unwrap_or(track.width.max(8)); - let data = (index, track, x, x + width); - x += width + Self::TRACK_SPACING; - data - }) - } /// Spacing between tracks. const TRACK_SPACING: usize = 0; } diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index 0488314b..4beee14d 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -9,8 +9,27 @@ pub trait TracksView: fn tracks_width_available (&self) -> u16 { (self.width() as u16).saturating_sub(40) } + /// Iterate over tracks with their corresponding sizes. + fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { + let editor_width = self.editor().map(|e|e.width()); + let active_track = if let Some(width) = editor_width { + self.selection().track() + } else { + None + }; + let mut x = 0; + self.tracks().iter().enumerate().map(move |(index, track)|{ + let width = active_track + .and_then(|_|editor_width) + .unwrap_or(track.width.max(8)); + let data = (index, track, x, x + width); + x += width + Self::TRACK_SPACING; + data + }) + } fn tracks_with_sizes_scrolled <'t> (&'t self) -> impl TracksSizes<'t> { - self.tracks_with_sizes(&self.selection(), self.is_editing().then_some(20/*FIXME*/)) + self.tracks_with_sizes() + .skip(self.track_scroll()) .map_while(move|(t, track, x1, x2)| ((x2 as u16) < self.tracks_width_available()) .then_some((t, track, x1, x2))) @@ -23,10 +42,7 @@ pub trait TracksView: fn view_track_names (&self, theme: ItemTheme) -> impl Content { let content = Fixed::y(1, Align::w(Tui::bg(theme.darker.rgb, Align::w(Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ - for (index, track, x1, x2) in self - .tracks_with_sizes(&self.selection(), None) - .skip(self.track_scroll()) - { + for (index, track, x1, x2) in self.tracks_with_sizes() { add(&Fixed::x(track.width as u16, Tui::bg(if self.selection().track() == Some(index) { track.color.light.rgb @@ -53,11 +69,8 @@ pub trait TracksView: let content = Align::w(Fixed::y(1 + max_outputs*2, Tui::bg(theme.darker.rgb, Align::w(Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ - for (index, track, x1, x2) in self - .tracks_with_sizes(&self.selection(), None) - .skip(self.track_scroll()) - { - add(&Fixed::x(track.width as u16, Align::nw(Bsp::s( + for (index, track, x1, x2) in self.tracks_with_sizes() { + add(&Fixed::x(track.width as u16, Align::nw(Bsp::n( Tui::bg(if self.selection().track() == Some(index) { track.color.light.rgb } else { @@ -89,10 +102,7 @@ pub trait TracksView: )), Fixed::y(h, Tui::bg(theme.darker.rgb, Align::w(Fill::x(Stack::east( move|add: &mut dyn FnMut(&dyn Render)|{ - for (index, track, x1, x2) in self - .tracks_with_sizes(&self.selection(), None) - .skip(self.track_scroll()) - { + for (index, track, x1, x2) in self.tracks_with_sizes() { add(&Fixed::xy(track.width as u16, h + 1, Tui::bg(track.color.dark.rgb, Align::nw(Map::south(1, move||0..h, |_, index|format!("·d{index}: {}", "--------")))))); @@ -106,12 +116,9 @@ pub trait TracksView: } let content = Tui::bg(theme.darker.rgb, Align::w(Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ - for (index, track, x1, x2) in self - .tracks_with_sizes(&self.selection(), None) - .skip(self.track_scroll()) - { + for (index, track, x1, x2) in self.tracks_with_sizes() { add(&Fixed::xy(track.width as u16, h + 1, - Align::nw(Bsp::n( + Align::nw(Bsp::s( Tui::bg(track.color.base.rgb, Fill::x(Align::w(format!("·mon ·rec ·dub")))), Map::south(1, ||track.sequencer.midi_ins.iter(), @@ -176,10 +183,7 @@ pub trait ClipsView: TracksView + ScenesView + Send + Sync { -> impl Content + 'a { Fill::xy(Stack::::east(move|column: &mut dyn FnMut(&dyn Render)|{ - for (track_index, track, _, _) in self - .tracks_with_sizes(&self.selection(), None) - .skip(self.track_scroll()) - { + for (track_index, track, _, _) in self.tracks_with_sizes() { //column(&Fixed::x(5, Fill::xy(Tui::bg(Green, "kyp")))); column(&Fixed::x( if self.selection().track() == Some(track_index) From 62bfb0120b886755952ea7f19a5a183d96d6311f Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 17:05:11 +0300 Subject: [PATCH 09/17] polite outline --- crates/app/src/view.rs | 20 +- crates/device/src/arranger/arranger_model.rs | 2 +- crates/device/src/arranger/arranger_view.rs | 208 +++++++++---------- 3 files changed, 109 insertions(+), 121 deletions(-) diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index fbb50f1b..35239af1 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -115,28 +115,16 @@ impl App { ) } pub fn view_arranger_inputs <'a> (&'a self) -> impl Content + use<'a> { - Fixed::y(3, Bsp::e( - Fixed::x(20, Tui::bg(self.color.darker.rgb, Fill::xy(""))), - self.project.view_track_inputs(self.color) - )) + Fixed::y(3, self.project.view_track_inputs(self.color)) } pub fn view_arranger_outputs <'a> (&'a self) -> impl Content + use<'a> { - Fixed::y(3, Bsp::e( - Fixed::x(20, Tui::bg(self.color.darker.rgb, Fill::xy(""))), - self.project.view_track_outputs(self.color) - )) + Fixed::y(3, self.project.view_track_outputs(self.color)) } pub fn view_arranger_devices <'a> (&'a self) -> impl Content + use<'a> { - Fixed::y(3, Bsp::e( - Fixed::x(20, Tui::bg(self.color.darker.rgb, Fill::xy(""))), - self.project.view_track_devices(self.color) - )) + Fixed::y(3, self.project.view_track_devices(self.color)) } pub fn view_arranger_tracks <'a> (&'a self) -> impl Content + use<'a> { - Fixed::y(2, Bsp::e( - Fixed::x(20, Tui::bg(self.color.darker.rgb, Fill::xy(""))), - self.project.view_track_names(self.color) - )) + Fixed::y(3, self.project.view_track_names(self.color)) } pub fn view_arranger_track_names (&self) -> impl Content + use<'_> { self.project.view_track_names(self.color) diff --git a/crates/device/src/arranger/arranger_model.rs b/crates/device/src/arranger/arranger_model.rs index a1717211..5c3bb4d1 100644 --- a/crates/device/src/arranger/arranger_model.rs +++ b/crates/device/src/arranger/arranger_model.rs @@ -157,7 +157,7 @@ impl Arrangement { mouts: &[PortConnect], ) -> Usually<(usize, &mut Track)> { let name: Arc = name.map_or_else( - ||format!("t{:02}", self.track_last).into(), + ||format!("trk{:02}", self.track_last).into(), |x|x.to_string().into() ); self.track_last += 1; diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index 4beee14d..90240754 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -12,16 +12,10 @@ pub trait TracksView: /// Iterate over tracks with their corresponding sizes. fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { let editor_width = self.editor().map(|e|e.width()); - let active_track = if let Some(width) = editor_width { - self.selection().track() - } else { - None - }; + let active_track = self.selection().track(); let mut x = 0; self.tracks().iter().enumerate().map(move |(index, track)|{ - let width = active_track - .and_then(|_|editor_width) - .unwrap_or(track.width.max(8)); + let width = active_track.and_then(|_|editor_width).unwrap_or(track.width.max(8)); let data = (index, track, x, x + width); x += width + Self::TRACK_SPACING; data @@ -34,78 +28,87 @@ pub trait TracksView: ((x2 as u16) < self.tracks_width_available()) .then_some((t, track, x1, x2))) } + fn view_track_row_section <'a> ( + &'a self, + theme: ItemTheme, + button: impl Content, + button_add: impl Content, + content: impl Content + ) -> impl Content { + Bsp::w( + Fixed::x(4, button_add), + Bsp::e( + Fixed::x(20, Align::w(button)), + content + ) + ) + } fn view_track_header <'a, T: Content> ( &'a self, theme: ItemTheme, content: T ) -> impl Content { Fixed::x(12, Tui::bg(theme.darker.rgb, Fill::x(Align::e(content)))) } fn view_track_names (&self, theme: ItemTheme) -> impl Content { - let content = Fixed::y(1, Align::w(Tui::bg(theme.darker.rgb, Align::w(Fill::x( - Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ - for (index, track, x1, x2) in self.tracks_with_sizes() { - add(&Fixed::x(track.width as u16, - Tui::bg(if self.selection().track() == Some(index) { - track.color.light.rgb - } else { - track.color.base.rgb - }, Align::nw(Tui::fg( - Rgb(255, 255, 255), Tui::bold(true, - format!("{}", track.name))))))); - } - })))))); - Bsp::w( - self.view_track_header(theme, row!( - Tui::bold(true, button_2("t", "rack ", false)), - button_2("T", "+", false) - )), - content - ) - } - fn view_track_outputs <'a> (&'a self, theme: ItemTheme) -> impl Content { - let mut max_outputs = 0u16; - for track in self.tracks().iter() { - max_outputs = max_outputs.max(track.sequencer.midi_outs.len() as u16); - } - let content = Align::w(Fixed::y(1 + max_outputs*2, - Tui::bg(theme.darker.rgb, Align::w(Fill::x( + self.view_track_row_section( + theme, + button_2("t", "rack", false), + button_2("T", "+", false), + Fixed::y(1, Align::w(Tui::bg(theme.darker.rgb, Align::w(Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self.tracks_with_sizes() { - add(&Fixed::x(track.width as u16, Align::nw(Bsp::n( + add(&Fixed::x(track.width as u16, Tui::bg(if self.selection().track() == Some(index) { track.color.light.rgb } else { track.color.base.rgb - }, Fill::x(Align::w(format!("·mute ·solo")))), - Map::south(2, ||track.sequencer.midi_outs.iter(), - |port, index|Tui::fg(Rgb(255, 255, 255), - Fixed::y(2, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( - format!("·o{index}: {}", port.name()))))))))))); + }, Align::nw(Bsp::e( + format!("·t{index:02} "), + Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &track.name)) + ))))); } - })))))); - Bsp::w( - self.view_track_header(theme, row!( - Tui::bold(true, button_2("o", "utput", false)), - button_2("O", "+", false) - )), - content - ) + }))))))) + } + fn view_track_outputs <'a> (&'a self, theme: ItemTheme) -> impl Content { + let mut h = 0u16; + for track in self.tracks().iter() { + h = h.max(track.sequencer.midi_outs.len() as u16); + } + self.view_track_row_section( + theme, + button_2("o", "utput", false), + button_2("O", "+", false), + Align::w(Fixed::y(1 + h*2, + Tui::bg(theme.darker.rgb, Align::w(Fill::x( + Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ + for (index, track, x1, x2) in self.tracks_with_sizes() { + add(&Fixed::x(track.width as u16, Align::nw(Bsp::n( + Tui::bg(if self.selection().track() == Some(index) { + track.color.light.rgb + } else { + track.color.base.rgb + }, Fill::x(Align::w(format!("·mute ·solo")))), + Map::south(2, ||track.sequencer.midi_outs.iter(), + |port, index|Tui::fg(Rgb(255, 255, 255), + Fixed::y(2, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( + format!("·o{index:02} {}", port.name()))))))))))); + } + }))))))) } fn view_track_devices <'a> (&'a self, theme: ItemTheme) -> impl Content { let mut h = 2u16; for track in self.tracks().iter() { h = h.max(track.devices.len() as u16); } - Bsp::w( - self.view_track_header(theme, row!( - Tui::bold(true, button_2("d", "evice", false)), - button_2("D", "+", false) - )), + self.view_track_row_section( + theme, + button_2("d", "evice", false), + button_2("D", "+", false), Fixed::y(h, Tui::bg(theme.darker.rgb, Align::w(Fill::x(Stack::east( move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self.tracks_with_sizes() { add(&Fixed::xy(track.width as u16, h + 1, Tui::bg(track.color.dark.rgb, Align::nw(Map::south(1, move||0..h, - |_, index|format!("·d{index}: {}", "--------")))))); + |_, index|format!("·d{index:02} {}", "--------")))))); } })))))) } @@ -114,26 +117,21 @@ pub trait TracksView: for track in self.tracks().iter() { h = h.max(track.sequencer.midi_ins.len() as u16); } - let content = Tui::bg(theme.darker.rgb, Align::w(Fill::x( - Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ - for (index, track, x1, x2) in self.tracks_with_sizes() { - add(&Fixed::xy(track.width as u16, h + 1, - Align::nw(Bsp::s( - Tui::bg(track.color.base.rgb, - Fill::x(Align::w(format!("·mon ·rec ·dub")))), - Map::south(1, ||track.sequencer.midi_ins.iter(), - |port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb, - Fill::x(Align::w(format!("·i{index}: {}", port.name()))))))))); - } - - })))); - Bsp::w( - self.view_track_header(theme, row!( - Tui::bold(true, button_2("i", "nputs", false)), - button_2("I", "+", false) - )), - Fixed::y(h, Fill::x(Align::w(Fixed::y(h + 1, content)))), - ) + self.view_track_row_section( + theme, + button_2("i", "nput", false), + button_2("I", "+", false), + Tui::bg(theme.darker.rgb, Align::w(Fill::x( + Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ + for (index, track, x1, x2) in self.tracks_with_sizes() { + add(&Fixed::xy(track.width as u16, h + 1, + Align::nw(Bsp::s( + Tui::bg(track.color.base.rgb, + Fill::x(Align::w(format!("·mon ·rec ·dub")))), + Map::south(1, ||track.sequencer.midi_ins.iter(), + |port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb, + Fill::x(Align::w(format!("·i{index:02} {}", port.name()))))))))); + }}))))) } } @@ -165,8 +163,8 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + Send + Sync { }; Fixed::xy(20, h, Tui::bg(bg, Align::nw(Bsp::s( Fill::x(Align::w(Bsp::e( - format!(" {index:2} "), - Tui::fg(Rgb(255, 255, 255), Tui::bold(true, format!("{}", scene.name))) + format!("·s{index:02} "), + Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &scene.name)) ))), When(self.selection().scene() == Some(index) && self.is_editing(), Fill::xy(Align::nw(Bsp::s( @@ -202,18 +200,18 @@ pub trait ClipsView: TracksView + ScenesView + Send + Sync { fn view_track_clips <'a> (&'a self, track_index: usize, track: &'a Track) -> impl Content + 'a { Stack::south(move|cell: &mut dyn FnMut(&dyn Render)|{ for (scene_index, scene) in self.scenes().iter().enumerate().skip(self.scene_scroll()) { - let (name, theme) = if let Some(Some(clip)) = &scene.clips.get(track_index) { + let (name, theme): (Arc, ItemTheme) = if let Some(Some(clip)) = &scene.clips.get(track_index) { let clip = clip.read().unwrap(); - (Some(clip.name.clone()), clip.color) + (clip.name.clone(), clip.color) } else { - (None, ItemTheme::G[32]) + (" ---- ".into(), ItemTheme::G[32]) }; let fg = theme.lightest.rgb; let mut outline = theme.base.rgb; let bg = if self.selection().track() == Some(track_index) && self.selection().scene() == Some(scene_index) { - outline = theme.lightest.rgb; + outline = theme.lighter.rgb; theme.light.rgb } else if self.selection().track() == Some(track_index) || self.selection().scene() == Some(scene_index) @@ -223,28 +221,30 @@ pub trait ClipsView: TracksView + ScenesView + Send + Sync { } else { theme.dark.rgb }; - cell(&Fixed::xy( - if self.selection().track() == Some(track_index) - && let Some(editor) = self.editor () - { - editor.width().max(24).max(track.width) - } else { - track.width - } as u16, - if self.selection().scene() == Some(scene_index) - && let Some(editor) = self.editor () - { - editor.height().max(12) - } else { - Self::H_SCENE - } as u16, - Bsp::b( - Fill::xy(Outer(true, Style::default().fg(outline))), - Fill::xy(Bsp::b( - Fill::xy(Align::nw(Tui::fg_bg(fg, bg, Align::nw(name.unwrap_or(" ---- ".into()))))), - Fill::xy(When(self.selection().track() == Some(track_index) - && self.selection().scene() == Some(scene_index) - && self.is_editing(), self.editor()))))))); + let w = if self.selection().track() == Some(track_index) + && let Some(editor) = self.editor () + { + editor.width().max(24).max(track.width) + } else { + track.width + } as u16; + let y = if self.selection().scene() == Some(scene_index) + && let Some(editor) = self.editor () + { + editor.height().max(12) + } else { + Self::H_SCENE + } as u16; + cell(&Fixed::xy(w, y, Bsp::b( + Fill::xy(Outer(true, Style::default().fg(outline))), + Fill::xy(Bsp::b( + Bsp::b( + Tui::fg_bg(outline, bg, Fill::xy("")), + Fill::xy(Align::nw(Tui::fg_bg(fg, bg, Tui::bold(true, name)))), + ), + Fill::xy(When(self.selection().track() == Some(track_index) + && self.selection().scene() == Some(scene_index) + && self.is_editing(), self.editor()))))))); //let (name, theme) = if let Some(clip) = &scene.clips.get(track_index).flatten() { //let clip = clip.read().unwrap(); //(Some(clip.name.clone()), clip.color) From f1f5ac63e11b8858ee0fd4f22e766e5ea9901b62 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 17:29:02 +0300 Subject: [PATCH 10/17] reenable adding tracks/scenes --- config/config_arranger.edn | 19 +-- config/keys_arranger.edn | 9 +- crates/app/src/view.rs | 41 ++----- crates/device/src/arranger/arranger_view.rs | 122 +++++--------------- 4 files changed, 52 insertions(+), 139 deletions(-) diff --git a/config/config_arranger.edn b/config/config_arranger.edn index fb40e339..43a6577d 100644 --- a/config/config_arranger.edn +++ b/config/config_arranger.edn @@ -17,11 +17,14 @@ (layer "./keys_arranger.edn") (layer "./keys_global.edn")) -(view (bsp/a :view-dialog - (bsp/s - :view-status-h2 - (bsp/n (fill/x (align/w :view-arranger-inputs)) - (bsp/n (fill/x (align/w :view-arranger-devices)) - (bsp/s (fill/x (align/w :view-arranger-outputs)) - (bsp/s (fill/x (align/w :view-arranger-tracks)) - :view-arranger-scenes))))))) +(view + (bsp/a :view-dialog + (bsp/w :view-meters-output + (bsp/e :view-meters-input + (bsp/s + :view-status-h2 + (bsp/n (fill/x (align/w :view-tracks-inputs)) + (bsp/n (fill/x (align/w :view-tracks-devices)) + (bsp/s (fill/x (align/w :view-tracks-outputs)) + (bsp/s (fill/x (align/w :view-tracks-tracks)) + :view-tracks-scenes))))))))) diff --git a/config/keys_arranger.edn b/config/keys_arranger.edn index 23da3a49..1953e0c4 100644 --- a/config/keys_arranger.edn +++ b/config/keys_arranger.edn @@ -3,10 +3,11 @@ (@t select :select-track-header) (@s select :select-scene-header) (@tab edit :clip-selected) -(@shift-I input add) -(@shift-O output add) -(@shift-S scene add) -(@shift-T track add) +(@enter edit :clip-selected) +(@shift-I project input-add) +(@shift-O project output-add) +(@shift-S project scene-add) +(@shift-T project track-add) (@shift-D toggle-dialog :dialog-device) (@up select :select-scene-prev) diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 35239af1..90d9983e 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -108,50 +108,23 @@ impl App { pub fn view_audio_outs_status (&self) -> impl Content + use<'_> { self.project.view_audio_outs_status(self.color) } - pub fn view_arranger_scenes (&self) -> impl Content + use<'_> { + pub fn view_tracks_scenes (&self) -> impl Content + use<'_> { Bsp::e( Fixed::x(20, Align::nw(self.project.view_scenes_names())), Bsp::w(self.view_pool(), self.project.view_scenes_clips()), ) } - pub fn view_arranger_inputs <'a> (&'a self) -> impl Content + use<'a> { - Fixed::y(3, self.project.view_track_inputs(self.color)) + pub fn view_tracks_inputs <'a> (&'a self) -> impl Content + use<'a> { + Fixed::y(2, self.project.view_track_inputs(self.color)) } - pub fn view_arranger_outputs <'a> (&'a self) -> impl Content + use<'a> { + pub fn view_tracks_outputs <'a> (&'a self) -> impl Content + use<'a> { Fixed::y(3, self.project.view_track_outputs(self.color)) } - pub fn view_arranger_devices <'a> (&'a self) -> impl Content + use<'a> { + pub fn view_tracks_devices <'a> (&'a self) -> impl Content + use<'a> { Fixed::y(3, self.project.view_track_devices(self.color)) } - pub fn view_arranger_tracks <'a> (&'a self) -> impl Content + use<'a> { - Fixed::y(3, self.project.view_track_names(self.color)) - } - pub fn view_arranger_track_names (&self) -> impl Content + use<'_> { - self.project.view_track_names(self.color) - } - pub fn view_arranger_track_outputs <'a> (&'a self) -> impl Content + use<'a> { - self.project.view_track_outputs(self.color) - } - pub fn view_arranger_track_inputs <'a> (&'a self) -> impl Content + use<'a> { - self.project.view_track_inputs(self.color) - } - pub fn view_arranger_track_devices <'a> (&'a self) -> impl Content + use<'a> { - self.project.view_track_devices(self.color) - } - pub fn view_arranger_track_scenes <'a> (&'a self) -> impl Content + use<'a> { - let mut max_devices = 0u16; - for track in self.project.tracks.iter() { - max_devices = max_devices.max(track.devices.len() as u16); - } - Bsp::w( - Fixed::x(20, Tui::bg(self.color.darkest.rgb, - col!(Tui::bold(true, "Devices"), "[d] Select", "[D] Add"))), - Fixed::y(max_devices + 1, Tui::bg(self.color.darker.rgb, Align::w(Fill::x(Map::new( - ||self.project.tracks_with_sizes_scrolled(), - move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _| - Push::x(x2 as u16, Fixed::xy(track.width as u16, max_devices + 1, - Align::nw(Map::south(1, ||track.devices.iter(), - |device, index|format!("{index}: {}", device.name()))))))))))) + pub fn view_tracks_tracks <'a> (&'a self) -> impl Content + use<'a> { + Fixed::y(1, self.project.view_track_names(self.color)) } pub fn view_pool (&self) -> impl Content + use<'_> { Fixed::x(20, Bsp::s( diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index 90240754..ea42c575 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -3,6 +3,8 @@ use crate::*; impl TracksView for T where T: ScenesView + HasMidiIns + HasMidiOuts + HasSize + HasTrackScroll + HasSelection + HasMidiIns + HasEditor {} +impl ClipsView for T {} + pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasSize + HasTrackScroll + HasSelection + HasMidiIns + HasEditor { @@ -39,7 +41,7 @@ pub trait TracksView: Fixed::x(4, button_add), Bsp::e( Fixed::x(20, Align::w(button)), - content + Fill::xy(Align::c(content)) ) ) } @@ -53,10 +55,10 @@ pub trait TracksView: theme, button_2("t", "rack", false), button_2("T", "+", false), - Fixed::y(1, Align::w(Tui::bg(theme.darker.rgb, Align::w(Fill::x( + Tui::bg(theme.darker.rgb, Fixed::y(1, Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self.tracks_with_sizes() { - add(&Fixed::x(track.width as u16, + add(&Fixed::x(self.track_width(index, track), Tui::bg(if self.selection().track() == Some(index) { track.color.light.rgb } else { @@ -66,7 +68,7 @@ pub trait TracksView: Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &track.name)) ))))); } - }))))))) + }))))) } fn view_track_outputs <'a> (&'a self, theme: ItemTheme) -> impl Content { let mut h = 0u16; @@ -81,16 +83,17 @@ pub trait TracksView: Tui::bg(theme.darker.rgb, Align::w(Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self.tracks_with_sizes() { - add(&Fixed::x(track.width as u16, Align::nw(Bsp::n( - Tui::bg(if self.selection().track() == Some(index) { - track.color.light.rgb - } else { - track.color.base.rgb - }, Fill::x(Align::w(format!("·mute ·solo")))), - Map::south(2, ||track.sequencer.midi_outs.iter(), - |port, index|Tui::fg(Rgb(255, 255, 255), - Fixed::y(2, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( - format!("·o{index:02} {}", port.name()))))))))))); + add(&Fixed::x(self.track_width(index, track), + Align::nw(Bsp::n( + Tui::bg(if self.selection().track() == Some(index) { + track.color.light.rgb + } else { + track.color.base.rgb + }, Fill::x(Align::w(format!("·mute ·solo")))), + Map::south(2, ||track.sequencer.midi_outs.iter(), + |port, index|Tui::fg(Rgb(255, 255, 255), + Fixed::y(2, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( + format!("·o{index:02} {}", port.name()))))))))))); } }))))))) } @@ -106,7 +109,7 @@ pub trait TracksView: Fixed::y(h, Tui::bg(theme.darker.rgb, Align::w(Fill::x(Stack::east( move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self.tracks_with_sizes() { - add(&Fixed::xy(track.width as u16, h + 1, + add(&Fixed::xy(self.track_width(index, track), h + 1, Tui::bg(track.color.dark.rgb, Align::nw(Map::south(1, move||0..h, |_, index|format!("·d{index:02} {}", "--------")))))); } @@ -124,7 +127,7 @@ pub trait TracksView: Tui::bg(theme.darker.rgb, Align::w(Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self.tracks_with_sizes() { - add(&Fixed::xy(track.width as u16, h + 1, + add(&Fixed::xy(self.track_width(index, track), h + 1, Align::nw(Bsp::s( Tui::bg(track.color.base.rgb, Fill::x(Align::w(format!("·mon ·rec ·dub")))), @@ -133,6 +136,15 @@ pub trait TracksView: Fill::x(Align::w(format!("·i{index:02} {}", port.name()))))))))); }}))))) } + fn track_width (&self, index: usize, track: &Track) -> u16 { + (if self.selection().track() == Some(index) + && let Some(editor) = self.editor() + { + editor.width().max(24).max(track.width) + } else { + track.width + }) as u16 + } } pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + Send + Sync { @@ -173,10 +185,7 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + Send + Sync { } } -impl ClipsView for T {} - pub trait ClipsView: TracksView + ScenesView + Send + Sync { - fn view_scenes_clips <'a> (&'a self) -> impl Content + 'a { @@ -185,7 +194,7 @@ pub trait ClipsView: TracksView + ScenesView + Send + Sync { //column(&Fixed::x(5, Fill::xy(Tui::bg(Green, "kyp")))); column(&Fixed::x( if self.selection().track() == Some(track_index) - && let Some(editor) = self.editor () + && let Some(editor) = self.editor() { editor.width().max(24).max(track.width) } else { @@ -245,82 +254,9 @@ pub trait ClipsView: TracksView + ScenesView + Send + Sync { Fill::xy(When(self.selection().track() == Some(track_index) && self.selection().scene() == Some(scene_index) && self.is_editing(), self.editor()))))))); - //let (name, theme) = if let Some(clip) = &scene.clips.get(track_index).flatten() { - //let clip = clip.read().unwrap(); - //(Some(clip.name.clone()), clip.color) - //} else { - //(None, ItemTheme::G[32]) - //}; - //let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⏹ ", name)))); - //let same_track = self.track_selected() == Some(track_index); - //let selected = same_track && self.scene_selected() == Some(s); - //let neighbor = same_track && s > 0 && self.scene_selected() == Some(s - 1); - //let is_last = self.scenes().len().saturating_sub(1) == s; - //let fg = theme.lightest.rgb; - //let bg = if selected { theme.light } else { theme.base }.rgb; - //let hi = if let Some(previous) = previous { - //if neighbor { previous.light.rgb } else { previous.base.rgb } - //} else { - //Reset - //}; - //let lo = if is_last { - //Reset - //} else if selected { - //theme.light.rgb - //} else { - //theme.base.rgb - //}; - //let height = (1 + y2 - y1) as u16; - //let is_editing = false; //FIXME - //let editor = (); //FIXME - //cell(&Fixed::xy(track.width as u16, 2, Bsp::b(Fixed::y(height, Phat { - //width: 0, height: 0, content, colors: [fg, bg, hi, lo] - //}), When( - //is_editing && same_track && self.scene_selected() == Some(s), - //editor - //)))) } }) } - - fn scenes_clips_2 <'a> ( - &'a self, - theme: ItemTheme - ) -> impl Content + 'a { - Fixed::y(self.scenes().len() as u16 * 2, Tui::bg(theme.darker.rgb, - Align::w(Fill::x(Map::new(||self.scenes().iter().skip(self.scene_scroll()), - move|scene: &'a Scene, index|self.track_scenes(index, scene)))))) - } - - fn track_scenes <'a> ( - &'a self, - scene_index: usize, - scene: &'a Scene - ) -> impl Content + 'a { - Push::y(scene_index as u16 * 2u16, Fixed::xy(20, 2, Map::new( - move||scene.clips.iter().skip(self.track_scroll()), - move|clip: &'a Option>>, track_index| - self.track_scene_clip(scene_index, scene, track_index, clip)))) - } - - fn track_scene_clip ( - &self, - scene_index: usize, - scene: &Scene, - track_index: usize, - clip: &Option>> - ) -> impl Content { - let (theme, text) = if let Some(clip) = clip { - let clip = clip.read().unwrap(); - (clip.color, clip.name.clone()) - } else { - (scene.color, Default::default()) - }; - Push::x(track_index as u16 * 14, Tui::bg(theme.dark.rgb, Bsp::e( - format!(" {scene_index:2} {track_index:2} "), - Tui::fg(Rgb(255, 255, 255), - Tui::bold(true, format!("{}", text)))))) - } } pub trait HasWidth { From 29b2789be69ca91ee0a7493acee8068d1583a464 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 17:59:43 +0300 Subject: [PATCH 11/17] starting to look very much like something --- config/keys_arranger.edn | 4 +- crates/app/src/model.rs | 3 + crates/device/src/arranger/arranger_model.rs | 2 + crates/device/src/arranger/arranger_scenes.rs | 17 --- crates/device/src/arranger/arranger_view.rs | 115 +++++++++++++----- crates/device/src/sequencer/seq_model.rs | 2 +- deps/tengri | 2 +- 7 files changed, 94 insertions(+), 51 deletions(-) diff --git a/config/keys_arranger.edn b/config/keys_arranger.edn index 1953e0c4..ee691491 100644 --- a/config/keys_arranger.edn +++ b/config/keys_arranger.edn @@ -2,8 +2,8 @@ (@q launch) (@t select :select-track-header) (@s select :select-scene-header) -(@tab edit :clip-selected) -(@enter edit :clip-selected) +(@tab edit :clip-selected) +(@enter edit :clip-selected) (@shift-I project input-add) (@shift-O project output-add) (@shift-S project scene-add) diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index 58c8eeab..632633f6 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -49,6 +49,9 @@ impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } } has_clips!(|self: App|self.pool.clips); +impl HasClipsSize for App { + fn clips_size (&self) -> &Measure { &self.project.inner_size } +} //has_editor!(|self: App|{ //editor = self.editor; //editor_w = { diff --git a/crates/device/src/arranger/arranger_model.rs b/crates/device/src/arranger/arranger_model.rs index 5c3bb4d1..87c8ce04 100644 --- a/crates/device/src/arranger/arranger_model.rs +++ b/crates/device/src/arranger/arranger_model.rs @@ -37,6 +37,8 @@ pub struct Arrangement { pub arranger: Arc>, /// Display size pub size: Measure, + /// Display size of clips area + pub inner_size: Measure, } has!(Jack: |self: Arrangement|self.jack); diff --git a/crates/device/src/arranger/arranger_scenes.rs b/crates/device/src/arranger/arranger_scenes.rs index 0aca0946..0376e7eb 100644 --- a/crates/device/src/arranger/arranger_scenes.rs +++ b/crates/device/src/arranger/arranger_scenes.rs @@ -9,23 +9,6 @@ pub trait HasScenes: Has> + Send + Sync { fn scenes_mut (&mut self) -> &mut Vec { Has::>::get_mut(self) } - fn scenes_with_sizes ( - &self, - editing: bool, - height: usize, - larger: usize, - selected_track: Option, - selected_scene: Option, - ) -> impl ScenesSizes<'_> { - let mut y = 0; - 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 - }) - } /// Generate the default name for a new scene fn scene_default_name (&self) -> Arc { format!("s{:3>}", self.scenes().len() + 1).into() diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index ea42c575..1a234398 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -1,12 +1,25 @@ use crate::*; -impl TracksView for T -where T: ScenesView + HasMidiIns + HasMidiOuts + HasSize + HasTrackScroll + HasSelection + HasMidiIns + HasEditor {} +pub trait HasClipsSize { + fn clips_size (&self) -> &Measure; +} +impl HasClipsSize for Arrangement { + fn clips_size (&self) -> &Measure { &self.inner_size } +} + +impl TracksView for Arrangement {} impl ClipsView for T {} pub trait TracksView: - ScenesView + HasMidiIns + HasMidiOuts + HasSize + HasTrackScroll + HasSelection + HasMidiIns + HasEditor + ScenesView + + HasMidiIns + + HasMidiOuts + + HasSize + + HasTrackScroll + + HasSelection + + HasEditor + + HasClipsSize { fn tracks_width_available (&self) -> u16 { (self.width() as u16).saturating_sub(40) @@ -16,11 +29,15 @@ pub trait TracksView: let editor_width = self.editor().map(|e|e.width()); let active_track = self.selection().track(); let mut x = 0; - self.tracks().iter().enumerate().map(move |(index, track)|{ - let width = active_track.and_then(|_|editor_width).unwrap_or(track.width.max(8)); - let data = (index, track, x, x + width); - x += width + Self::TRACK_SPACING; - data + self.tracks().iter().enumerate().map_while(move |(index, track)|{ + if x < self.clips_size().w() { + let width = active_track.and_then(|_|editor_width).unwrap_or(track.width.max(8)); + let data = (index, track, x, x + width); + x += width + Self::TRACK_SPACING; + Some(data) + } else { + None + } }) } fn tracks_with_sizes_scrolled <'t> (&'t self) -> impl TracksSizes<'t> { @@ -53,7 +70,7 @@ pub trait TracksView: fn view_track_names (&self, theme: ItemTheme) -> impl Content { self.view_track_row_section( theme, - button_2("t", "rack", false), + button_2("t", "rack", false), button_2("T", "+", false), Tui::bg(theme.darker.rgb, Fixed::y(1, Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ @@ -130,7 +147,11 @@ pub trait TracksView: add(&Fixed::xy(self.track_width(index, track), h + 1, Align::nw(Bsp::s( Tui::bg(track.color.base.rgb, - Fill::x(Align::w(format!("·mon ·rec ·dub")))), + Fill::x(Align::w(row!( + Either(track.sequencer.monitoring, Tui::fg(Green, "●mon "), "·mon "), + Either(track.sequencer.recording, Tui::fg(Red, "●rec "), "·rec "), + Either(track.sequencer.overdub, Tui::fg(Yellow, "●dub "), "·dub "), + )))), Map::south(1, ||track.sequencer.midi_ins.iter(), |port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb, Fill::x(Align::w(format!("·i{index:02} {}", port.name()))))))))); @@ -147,7 +168,14 @@ pub trait TracksView: } } -pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + Send + Sync { +pub trait ScenesView: + HasEditor + + HasSelection + + HasSceneScroll + + HasClipsSize + + Send + + Sync +{ /// Default scene height. const H_SCENE: usize = 2; /// Default editor height. @@ -155,9 +183,28 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + Send + Sync { fn h_scenes (&self) -> u16; fn w_side (&self) -> u16; fn w_mid (&self) -> u16; + fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> { + let editing = self.editor().is_some(); + let height = Self::H_SCENE; + let larger = self.editor().map(|e|e.height()).unwrap_or(Self::H_SCENE); + let selected_track = self.selection().track(); + let selected_scene = self.selection().scene(); + let mut y = 0; + self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{ + if y < self.clips_size().h() { + 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; + Some(data) + } else { + None + } + }) + } fn view_scenes_names (&self) -> impl Content { Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ - for (index, scene) in self.scenes().iter().enumerate().skip(self.scene_scroll()) { + for (index, scene, ..) in self.scenes_with_sizes() { add(&self.view_scene_name(index, scene)); } }) @@ -166,7 +213,7 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + Send + Sync { let h = if self.selection().scene() == Some(index) && let Some(editor) = self.editor() { (editor.height() as u16).max(12) } else { - Self::H_SCENE as u16 + Self::H_SCENE as u16 }; let bg = if self.selection().scene() == Some(index) { scene.color.light.rgb @@ -185,30 +232,38 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + Send + Sync { } } -pub trait ClipsView: TracksView + ScenesView + Send + Sync { +pub trait ClipsView: + TracksView + + ScenesView + + HasClipsSize + + Send + + Sync +{ fn view_scenes_clips <'a> (&'a self) -> impl Content + 'a { - Fill::xy(Stack::::east(move|column: &mut dyn FnMut(&dyn Render)|{ - for (track_index, track, _, _) in self.tracks_with_sizes() { - //column(&Fixed::x(5, Fill::xy(Tui::bg(Green, "kyp")))); - column(&Fixed::x( - if self.selection().track() == Some(track_index) - && let Some(editor) = self.editor() - { - editor.width().max(24).max(track.width) - } else { - track.width - } as u16, - Fill::y(self.view_track_clips(track_index, track)) - )) - } - })) + self.clips_size().of(Fill::xy(Bsp::a( + Fill::xy(Align::se(Tui::fg(Green, format!("{}x{}", self.clips_size().w(), self.clips_size().h())))), + Stack::::east(move|column: &mut dyn FnMut(&dyn Render)|{ + for (track_index, track, _, _) in self.tracks_with_sizes() { + //column(&Fixed::x(5, Fill::xy(Tui::bg(Green, "kyp")))); + column(&Fixed::x( + if self.selection().track() == Some(track_index) + && let Some(editor) = self.editor() + { + editor.width().max(24).max(track.width) + } else { + track.width + } as u16, + Fill::y(self.view_track_clips(track_index, track)) + )) + } + })))) } fn view_track_clips <'a> (&'a self, track_index: usize, track: &'a Track) -> impl Content + 'a { Stack::south(move|cell: &mut dyn FnMut(&dyn Render)|{ - for (scene_index, scene) in self.scenes().iter().enumerate().skip(self.scene_scroll()) { + for (scene_index, scene, ..) in self.scenes_with_sizes() { let (name, theme): (Arc, ItemTheme) = if let Some(Some(clip)) = &scene.clips.get(track_index) { let clip = clip.read().unwrap(); (clip.name.clone(), clip.color) diff --git a/crates/device/src/sequencer/seq_model.rs b/crates/device/src/sequencer/seq_model.rs index 5524dc04..22bda520 100644 --- a/crates/device/src/sequencer/seq_model.rs +++ b/crates/device/src/sequencer/seq_model.rs @@ -51,7 +51,7 @@ impl Default for Sequencer { play_clip: None, next_clip: None, recording: false, - monitoring: false, + monitoring: true, overdub: false, notes_in: RwLock::new([false;128]).into(), diff --git a/deps/tengri b/deps/tengri index a55e84c2..c9f01648 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit a55e84c29f51606e0996f7f88b7664ca0d37365b +Subproject commit c9f01648712a0c3c1a8290fc09c65746decbbbcb From aeb1f7a9e00527cedb96b04851c84929432ec80d Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 18:33:51 +0300 Subject: [PATCH 12/17] align command buttons --- config/config_arranger.edn | 4 +- crates/app/src/view.rs | 6 +-- crates/cli/tek.rs | 6 +-- crates/device/src/arranger/arranger_api.rs | 16 ++++++ crates/device/src/arranger/arranger_view.rs | 58 ++++++++++----------- 5 files changed, 52 insertions(+), 38 deletions(-) diff --git a/config/config_arranger.edn b/config/config_arranger.edn index 43a6577d..88191575 100644 --- a/config/config_arranger.edn +++ b/config/config_arranger.edn @@ -24,7 +24,7 @@ (bsp/s :view-status-h2 (bsp/n (fill/x (align/w :view-tracks-inputs)) - (bsp/n (fill/x (align/w :view-tracks-devices)) + (bsp/s (fill/x (align/w :view-tracks-names)) (bsp/s (fill/x (align/w :view-tracks-outputs)) - (bsp/s (fill/x (align/w :view-tracks-tracks)) + (bsp/s (fill/x (align/w :view-tracks-devices)) :view-tracks-scenes))))))))) diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 90d9983e..c0900f5e 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -118,13 +118,13 @@ impl App { Fixed::y(2, self.project.view_track_inputs(self.color)) } pub fn view_tracks_outputs <'a> (&'a self) -> impl Content + use<'a> { - Fixed::y(3, self.project.view_track_outputs(self.color)) + Fixed::y(1 + self.project.midi_outs.len() as u16, self.project.view_track_outputs(self.color, self.midi_outs().len().min(24) as u16)) } pub fn view_tracks_devices <'a> (&'a self) -> impl Content + use<'a> { Fixed::y(3, self.project.view_track_devices(self.color)) } - pub fn view_tracks_tracks <'a> (&'a self) -> impl Content + use<'a> { - Fixed::y(1, self.project.view_track_names(self.color)) + pub fn view_tracks_names <'a> (&'a self) -> impl Content + use<'a> { + Fixed::y(2, self.project.view_track_names(self.color)) } pub fn view_pool (&self) -> impl Content + use<'_> { Fixed::x(20, Bsp::s( diff --git a/crates/cli/tek.rs b/crates/cli/tek.rs index 1cb5dc7e..32a10df7 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -56,11 +56,11 @@ pub enum LaunchMode { /// 🎧 Multi-track MIDI sequencer. Arranger { /// Number of scenes - #[arg(short = 'y', long, default_value_t = 8)] scenes: usize, + #[arg(short = 'y', long, default_value_t = 16)] scenes: usize, /// Number of tracks - #[arg(short = 'x', long, default_value_t = 4)] tracks: usize, + #[arg(short = 'x', long, default_value_t = 12)] tracks: usize, /// Width of tracks - #[arg(short = 'w', long, default_value_t = 14)] track_width: usize, + #[arg(short = 'w', long, default_value_t = 15)] track_width: usize, }, /// TODO: A MIDI-controlled audio mixer Mixer, diff --git a/crates/device/src/arranger/arranger_api.rs b/crates/device/src/arranger/arranger_api.rs index aba398e9..6710bd73 100644 --- a/crates/device/src/arranger/arranger_api.rs +++ b/crates/device/src/arranger/arranger_api.rs @@ -129,6 +129,22 @@ impl ArrangementCommand { todo!("delegate"); Ok(None) } + fn output_add (arranger: &mut Arrangement) -> Perhaps { + arranger.midi_outs.push(JackMidiOut::new( + arranger.jack(), + format!("/M{}", arranger.midi_outs.len() + 1), + &[] + )?); + Ok(None) + } + fn input_add (arranger: &mut Arrangement) -> Perhaps { + arranger.midi_ins.push(JackMidiIn::new( + arranger.jack(), + format!("M{}/", arranger.midi_ins.len() + 1), + &[] + )?); + Ok(None) + } fn scene_add (arranger: &mut Arrangement) -> Perhaps { let index = arranger.scene_add(None, None)?.0; *arranger.selection_mut() = match arranger.selection() { diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index 1a234398..e27773c7 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -55,9 +55,9 @@ pub trait TracksView: content: impl Content ) -> impl Content { Bsp::w( - Fixed::x(4, button_add), + Fill::y(Fixed::x(4, Align::nw(button_add))), Bsp::e( - Fixed::x(20, Align::w(button)), + Fixed::x(20, Fill::y(Align::nw(button))), Fill::xy(Align::c(content)) ) ) @@ -72,7 +72,7 @@ pub trait TracksView: theme, button_2("t", "rack", false), button_2("T", "+", false), - Tui::bg(theme.darker.rgb, Fixed::y(1, Fill::x( + Tui::bg(theme.darker.rgb, Fixed::y(2, Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self.tracks_with_sizes() { add(&Fixed::x(self.track_width(index, track), @@ -80,39 +80,37 @@ pub trait TracksView: track.color.light.rgb } else { track.color.base.rgb - }, Align::nw(Bsp::e( + }, Bsp::s(Fill::x(Align::nw(Bsp::e( format!("·t{index:02} "), Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &track.name)) - ))))); + ))), Tui::bg(if self.selection().track() == Some(index) { + track.color.light.rgb + } else { + track.color.base.rgb + }, Fill::x(Align::w(format!("·mute ·solo")))))))); } }))))) } - fn view_track_outputs <'a> (&'a self, theme: ItemTheme) -> impl Content { - let mut h = 0u16; - for track in self.tracks().iter() { - h = h.max(track.sequencer.midi_outs.len() as u16); - } - self.view_track_row_section( - theme, - button_2("o", "utput", false), + fn view_track_outputs <'a> (&'a self, theme: ItemTheme, h: u16) -> impl Content { + self.view_track_row_section(theme, + Bsp::s( + Fill::x(Align::w(button_2("o", "utput", false))), + Fill::xy(Stack::south(|add: &mut dyn FnMut(&dyn Render)|{ + for port in self.midi_outs().iter() { + add(&Push::x(2, Fill::x(Align::w(port.name())))); + } + }))), button_2("O", "+", false), - Align::w(Fixed::y(1 + h*2, - Tui::bg(theme.darker.rgb, Align::w(Fill::x( - Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ - for (index, track, x1, x2) in self.tracks_with_sizes() { - add(&Fixed::x(self.track_width(index, track), - Align::nw(Bsp::n( - Tui::bg(if self.selection().track() == Some(index) { - track.color.light.rgb - } else { - track.color.base.rgb - }, Fill::x(Align::w(format!("·mute ·solo")))), - Map::south(2, ||track.sequencer.midi_outs.iter(), - |port, index|Tui::fg(Rgb(255, 255, 255), - Fixed::y(2, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( - format!("·o{index:02} {}", port.name()))))))))))); - } - }))))))) + Tui::bg(theme.darker.rgb, Align::w(Fill::x( + Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ + for (index, track, x1, x2) in self.tracks_with_sizes() { + add(&Fixed::x(self.track_width(index, track), + Align::nw(Fill::y(Map::south(1, ||track.sequencer.midi_outs.iter(), + |port, index|Tui::fg(Rgb(255, 255, 255), + Fixed::y(1, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( + format!("·o{index:02} {}", port.name()))))))))))); + } + }))))) } fn view_track_devices <'a> (&'a self, theme: ItemTheme) -> impl Content { let mut h = 2u16; From 4f575246ef46c92f2e6417370e07a67d616d9fd6 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 18:57:29 +0300 Subject: [PATCH 13/17] arranger: trim scenes/tracks more harshly favor empty space over overlap. later centered and/or partial. --- config/config_arranger.edn | 3 +- crates/app/src/view.rs | 4 +- crates/device/src/arranger/arranger_view.rs | 83 +++++++++++++++++---- 3 files changed, 72 insertions(+), 18 deletions(-) diff --git a/config/config_arranger.edn b/config/config_arranger.edn index 88191575..828b549d 100644 --- a/config/config_arranger.edn +++ b/config/config_arranger.edn @@ -21,8 +21,7 @@ (bsp/a :view-dialog (bsp/w :view-meters-output (bsp/e :view-meters-input - (bsp/s - :view-status-h2 + (bsp/n (fixed/y 2 :view-status-h2) (bsp/n (fill/x (align/w :view-tracks-inputs)) (bsp/s (fill/x (align/w :view-tracks-names)) (bsp/s (fill/x (align/w :view-tracks-outputs)) diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index c0900f5e..e42143d7 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -115,10 +115,10 @@ impl App { ) } pub fn view_tracks_inputs <'a> (&'a self) -> impl Content + use<'a> { - Fixed::y(2, self.project.view_track_inputs(self.color)) + Fixed::y(2 + self.project.midi_ins.len() as u16, self.project.view_inputs(self.color)) } pub fn view_tracks_outputs <'a> (&'a self) -> impl Content + use<'a> { - Fixed::y(1 + self.project.midi_outs.len() as u16, self.project.view_track_outputs(self.color, self.midi_outs().len().min(24) as u16)) + Fixed::y(1 + self.project.midi_outs.len() as u16, self.project.view_outputs(self.color)) } pub fn view_tracks_devices <'a> (&'a self) -> impl Content + use<'a> { Fixed::y(3, self.project.view_track_devices(self.color)) diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index e27773c7..dbf87f2c 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -1,5 +1,67 @@ use crate::*; +impl Arrangement { + pub fn view_inputs <'a> (&'a self, theme: ItemTheme) -> impl Content + 'a { + let mut h = 1u16; + for track in self.tracks().iter() { + h = h.max(track.sequencer.midi_ins.len() as u16); + } + let h = h + 1; + self.view_track_row_section( + theme, + Bsp::s( + Fill::x(Align::w(button_2("i", "nput", false))), + Fill::x(Align::nw(Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ + add(&"kyp"); + for (index, port) in self.midi_ins.iter().enumerate() { + add(&Fill::x(Align::w(format!("·i{index:02} {}", port.name())))); + } + })))), + button_2("I", "+", false), + Tui::bg(theme.darker.rgb, Align::w(Fill::x( + Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ + for (index, track, x1, x2) in self.tracks_with_sizes() { + add(&Fixed::xy(self.track_width(index, track), h, + Align::nw(Bsp::s( + Tui::bg(track.color.base.rgb, + Fill::x(Align::w(row!( + Either(track.sequencer.monitoring, Tui::fg(Green, "●mon "), "·mon "), + Either(track.sequencer.recording, Tui::fg(Red, "●rec "), "·rec "), + Either(track.sequencer.overdub, Tui::fg(Yellow, "●dub "), "·dub "), + )))), + Map::south(1, ||track.sequencer.midi_ins.iter(), + |port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb, + Fill::x(Align::w(format!("·i{index:02} {}", port.name()))))))))); + }}))))) + } + pub fn view_outputs <'a> (&'a self, theme: ItemTheme) -> impl Content + 'a { + let mut h = 1u16; + for track in self.tracks().iter() { + h = h.max(track.sequencer.midi_outs.len() as u16); + } + let h = h + 1; + self.view_track_row_section( + theme, + Bsp::s(Fill::x(Align::w(button_2("o", "utput", false))), + Fill::xy(Align::nw(Stack::south(|add: &mut dyn FnMut(&dyn Render)|{ + for (index, port) in self.midi_outs().iter().enumerate() { + add(&Fill::x(Align::w(format!("·o{index:02} {}", port.name())))); + } + })))), + button_2("O", "+", false), + Tui::bg(theme.darker.rgb, Align::w(Fill::x( + Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ + for (index, track, x1, x2) in self.tracks_with_sizes() { + add(&Fixed::x(self.track_width(index, track), + Align::nw(Fill::y(Map::south(1, ||track.sequencer.midi_outs.iter(), + |port, index|Tui::fg(Rgb(255, 255, 255), + Fixed::y(1, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( + format!("·o{index:02} {}", port.name()))))))))))); + } + }))))) + } +} + pub trait HasClipsSize { fn clips_size (&self) -> &Measure; } @@ -30,8 +92,8 @@ pub trait TracksView: let active_track = self.selection().track(); let mut x = 0; self.tracks().iter().enumerate().map_while(move |(index, track)|{ - if x < self.clips_size().w() { - let width = active_track.and_then(|_|editor_width).unwrap_or(track.width.max(8)); + let width = active_track.and_then(|_|editor_width).unwrap_or(track.width.max(8)); + if x + width < self.clips_size().w() { let data = (index, track, x, x + width); x += width + Self::TRACK_SPACING; Some(data) @@ -40,13 +102,6 @@ pub trait TracksView: } }) } - fn tracks_with_sizes_scrolled <'t> (&'t self) -> impl TracksSizes<'t> { - self.tracks_with_sizes() - .skip(self.track_scroll()) - .map_while(move|(t, track, x1, x2)| - ((x2 as u16) < self.tracks_width_available()) - .then_some((t, track, x1, x2))) - } fn view_track_row_section <'a> ( &'a self, theme: ItemTheme, @@ -97,7 +152,7 @@ pub trait TracksView: Fill::x(Align::w(button_2("o", "utput", false))), Fill::xy(Stack::south(|add: &mut dyn FnMut(&dyn Render)|{ for port in self.midi_outs().iter() { - add(&Push::x(2, Fill::x(Align::w(port.name())))); + add(&Fill::x(Align::w(port.name()))); } }))), button_2("O", "+", false), @@ -189,10 +244,10 @@ pub trait ScenesView: let selected_scene = self.selection().scene(); let mut y = 0; self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{ - if y < self.clips_size().h() { - 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); + let active = editing && selected_track.is_some() && selected_scene == Some(s); + let height = if active { larger } else { height }; + if y + height < self.clips_size().h() { + let data = (s, scene, y, y + height); y += height; Some(data) } else { From f938ade8394b3e80f2695b46644a9ebd71365037 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 19:27:27 +0300 Subject: [PATCH 14/17] wip: full screen editor in arranger --- config/config_arranger.edn | 6 ++- crates/app/src/view.rs | 21 +++++---- crates/device/src/arranger/arranger_view.rs | 52 ++++++++++----------- deps/tengri | 2 +- 4 files changed, 44 insertions(+), 37 deletions(-) diff --git a/config/config_arranger.edn b/config/config_arranger.edn index 828b549d..f1db5933 100644 --- a/config/config_arranger.edn +++ b/config/config_arranger.edn @@ -3,12 +3,12 @@ (info "A session grid.") (keys + (layer-if :is-editing "./keys_editor.edn") (layer-if :focus-message "./keys_message.edn") (layer-if :focus-device-add "./keys_device_add.edn") (layer-if :focus-browser "./keys_browser.edn") (layer-if :focus-pool-rename "./keys_rename.edn") (layer-if :focus-pool-length "./keys_length.edn") - (layer-if :focus-editor "./keys_editor.edn") (layer-if :focus-clip "./keys_clip.edn") (layer-if :focus-track "./keys_track.edn") (layer-if :focus-scene "./keys_scene.edn") @@ -26,4 +26,6 @@ (bsp/s (fill/x (align/w :view-tracks-names)) (bsp/s (fill/x (align/w :view-tracks-outputs)) (bsp/s (fill/x (align/w :view-tracks-devices)) - :view-tracks-scenes))))))))) + (either :is-editing + :view-editor + :view-tracks-scenes)))))))))) diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index e42143d7..2f4d9207 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -33,17 +33,22 @@ impl App { ))); { let cache = self.view_cache.read().unwrap(); - add(&Align::w(Bsp::s( + add(&Fixed::x(16, Align::w(Bsp::s( FieldH(theme, "Beat", cache.beat.view.clone()), FieldH(theme, "Time", cache.time.view.clone()), - ))); - add(&Align::w(Bsp::s( + )))); + add(&Fixed::x(16, Align::w(Bsp::s( Fill::x(Align::w(FieldH(theme, "BPM", cache.bpm.view.clone()))), Fill::x(Align::w(FieldH(theme, "SR ", cache.sr.view.clone()))), - ))); - add(&FieldH(theme, "Buf", - Bsp::e(cache.buf.view.clone(), Bsp::e(" = ", cache.lat.view.clone())) - )); + )))); + add(&Fixed::x(16, Align::w(Bsp::s( + Fill::x(Align::w(FieldH(theme, "Buf", cache.buf.view.clone()))), + Fill::x(Align::w(FieldH(theme, "Lat", cache.lat.view.clone()))), + )))); + add(&FieldV(theme, "Selection", Fill::x(Align::w(self.selection().describe( + self.tracks(), + self.scenes() + ))))); } })) } @@ -111,7 +116,7 @@ impl App { pub fn view_tracks_scenes (&self) -> impl Content + use<'_> { Bsp::e( Fixed::x(20, Align::nw(self.project.view_scenes_names())), - Bsp::w(self.view_pool(), self.project.view_scenes_clips()), + self.project.view_scenes_clips(), ) } pub fn view_tracks_inputs <'a> (&'a self) -> impl Content + use<'a> { diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index dbf87f2c..183cab93 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -10,13 +10,12 @@ impl Arrangement { self.view_track_row_section( theme, Bsp::s( - Fill::x(Align::w(button_2("i", "nput", false))), - Fill::x(Align::nw(Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ - add(&"kyp"); + Fixed::y(1, Fill::x(Align::w(button_3("i", "nput ", format!("{}", self.midi_ins.len()), false)))), + Fixed::y(h - 1, Fill::x(Align::nw(Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, port) in self.midi_ins.iter().enumerate() { add(&Fill::x(Align::w(format!("·i{index:02} {}", port.name())))); } - })))), + }))))), button_2("I", "+", false), Tui::bg(theme.darker.rgb, Align::w(Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ @@ -42,12 +41,13 @@ impl Arrangement { let h = h + 1; self.view_track_row_section( theme, - Bsp::s(Fill::x(Align::w(button_2("o", "utput", false))), - Fill::xy(Align::nw(Stack::south(|add: &mut dyn FnMut(&dyn Render)|{ + Bsp::s( + Fixed::y(1, Fill::x(Align::w(button_3("o", "utput", format!("{}", self.midi_outs.len()), false)))), + Fixed::y(h - 1, Fill::xy(Align::nw(Stack::south(|add: &mut dyn FnMut(&dyn Render)|{ for (index, port) in self.midi_outs().iter().enumerate() { add(&Fill::x(Align::w(format!("·o{index:02} {}", port.name())))); } - })))), + }))))), button_2("O", "+", false), Tui::bg(theme.darker.rgb, Align::w(Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ @@ -60,6 +60,24 @@ impl Arrangement { } }))))) } + pub fn view_track_devices <'a> (&'a self, theme: ItemTheme) -> impl Content + 'a { + let mut h = 2u16; + for track in self.tracks().iter() { + h = h.max(track.devices.len() as u16); + } + self.view_track_row_section( + theme, + button_3("d", "evice", format!("{}", self.track().map(|t|t.devices.len()).unwrap_or(0)), false), + button_2("D", "+", false), + Fixed::y(h, Tui::bg(theme.darker.rgb, Align::w(Fill::x(Stack::east( + move|add: &mut dyn FnMut(&dyn Render)|{ + for (index, track, x1, x2) in self.tracks_with_sizes() { + add(&Fixed::xy(self.track_width(index, track), h + 1, + Tui::bg(track.color.dark.rgb, Align::nw(Map::south(1, move||0..h, + |_, index|format!("·d{index:02} {}", "--------")))))); + } + })))))) + } } pub trait HasClipsSize { @@ -125,7 +143,7 @@ pub trait TracksView: fn view_track_names (&self, theme: ItemTheme) -> impl Content { self.view_track_row_section( theme, - button_2("t", "rack", false), + button_2("t", "rack ", false), button_2("T", "+", false), Tui::bg(theme.darker.rgb, Fixed::y(2, Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ @@ -167,24 +185,6 @@ pub trait TracksView: } }))))) } - fn view_track_devices <'a> (&'a self, theme: ItemTheme) -> impl Content { - let mut h = 2u16; - for track in self.tracks().iter() { - h = h.max(track.devices.len() as u16); - } - self.view_track_row_section( - theme, - button_2("d", "evice", false), - button_2("D", "+", false), - Fixed::y(h, Tui::bg(theme.darker.rgb, Align::w(Fill::x(Stack::east( - move|add: &mut dyn FnMut(&dyn Render)|{ - for (index, track, x1, x2) in self.tracks_with_sizes() { - add(&Fixed::xy(self.track_width(index, track), h + 1, - Tui::bg(track.color.dark.rgb, Align::nw(Map::south(1, move||0..h, - |_, index|format!("·d{index:02} {}", "--------")))))); - } - })))))) - } fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Content { let mut h = 0u16; for track in self.tracks().iter() { diff --git a/deps/tengri b/deps/tengri index c9f01648..b1275265 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit c9f01648712a0c3c1a8290fc09c65746decbbbcb +Subproject commit b1275265702a835d8cf69fbee2ddee1915f6024b From 3e748fefa7c1f201dde4b5659a6a63041aaf13f5 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 20:08:29 +0300 Subject: [PATCH 15/17] per-port routing; enter/exit fullscreen editor --- config/config_arranger.edn | 10 ++-- config/keys_arranger.edn | 5 +- crates/app/src/api.rs | 23 -------- crates/app/src/view.rs | 8 ++- crates/device/src/arranger/arranger_api.rs | 27 +++++++++ crates/device/src/arranger/arranger_view.rs | 62 ++++++++++++--------- deps/tengri | 2 +- 7 files changed, 79 insertions(+), 58 deletions(-) diff --git a/config/config_arranger.edn b/config/config_arranger.edn index f1db5933..dac9ddc4 100644 --- a/config/config_arranger.edn +++ b/config/config_arranger.edn @@ -23,9 +23,9 @@ (bsp/e :view-meters-input (bsp/n (fixed/y 2 :view-status-h2) (bsp/n (fill/x (align/w :view-tracks-inputs)) - (bsp/s (fill/x (align/w :view-tracks-names)) + (bsp/s (fill/x (align/w :view-tracks-devices)) (bsp/s (fill/x (align/w :view-tracks-outputs)) - (bsp/s (fill/x (align/w :view-tracks-devices)) - (either :is-editing - :view-editor - :view-tracks-scenes)))))))))) + (bsp/s (fill/x (align/w :view-tracks-names)) + (fill/xy (either :is-editing + (bsp/e (fixed/x 20 :view-scenes-names) :view-editor) + :view-scenes))))))))))) diff --git a/config/keys_arranger.edn b/config/keys_arranger.edn index ee691491..6da6a560 100644 --- a/config/keys_arranger.edn +++ b/config/keys_arranger.edn @@ -2,8 +2,9 @@ (@q launch) (@t select :select-track-header) (@s select :select-scene-header) -(@tab edit :clip-selected) -(@enter edit :clip-selected) +(@tab project edit) +(@enter project edit) +(@escape project home) (@shift-I project input-add) (@shift-O project output-add) (@shift-S project scene-add) diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index 4ee8b868..e15634e1 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -18,29 +18,6 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm })); #[tengri_proc::command(App)] impl AppCommand { - fn edit (app: &mut App) -> Perhaps { - app.project.editor = if app.project.editor.is_some() { - None - } else { - let selection = app.selection().clone(); - match selection { - Selection::TrackClip { track, scene } => { - let clip = &mut app.scenes_mut()[scene].clips[track]; - if clip.is_none() { - //app.clip_auto_create(); - *clip = Some(Arc::new(RwLock::new(MidiClip::new( - "", false, 384, None, Some(ItemTheme::random()) - )))); - } - clip.as_ref().map(|c|c.into()) - } - _ => { - None - } - } - }; - Ok(None) - } fn dialog (app: &mut App, dialog: Option) -> Perhaps { app.toggle_dialog(dialog); Ok(None) diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 2f4d9207..184e6716 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -113,12 +113,18 @@ impl App { pub fn view_audio_outs_status (&self) -> impl Content + use<'_> { self.project.view_audio_outs_status(self.color) } - pub fn view_tracks_scenes (&self) -> impl Content + use<'_> { + pub fn view_scenes (&self) -> impl Content + use<'_> { Bsp::e( Fixed::x(20, Align::nw(self.project.view_scenes_names())), self.project.view_scenes_clips(), ) } + pub fn view_scenes_names (&self) -> impl Content + use<'_> { + self.project.view_scenes_names() + } + pub fn view_scenes_clips (&self) -> impl Content + use<'_> { + self.project.view_scenes_clips() + } pub fn view_tracks_inputs <'a> (&'a self) -> impl Content + use<'a> { Fixed::y(2 + self.project.midi_ins.len() as u16, self.project.view_inputs(self.color)) } diff --git a/crates/device/src/arranger/arranger_api.rs b/crates/device/src/arranger/arranger_api.rs index 6710bd73..104af36e 100644 --- a/crates/device/src/arranger/arranger_api.rs +++ b/crates/device/src/arranger/arranger_api.rs @@ -13,6 +13,33 @@ impl Arrangement { #[tengri_proc::command(Arrangement)] impl ArrangementCommand { + fn home (arranger: &mut Arrangement) -> Perhaps { + arranger.editor = None; + Ok(None) + } + fn edit (arranger: &mut Arrangement) -> Perhaps { + let selection = arranger.selection().clone(); + arranger.editor = if arranger.editor.is_some() { + None + } else { + match selection { + Selection::TrackClip { track, scene } => { + let clip = &mut arranger.scenes_mut()[scene].clips[track]; + if clip.is_none() { + //app.clip_auto_create(); + *clip = Some(Arc::new(RwLock::new(MidiClip::new( + "", false, 384, None, Some(ItemTheme::random()) + )))); + } + clip.as_ref().map(|c|c.into()) + } + _ => { + None + } + } + }; + Ok(None) + } /// Set the selection fn select (arranger: &mut Arrangement, s: Selection) -> Perhaps { *arranger.selection_mut() = s; diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index 183cab93..00776252 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -20,18 +20,20 @@ impl Arrangement { Tui::bg(theme.darker.rgb, Align::w(Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self.tracks_with_sizes() { - add(&Fixed::xy(self.track_width(index, track), h, - Align::nw(Bsp::s( - Tui::bg(track.color.base.rgb, - Fill::x(Align::w(row!( + add(&Fixed::x(self.track_width(index, track), + Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ + let index = 0; + add(&Fixed::y(1, track.sequencer.midi_ins.get(0).map(|port| + Tui::fg_bg(Rgb(255, 255, 255), track.color.base.rgb, + Fill::x(Align::w(format!("·i{index:02} {}", port.name()))))))); + for (index, port) in self.midi_ins().iter().enumerate() { + add(&Fixed::y(1, Align::w(row!( Either(track.sequencer.monitoring, Tui::fg(Green, "●mon "), "·mon "), - Either(track.sequencer.recording, Tui::fg(Red, "●rec "), "·rec "), - Either(track.sequencer.overdub, Tui::fg(Yellow, "●dub "), "·dub "), - )))), - Map::south(1, ||track.sequencer.midi_ins.iter(), - |port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb, - Fill::x(Align::w(format!("·i{index:02} {}", port.name()))))))))); - }}))))) + Either(track.sequencer.recording, Tui::fg(Red, "●rec "), "·rec "), + Either(track.sequencer.overdub, Tui::fg(Yellow, "●dub "), "·dub "), + )))); + } + })))}}))))) } pub fn view_outputs <'a> (&'a self, theme: ItemTheme) -> impl Content + 'a { let mut h = 1u16; @@ -53,10 +55,18 @@ impl Arrangement { Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self.tracks_with_sizes() { add(&Fixed::x(self.track_width(index, track), - Align::nw(Fill::y(Map::south(1, ||track.sequencer.midi_outs.iter(), - |port, index|Tui::fg(Rgb(255, 255, 255), - Fixed::y(1, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( - format!("·o{index:02} {}", port.name()))))))))))); + Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ + let index = 0; + add(&Fixed::y(1, track.sequencer.midi_outs.get(0).map(|port| + Tui::fg_bg(Rgb(255, 255, 255), track.color.base.rgb, + Fill::x(Align::w(format!("·o{index:02} {}", port.name()))))))); + for (index, port) in self.midi_outs().iter().enumerate() { + add(&Fixed::y(1, Align::w(Bsp::e( + Either(true, Tui::fg(Green, "●play "), "·play "), + Either(false, Tui::fg(Yellow, "●solo "), "·solo "), + )))); + } + }))); } }))))) } @@ -110,7 +120,7 @@ pub trait TracksView: let active_track = self.selection().track(); let mut x = 0; self.tracks().iter().enumerate().map_while(move |(index, track)|{ - let width = active_track.and_then(|_|editor_width).unwrap_or(track.width.max(8)); + let width = track.width.max(8); if x + width < self.clips_size().w() { let data = (index, track, x, x + width); x += width + Self::TRACK_SPACING; @@ -143,7 +153,11 @@ pub trait TracksView: fn view_track_names (&self, theme: ItemTheme) -> impl Content { self.view_track_row_section( theme, - button_2("t", "rack ", false), + button_3("t", "rack ", if let Some(track) = self.selection().track() { + format!("{track}/{}", self.tracks().len()) + } else { + format!("{}", self.tracks().len()) + }, false), button_2("T", "+", false), Tui::bg(theme.darker.rgb, Fixed::y(2, Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ @@ -156,11 +170,7 @@ pub trait TracksView: }, Bsp::s(Fill::x(Align::nw(Bsp::e( format!("·t{index:02} "), Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &track.name)) - ))), Tui::bg(if self.selection().track() == Some(index) { - track.color.light.rgb - } else { - track.color.base.rgb - }, Fill::x(Align::w(format!("·mute ·solo")))))))); + ))), ""))) ); } }))))) } @@ -179,9 +189,9 @@ pub trait TracksView: for (index, track, x1, x2) in self.tracks_with_sizes() { add(&Fixed::x(self.track_width(index, track), Align::nw(Fill::y(Map::south(1, ||track.sequencer.midi_outs.iter(), - |port, index|Tui::fg(Rgb(255, 255, 255), - Fixed::y(1, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( - format!("·o{index:02} {}", port.name()))))))))))); + |port, index|Tui::fg(Rgb(255, 255, 255), + Fixed::y(1, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( + format!("·o{index:02} {}", port.name()))))))))))); } }))))) } @@ -239,7 +249,7 @@ pub trait ScenesView: fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> { let editing = self.editor().is_some(); let height = Self::H_SCENE; - let larger = self.editor().map(|e|e.height()).unwrap_or(Self::H_SCENE); + let larger = 8;//FIXME//self.editor().map(|e|e.height()).unwrap_or(Self::H_SCENE); let selected_track = self.selection().track(); let selected_scene = self.selection().scene(); let mut y = 0; diff --git a/deps/tengri b/deps/tengri index b1275265..12998a94 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit b1275265702a835d8cf69fbee2ddee1915f6024b +Subproject commit 12998a94ea02bc84c1a490783bc76b10789ce37f From eb0547dc37fc17fe95dd2c28f22779a15cc5be95 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 20:21:25 +0300 Subject: [PATCH 16/17] labels and icons --- config/config_arranger.edn | 6 +++--- crates/app/src/model.rs | 3 +++ crates/app/src/view.rs | 13 +++++-------- crates/device/src/arranger/arranger_api.rs | 3 ++- crates/device/src/arranger/arranger_view.rs | 4 ++-- crates/device/src/editor/editor_view_h.rs | 8 ++++---- 6 files changed, 19 insertions(+), 18 deletions(-) diff --git a/config/config_arranger.edn b/config/config_arranger.edn index dac9ddc4..a83b8521 100644 --- a/config/config_arranger.edn +++ b/config/config_arranger.edn @@ -26,6 +26,6 @@ (bsp/s (fill/x (align/w :view-tracks-devices)) (bsp/s (fill/x (align/w :view-tracks-outputs)) (bsp/s (fill/x (align/w :view-tracks-names)) - (fill/xy (either :is-editing - (bsp/e (fixed/x 20 :view-scenes-names) :view-editor) - :view-scenes))))))))))) + (fill/xy (either :is-editing + (bsp/e (fixed/x 20 :view-scenes-names) :view-editor) + :view-scenes))))))))))) diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index 632633f6..c6dabdf7 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -66,6 +66,9 @@ impl HasClipsSize for App { //}); impl App { + pub fn update_clock (&self) { + ViewCache::update_clock(&self.view_cache, self.clock(), self.size.w() > 80) + } pub fn toggle_dialog (&mut self, mut dialog: Option) -> Option { std::mem::swap(&mut self.dialog, &mut dialog); dialog diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 184e6716..55afff65 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -2,13 +2,6 @@ use crate::*; pub(crate) use std::fmt::Write; pub(crate) use ::tengri::tui::ratatui::prelude::Position; -impl App { - - pub fn update_clock (&self) { - ViewCache::update_clock(&self.view_cache, self.clock(), self.size.w() > 80) - } -} - #[tengri_proc::view(TuiOut)] impl App { pub fn view_nil (&self) -> impl Content + use<'_> { @@ -95,7 +88,11 @@ impl App { cache.bpm.view.clone(), cache.beat.view.clone(), cache.time.view.clone()) } pub fn view_editor (&self) -> impl Content + use<'_> { - self.editor() + let bg = self.editor() + .and_then(|editor|editor.clip().clone()) + .map(|clip|clip.read().unwrap().color.darker) + .unwrap_or(self.color.darker); + Fill::xy(Tui::bg(bg.rgb, self.editor())) } pub fn view_editor_status (&self) -> impl Content + use<'_> { self.editor().map(|e|Fixed::x(20, Outer(true, Style::default().fg(Tui::g(96))).enclose( diff --git a/crates/device/src/arranger/arranger_api.rs b/crates/device/src/arranger/arranger_api.rs index 104af36e..46043485 100644 --- a/crates/device/src/arranger/arranger_api.rs +++ b/crates/device/src/arranger/arranger_api.rs @@ -28,7 +28,8 @@ impl ArrangementCommand { if clip.is_none() { //app.clip_auto_create(); *clip = Some(Arc::new(RwLock::new(MidiClip::new( - "", false, 384, None, Some(ItemTheme::random()) + &format!("t{track:02}s{scene:02}"), + false, 384, None, Some(ItemTheme::random()) )))); } clip.as_ref().map(|c|c.into()) diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index 00776252..3a4c4df0 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -329,9 +329,9 @@ pub trait ClipsView: for (scene_index, scene, ..) in self.scenes_with_sizes() { let (name, theme): (Arc, ItemTheme) = if let Some(Some(clip)) = &scene.clips.get(track_index) { let clip = clip.read().unwrap(); - (clip.name.clone(), clip.color) + (format!(" ⏹ {}", &clip.name).into(), clip.color) } else { - (" ---- ".into(), ItemTheme::G[32]) + (" ⏹ -- ".into(), ItemTheme::G[32]) }; let fg = theme.lightest.rgb; let mut outline = theme.base.rgb; diff --git a/crates/device/src/editor/editor_view_h.rs b/crates/device/src/editor/editor_view_h.rs index 1223d57e..bf04f16f 100644 --- a/crates/device/src/editor/editor_view_h.rs +++ b/crates/device/src/editor/editor_view_h.rs @@ -46,19 +46,19 @@ pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) (note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n)) } -content!(TuiOut:|self: PianoHorizontal| Tui::bg(Tui::g(40), Bsp::s( +content!(TuiOut:|self: PianoHorizontal| Bsp::s( Bsp::e( Fixed::x(5, format!("{}x{}", self.size.w(), self.size.h())), self.timeline() ), Bsp::e( self.keys(), - self.size.of(Tui::bg(Tui::g(32), Bsp::b( + self.size.of(Bsp::b( Fill::xy(self.notes()), Fill::xy(self.cursor()), - ))) + )) ), -))); +)); impl PianoHorizontal { /// Draw the piano roll background. From 2858b01bd49e567fd5f91dc6cf13b26ce47f16e3 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 20:58:02 +0300 Subject: [PATCH 17/17] arranger: now we're talkin --- config/keys_arranger.edn | 2 +- config/keys_global.edn | 12 +- crates/app/src/config.rs | 199 ---------------- crates/app/src/lib.rs | 1 - crates/app/src/model.rs | 239 +++++++++++++++++--- crates/app/src/view.rs | 96 +------- crates/device/src/arranger/arranger_view.rs | 28 ++- crates/device/src/browser/browser_model.rs | 10 + crates/device/src/device.rs | 7 + crates/device/src/dialog.rs | 113 +++++++++ crates/device/src/lib.rs | 61 ++--- deps/tengri | 2 +- 12 files changed, 379 insertions(+), 391 deletions(-) delete mode 100644 crates/app/src/config.rs create mode 100644 crates/device/src/dialog.rs diff --git a/config/keys_arranger.edn b/config/keys_arranger.edn index 6da6a560..76e1e6b4 100644 --- a/config/keys_arranger.edn +++ b/config/keys_arranger.edn @@ -9,7 +9,7 @@ (@shift-O project output-add) (@shift-S project scene-add) (@shift-T project track-add) -(@shift-D toggle-dialog :dialog-device) +(@shift-D dialog :dialog-device) (@up select :select-scene-prev) (@down select :select-scene-next) diff --git a/config/keys_global.edn b/config/keys_global.edn index 6ea6d748..8e9f4233 100644 --- a/config/keys_global.edn +++ b/config/keys_global.edn @@ -1,9 +1,9 @@ -(@esc dialog :dialog-none) -(@f1 dialog :dialog-help) -(@f6 dialog :dialog-save) -(@f8 dialog :dialog-options) -(@f9 dialog :dialog-load) -(@f10 dialog :dialog-quit) +(@esc dialog :dialog-none) +(@f1 dialog :dialog-help) +(@f6 dialog :dialog-save) +(@f8 dialog :dialog-options) +(@f9 dialog :dialog-load) +(@f10 dialog :dialog-quit) (@u undo 1) (@r redo 1) diff --git a/crates/app/src/config.rs b/crates/app/src/config.rs deleted file mode 100644 index 3b3b3cac..00000000 --- a/crates/app/src/config.rs +++ /dev/null @@ -1,199 +0,0 @@ -use crate::*; -use std::path::PathBuf; - -/// Configuration -#[derive(Default, Debug)] -pub struct Configuration { - /// Path of configuration entrypoint - pub path: PathBuf, - /// Name of configuration - pub name: Option>, - /// Description of configuration - pub info: Option>, - /// View definition - pub view: TokenIter<'static>, - // Input keymap - pub keys: InputMap<'static, App, AppCommand, TuiIn, TokenIter<'static>> -} - -impl Configuration { - - pub fn new (path: &impl AsRef, _watch: bool) -> Usually { - let text = read_and_leak(path.as_ref())?; - let [name, info, view, keys] = Self::parse(TokenIter::from(text))?; - Ok(Self { - path: path.as_ref().into(), - info: info.map(Self::parse_info).flatten(), - name: name.map(Self::parse_name).flatten(), - view: Self::parse_view(view)?, - keys: Self::parse_keys(&path, keys)?, - }) - } - - fn parse (iter: TokenIter) -> Usually<[Option;4]> { - let mut name: Option = None; - let mut info: Option = None; - let mut view: Option = None; - let mut keys: Option = None; - for token in iter { - match token.value { - Value::Exp(_, mut exp) => { - let next = exp.next(); - match next { - Some(Token { value: Value::Key(sym), .. }) => match sym { - "name" => name = Some(exp), - "info" => info = Some(exp), - "keys" => keys = Some(exp), - "view" => view = Some(exp), - _ => return Err( - format!("(e3) unexpected symbol {sym:?}").into() - ) - }, - _ => return Err( - format!("(e2) unexpected exp {:?}", next.map(|x|x.value)).into() - ) - } - }, - t => return Err( - format!("(e1) unexpected token {token:?}").into() - ) - }; - } - Ok([name, info, view, keys]) - } - - fn parse_info (mut iter: TokenIter) -> Option> { - iter.next().and_then(|x|if let Value::Str(x) = x.value { - Some(x.into()) - } else { - None - }) - } - - fn parse_name (mut iter: TokenIter) -> Option> { - iter.next().and_then(|x|if let Value::Str(x) = x.value { - Some(x.into()) - } else { - None - }) - } - - fn parse_view (iter: Option) -> Usually { - if let Some(view) = iter { - Ok(view) - } else { - Err(format!("missing view definition").into()) - } - } - - fn parse_keys (base: &impl AsRef, iter: Option>) - -> Usually>> - { - if iter.is_none() { - return Err(format!("missing keys definition").into()) - } - let mut keys = iter.unwrap(); - let mut map = InputMap::default(); - while let Some(token) = keys.next() { - if let Value::Exp(_, mut exp) = token.value { - let next = exp.next(); - if let Some(Token { value: Value::Key(sym), .. }) = next { - match sym { - "layer" => { - let next = exp.next(); - if let Some(Token { value: Value::Str(path), .. }) = next { - let path = base.as_ref().parent().unwrap().join(unquote(path)); - if !std::fs::exists(&path)? { - return Err(format!("(e5) not found: {path:?}").into()) - } - map.add_layer(read_and_leak(path)?.into()); - } else { - return Err(format!("(e4) unexpected non-string {next:?}").into()) - } - }, - - "layer-if" => { - let mut cond = None; - - let next = exp.next(); - if let Some(Token { value: Value::Sym(sym), .. }) = next { - cond = Some(leak(sym)); - } else { - return Err(format!("(e4) unexpected non-symbol {next:?}").into()) - }; - - if let Some(Token { value: Value::Str(path), .. }) = exp.peek() { - let path = base.as_ref().parent().unwrap().join(unquote(path)); - if !std::fs::exists(&path)? { - return Err(format!("(e5) not found: {path:?}").into()) - } - print!("{path:?}..."); - let keys = read_and_leak(path)?.into(); - let cond = cond.unwrap(); - print!("{exp:?}..."); - println!("ok"); - map.add_layer_if( - Box::new(move |state|{ - let mut exp = exp.clone(); - Context::get(state, &mut exp).unwrap_or(false) - }), - keys - ); - } else { - return Err(format!("(e4) unexpected non-symbol {next:?}").into()) - } - }, - - _ => return Err(format!("(e3) unexpected symbol {sym:?}").into()) - } - } else { - return Err(format!("(e2) unexpected exp {:?}", next.map(|x|x.value)).into()) - } - } else { - return Err(format!("(e1) unexpected token {token:?}").into()) - } - } - Ok(map) - } - -} - -fn read_and_leak (path: impl AsRef) -> Usually<&'static str> { - Ok(leak(String::from_utf8(std::fs::read(path.as_ref())?)?)) -} - -fn leak (x: impl AsRef) -> &'static str { - Box::leak(x.as_ref().into()) -} - -fn unquote (x: &str) -> &str { - let mut chars = x.chars(); - chars.next(); - //chars.next_back(); - chars.as_str() -} - -macro_rules! default_config { ($path:literal) => { ($path, include_str!($path)) }; } -pub const DEFAULT_CONFIGS: &'static [(&'static str, &'static str)] = &[ - default_config!("../../../config/config_arranger.edn"), - default_config!("../../../config/config_groovebox.edn"), - default_config!("../../../config/config_sampler.edn"), - default_config!("../../../config/config_sequencer.edn"), - default_config!("../../../config/config_transport.edn"), - - default_config!("../../../config/keys_arranger.edn"), - default_config!("../../../config/keys_clip.edn"), - default_config!("../../../config/keys_clock.edn"), - default_config!("../../../config/keys_editor.edn"), - default_config!("../../../config/keys_global.edn"), - default_config!("../../../config/keys_groovebox.edn"), - default_config!("../../../config/keys_length.edn"), - default_config!("../../../config/keys_mix.edn"), - default_config!("../../../config/keys_pool.edn"), - default_config!("../../../config/keys_pool_file.edn"), - default_config!("../../../config/keys_rename.edn"), - default_config!("../../../config/keys_sampler.edn"), - default_config!("../../../config/keys_scene.edn"), - default_config!("../../../config/keys_sequencer.edn"), - default_config!("../../../config/keys_track.edn"), -]; diff --git a/crates/app/src/lib.rs b/crates/app/src/lib.rs index ee5f836d..cc811d61 100644 --- a/crates/app/src/lib.rs +++ b/crates/app/src/lib.rs @@ -36,7 +36,6 @@ pub(crate) use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed}; mod api; pub use self::api::*; mod audio; pub use self::audio::*; -mod config; pub use self::config::*; mod model; pub use self::model::*; mod view; pub use self::view::*; diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index c6dabdf7..5ede40e6 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -1,4 +1,5 @@ use crate::*; +use std::path::PathBuf; #[derive(Default, Debug)] pub struct App { @@ -28,6 +29,7 @@ pub struct App { has!(Jack: |self: App|self.jack); has!(Pool: |self: App|self.pool); +has!(Option: |self: App|self.dialog); has!(Clock: |self: App|self.project.clock); has!(Option: |self: App|self.project.editor); has!(Selection: |self: App|self.project.selection); @@ -91,12 +93,6 @@ impl App { pub(crate) fn device_pick (&mut self, index: usize) { self.dialog = Some(Dialog::Device(index)); } - pub(crate) fn device_kinds (&self) -> &'static [&'static str] { - &[ - "Sampler", - "Plugin (LV2)", - ] - } pub(crate) fn device_add (&mut self, index: usize) -> Usually<()> { match index { 0 => self.device_add_sampler(), @@ -166,35 +162,6 @@ impl App { } } -/// Various possible dialog overlays -#[derive(Clone, Debug)] -pub enum Dialog { - Help(usize), - Menu(usize), - Device(usize), - Message(Message), - Browser(BrowserTarget, Browser), - Options, -} - -#[derive(Clone, Debug)] -pub enum BrowserTarget { - SaveProject, - LoadProject, - ImportSample(Arc>>), - ExportSample(Arc>>), - ImportClip(Arc>>), - ExportClip(Arc>>), -} - -/// Various possible messages -#[derive(PartialEq, Clone, Copy, Debug)] -pub enum Message { - FailedToAddDevice, -} - -content!(TuiOut: |self: Message| match self { Self::FailedToAddDevice => "Failed to add device." }); - #[tengri_proc::expose] impl App { fn _todo_isize_stub (&self) -> isize { @@ -332,16 +299,214 @@ impl App { } fn device_kind_prev (&self) -> usize { if let Some(Dialog::Device(index)) = self.dialog { - index.overflowing_sub(1).0.min(self.device_kinds().len().saturating_sub(1)) + index.overflowing_sub(1).0.min(device_kinds().len().saturating_sub(1)) } else { 0 } } fn device_kind_next (&self) -> usize { if let Some(Dialog::Device(index)) = self.dialog { - (index + 1) % self.device_kinds().len() + (index + 1) % device_kinds().len() } else { 0 } } } + +/// Configuration +#[derive(Default, Debug)] +pub struct Configuration { + /// Path of configuration entrypoint + pub path: PathBuf, + /// Name of configuration + pub name: Option>, + /// Description of configuration + pub info: Option>, + /// View definition + pub view: TokenIter<'static>, + // Input keymap + pub keys: InputMap<'static, App, AppCommand, TuiIn, TokenIter<'static>>, +} + +impl Configuration { + + pub fn new (path: &impl AsRef, _watch: bool) -> Usually { + let text = read_and_leak(path.as_ref())?; + let [name, info, view, keys] = Self::parse(TokenIter::from(text))?; + Ok(Self { + path: path.as_ref().into(), + info: info.map(Self::parse_info).flatten(), + name: name.map(Self::parse_name).flatten(), + view: Self::parse_view(view)?, + keys: Self::parse_keys(&path, keys)?, + }) + } + + fn parse (iter: TokenIter) -> Usually<[Option;4]> { + let mut name: Option = None; + let mut info: Option = None; + let mut view: Option = None; + let mut keys: Option = None; + for token in iter { + match token.value { + Value::Exp(_, mut exp) => { + let next = exp.next(); + match next { + Some(Token { value: Value::Key(sym), .. }) => match sym { + "name" => name = Some(exp), + "info" => info = Some(exp), + "keys" => keys = Some(exp), + "view" => view = Some(exp), + _ => return Err( + format!("(e3) unexpected symbol {sym:?}").into() + ) + }, + _ => return Err( + format!("(e2) unexpected exp {:?}", next.map(|x|x.value)).into() + ) + } + }, + t => return Err( + format!("(e1) unexpected token {token:?}").into() + ) + }; + } + Ok([name, info, view, keys]) + } + + fn parse_info (mut iter: TokenIter) -> Option> { + iter.next().and_then(|x|if let Value::Str(x) = x.value { + Some(x.into()) + } else { + None + }) + } + + fn parse_name (mut iter: TokenIter) -> Option> { + iter.next().and_then(|x|if let Value::Str(x) = x.value { + Some(x.into()) + } else { + None + }) + } + + fn parse_view (iter: Option) -> Usually { + if let Some(view) = iter { + Ok(view) + } else { + Err(format!("missing view definition").into()) + } + } + + fn parse_keys (base: &impl AsRef, iter: Option>) + -> Usually>> + { + if iter.is_none() { + return Err(format!("missing keys definition").into()) + } + let mut keys = iter.unwrap(); + let mut map = InputMap::default(); + while let Some(token) = keys.next() { + if let Value::Exp(_, mut exp) = token.value { + let next = exp.next(); + if let Some(Token { value: Value::Key(sym), .. }) = next { + match sym { + "layer" => { + if let Some(Token { value: Value::Str(path), .. }) = exp.peek() { + let path = base.as_ref().parent().unwrap().join(unquote(path)); + if !std::fs::exists(&path)? { + return Err(format!("(e5) not found: {path:?}").into()) + } + map.add_layer(read_and_leak(path)?.into()); + print!("layer:\n path: {:?}...", exp.0.0.trim()); + println!("ok"); + } else { + return Err(format!("(e4) unexpected non-string {next:?}").into()) + } + }, + + "layer-if" => { + let mut cond = None; + + let next = exp.next(); + if let Some(Token { value: Value::Sym(sym), .. }) = next { + cond = Some(leak(sym)); + } else { + return Err(format!("(e4) unexpected non-symbol {next:?}").into()) + }; + + if let Some(Token { value: Value::Str(path), .. }) = exp.peek() { + let path = base.as_ref().parent().unwrap().join(unquote(path)); + if !std::fs::exists(&path)? { + return Err(format!("(e5) not found: {path:?}").into()) + } + print!("layer-if:\n cond: {}\n path: {path:?}...", + cond.unwrap_or_default()); + let keys = read_and_leak(path)?.into(); + let cond = cond.unwrap(); + println!("ok"); + map.add_layer_if( + Box::new(move |state|{ + let mut exp = exp.clone(); + Context::get(state, &mut exp).unwrap_or(false) + }), + keys + ); + } else { + return Err(format!("(e4) unexpected non-symbol {next:?}").into()) + } + }, + + _ => return Err(format!("(e3) unexpected symbol {sym:?}").into()) + } + } else { + return Err(format!("(e2) unexpected exp {:?}", next.map(|x|x.value)).into()) + } + } else { + return Err(format!("(e1) unexpected token {token:?}").into()) + } + } + Ok(map) + } + +} + +fn read_and_leak (path: impl AsRef) -> Usually<&'static str> { + Ok(leak(String::from_utf8(std::fs::read(path.as_ref())?)?)) +} + +fn leak (x: impl AsRef) -> &'static str { + Box::leak(x.as_ref().into()) +} + +fn unquote (x: &str) -> &str { + let mut chars = x.chars(); + chars.next(); + //chars.next_back(); + chars.as_str() +} + +macro_rules! default_config { ($path:literal) => { ($path, include_str!($path)) }; } +pub const DEFAULT_CONFIGS: &'static [(&'static str, &'static str)] = &[ + default_config!("../../../config/config_arranger.edn"), + default_config!("../../../config/config_groovebox.edn"), + default_config!("../../../config/config_sampler.edn"), + default_config!("../../../config/config_sequencer.edn"), + default_config!("../../../config/config_transport.edn"), + + default_config!("../../../config/keys_arranger.edn"), + default_config!("../../../config/keys_clip.edn"), + default_config!("../../../config/keys_clock.edn"), + default_config!("../../../config/keys_editor.edn"), + default_config!("../../../config/keys_global.edn"), + default_config!("../../../config/keys_groovebox.edn"), + default_config!("../../../config/keys_length.edn"), + default_config!("../../../config/keys_mix.edn"), + default_config!("../../../config/keys_pool.edn"), + default_config!("../../../config/keys_pool_file.edn"), + default_config!("../../../config/keys_rename.edn"), + default_config!("../../../config/keys_sampler.edn"), + default_config!("../../../config/keys_scene.edn"), + default_config!("../../../config/keys_sequencer.edn"), + default_config!("../../../config/keys_track.edn"), +]; diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 55afff65..1a67e193 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -123,7 +123,7 @@ impl App { self.project.view_scenes_clips() } pub fn view_tracks_inputs <'a> (&'a self) -> impl Content + use<'a> { - Fixed::y(2 + self.project.midi_ins.len() as u16, self.project.view_inputs(self.color)) + Fixed::y(1 + self.project.midi_ins.len() as u16, self.project.view_inputs(self.color)) } pub fn view_tracks_outputs <'a> (&'a self) -> impl Content + use<'a> { Fixed::y(1 + self.project.midi_outs.len() as u16, self.project.view_outputs(self.color)) @@ -163,100 +163,10 @@ impl App { self.project.sampler().map(|s|s.view_meters_output()) } pub fn view_dialog (&self) -> impl Content + use<'_> { - When(self.dialog.is_some(), Bsp::b( "", + self.dialog.as_ref().map(|dialog|Bsp::b("", Fixed::xy(70, 23, Tui::fg_bg(Rgb(255,255,255), Rgb(16,16,16), Bsp::b( Repeat(" "), Outer(true, Style::default().fg(Tui::g(96))) - .enclose(self.dialog.as_ref().map(|dialog|match dialog { - Dialog::Menu(_) => - self.view_dialog_menu().boxed(), - Dialog::Help(offset) => - self.view_dialog_help(*offset).boxed(), - Dialog::Browser(target, browser) => - self.view_dialog_browser(target, browser).boxed(), - Dialog::Options => - self.view_dialog_options().boxed(), - Dialog::Device(index) => - self.view_dialog_device(*index).boxed(), - Dialog::Message(message) => - self.view_dialog_message(message).boxed(), - })) - ))) - )) - } -} - -impl App { - pub fn view_dialog_menu (&self) -> impl Content { - let options = ||["Projects", "Settings", "Help", "Quit"].iter(); - let option = |a,i|Tui::fg(Rgb(255,255,255), format!("{}", a)); - Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option))) - } - pub fn view_dialog_help <'a> (&'a self, offset: usize) -> impl Content + use<'a> { - Bsp::s(Tui::bold(true, "Help"), Bsp::s("", Map::south(1, - move||self.config.keys.layers.iter() - .filter_map(|a|(a.0)(self).then_some(a.1)) - .flat_map(|a|a) - .filter_map(|x|if let Value::Exp(_, iter)=x.value{ Some(iter) } else { None }) - .skip(offset) - .take(20), - |mut b,i|Fixed::x(60, Align::w(Bsp::e("(", Bsp::e( - b.next().map(|t|Fixed::x(16, Align::w(Tui::fg(Rgb(64,224,0), format!("{}", t.value))))), - Bsp::e(" ", Align::w(format!("{}", b.0.0.trim())))))))))) - } - pub fn view_dialog_device (&self, index: usize) -> impl Content + use<'_> { - let choices = ||self.device_kinds().iter(); - let choice = move|label, i| - Fill::x(Tui::bg(if i == index { Rgb(64,128,32) } else { Rgb(0,0,0) }, - Bsp::e(if i == index { "[ " } else { " " }, - Bsp::w(if i == index { " ]" } else { " " }, - label)))); - Bsp::s(Tui::bold(true, "Add device"), Map::south(1, choices, choice)) - } - pub fn view_dialog_message <'a> (&'a self, message: &'a Message) -> impl Content + use<'a> { - Bsp::s(message, Bsp::s("", "[ OK ]")) - } - pub fn view_dialog_browser <'a> (&'a self, target: &BrowserTarget, browser: &'a Browser) -> impl Content + use<'a> { - Bsp::s( - Padding::xy(3, 1, Fill::x(Align::w(FieldV( - self.color, - match target { - BrowserTarget::SaveProject => "Save project:", - BrowserTarget::LoadProject => "Load project:", - BrowserTarget::ImportSample(_) => "Import sample:", - BrowserTarget::ExportSample(_) => "Export sample:", - BrowserTarget::ImportClip(_) => "Import clip:", - BrowserTarget::ExportClip(_) => "Export clip:", - }, - Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))), - Outer(true, Style::default().fg(Tui::g(96))) - .enclose(Fill::xy(browser))) - } - pub fn view_dialog_load <'a> (&'a self, browser: &'a Browser) -> impl Content + use<'a> { - Bsp::s( - Fill::x(Align::w(Margin::xy(1, 1, Bsp::e( - Tui::bold(true, " Load project: "), - Shrink::x(3, Fixed::y(1, RepeatH("🭻"))))))), - Outer(true, Style::default().fg(Tui::g(96))) - .enclose(Fill::xy(browser))) - } - pub fn view_dialog_export <'a> (&'a self, browser: &'a Browser) -> impl Content + use<'a> { - Bsp::s( - Fill::x(Align::w(Margin::xy(1, 1, Bsp::e( - Tui::bold(true, " Export: "), - Shrink::x(3, Fixed::y(1, RepeatH("🭻"))))))), - Outer(true, Style::default().fg(Tui::g(96))) - .enclose(Fill::xy(browser))) - } - pub fn view_dialog_import <'a> (&'a self, browser: &'a Browser) -> impl Content + use<'a> { - Bsp::s( - Fill::x(Align::w(Margin::xy(1, 1, Bsp::e( - Tui::bold(true, " Import: "), - Shrink::x(3, Fixed::y(1, RepeatH("🭻"))))))), - Outer(true, Style::default().fg(Tui::g(96))) - .enclose(Fill::xy(browser))) - } - pub fn view_dialog_options <'a> (&'a self) -> impl Content + use<'a> { - "TODO" + .enclose(dialog)))))) } } diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index 3a4c4df0..55cc5198 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -2,9 +2,9 @@ use crate::*; impl Arrangement { pub fn view_inputs <'a> (&'a self, theme: ItemTheme) -> impl Content + 'a { - let mut h = 1u16; + let mut h = 0; for track in self.tracks().iter() { - h = h.max(track.sequencer.midi_ins.len() as u16); + h = h.max(self.midi_ins.len() as u16); } let h = h + 1; self.view_track_row_section( @@ -153,12 +153,22 @@ pub trait TracksView: fn view_track_names (&self, theme: ItemTheme) -> impl Content { self.view_track_row_section( theme, - button_3("t", "rack ", if let Some(track) = self.selection().track() { - format!("{track}/{}", self.tracks().len()) - } else { - format!("{}", self.tracks().len()) - }, false), - button_2("T", "+", false), + Bsp::s( + button_3("t", "rack ", if let Some(track) = self.selection().track() { + format!("{track}/{}", self.tracks().len()) + } else { + format!("{}", self.tracks().len()) + }, false), + button_3("s", "cene ", if let Some(scene) = self.selection().scene() { + format!("{scene}/{}", self.scenes().len()) + } else { + format!("{}", self.scenes().len()) + }, false) + ), + Bsp::s( + button_2("T", "+", false), + button_2("S", "+", false), + ), Tui::bg(theme.darker.rgb, Fixed::y(2, Fill::x( Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self.tracks_with_sizes() { @@ -256,7 +266,7 @@ pub trait ScenesView: self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{ let active = editing && selected_track.is_some() && selected_scene == Some(s); let height = if active { larger } else { height }; - if y + height < self.clips_size().h() { + if y + height <= self.clips_size().h() { let data = (s, scene, y, y + height); y += height; Some(data) diff --git a/crates/device/src/browser/browser_model.rs b/crates/device/src/browser/browser_model.rs index 213d4276..fe0a0645 100644 --- a/crates/device/src/browser/browser_model.rs +++ b/crates/device/src/browser/browser_model.rs @@ -2,6 +2,16 @@ use crate::*; use std::path::PathBuf; use std::ffi::OsString; +#[derive(Clone, Debug)] +pub enum BrowserTarget { + SaveProject, + LoadProject, + ImportSample(Arc>>), + ExportSample(Arc>>), + ImportClip(Arc>>), + ExportClip(Arc>>), +} + /// Browses for phrase to import/export #[derive(Debug, Clone, Default)] pub struct Browser { diff --git a/crates/device/src/device.rs b/crates/device/src/device.rs index 0512e686..894aa2d2 100644 --- a/crates/device/src/device.rs +++ b/crates/device/src/device.rs @@ -1,5 +1,12 @@ use crate::*; +pub fn device_kinds () -> &'static [&'static str] { + &[ + "Sampler", + "Plugin (LV2)", + ] +} + impl> + Has> HasDevices for T { fn devices (&self) -> &Vec { self.get() diff --git a/crates/device/src/dialog.rs b/crates/device/src/dialog.rs new file mode 100644 index 00000000..f9f7c309 --- /dev/null +++ b/crates/device/src/dialog.rs @@ -0,0 +1,113 @@ +use crate::*; + +/// Various possible dialog overlays +#[derive(Clone, Debug)] +pub enum Dialog { + Help(usize), + Menu(usize), + Device(usize), + Message(Message), + Browser(BrowserTarget, Browser), + Options, +} + +/// Various possible messages +#[derive(PartialEq, Clone, Copy, Debug)] +pub enum Message { + FailedToAddDevice, +} + +content!(TuiOut: |self: Message| match self { + Self::FailedToAddDevice => "Failed to add device." +}); + +content!(TuiOut: |self: Dialog| match self { + Self::Menu(_) => + self.view_dialog_menu().boxed(), + Self::Help(offset) => + self.view_dialog_help(*offset).boxed(), + Self::Browser(target, browser) => + self.view_dialog_browser(target, browser).boxed(), + Self::Options => + self.view_dialog_options().boxed(), + Self::Device(index) => + self.view_dialog_device(*index).boxed(), + Self::Message(message) => + self.view_dialog_message(message).boxed(), +}); + +impl Dialog { + pub fn view_dialog_menu (&self) -> impl Content { + let options = ||["Projects", "Settings", "Help", "Quit"].iter(); + let option = |a,i|Tui::fg(Rgb(255,255,255), format!("{}", a)); + Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option))) + } + pub fn view_dialog_help <'a> (&'a self, offset: usize) -> impl Content + use<'a> { + Bsp::s(Tui::bold(true, "Help"), "FIXME") + //Bsp::s(Tui::bold(true, "Help"), Bsp::s("", Map::south(1, + //move||self.config.keys.layers.iter() + //.filter_map(|a|(a.0)(self).then_some(a.1)) + //.flat_map(|a|a) + //.filter_map(|x|if let Value::Exp(_, iter)=x.value{ Some(iter) } else { None }) + //.skip(offset) + //.take(20), + //|mut b,i|Fixed::x(60, Align::w(Bsp::e("(", Bsp::e( + //b.next().map(|t|Fixed::x(16, Align::w(Tui::fg(Rgb(64,224,0), format!("{}", t.value))))), + //Bsp::e(" ", Align::w(format!("{}", b.0.0.trim())))))))))) + } + pub fn view_dialog_device (&self, index: usize) -> impl Content + use<'_> { + let choices = ||device_kinds().iter(); + let choice = move|label, i| + Fill::x(Tui::bg(if i == index { Rgb(64,128,32) } else { Rgb(0,0,0) }, + Bsp::e(if i == index { "[ " } else { " " }, + Bsp::w(if i == index { " ]" } else { " " }, + label)))); + Bsp::s(Tui::bold(true, "Add device"), Map::south(1, choices, choice)) + } + pub fn view_dialog_message <'a> (&'a self, message: &'a Message) -> impl Content + use<'a> { + Bsp::s(message, Bsp::s("", "[ OK ]")) + } + pub fn view_dialog_browser <'a> (&'a self, target: &BrowserTarget, browser: &'a Browser) -> impl Content + use<'a> { + Bsp::s( + Padding::xy(3, 1, Fill::x(Align::w(FieldV( + Default::default(), + match target { + BrowserTarget::SaveProject => "Save project:", + BrowserTarget::LoadProject => "Load project:", + BrowserTarget::ImportSample(_) => "Import sample:", + BrowserTarget::ExportSample(_) => "Export sample:", + BrowserTarget::ImportClip(_) => "Import clip:", + BrowserTarget::ExportClip(_) => "Export clip:", + }, + Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))), + Outer(true, Style::default().fg(Tui::g(96))) + .enclose(Fill::xy(browser))) + } + pub fn view_dialog_load <'a> (&'a self, browser: &'a Browser) -> impl Content + use<'a> { + Bsp::s( + Fill::x(Align::w(Margin::xy(1, 1, Bsp::e( + Tui::bold(true, " Load project: "), + Shrink::x(3, Fixed::y(1, RepeatH("🭻"))))))), + Outer(true, Style::default().fg(Tui::g(96))) + .enclose(Fill::xy(browser))) + } + pub fn view_dialog_export <'a> (&'a self, browser: &'a Browser) -> impl Content + use<'a> { + Bsp::s( + Fill::x(Align::w(Margin::xy(1, 1, Bsp::e( + Tui::bold(true, " Export: "), + Shrink::x(3, Fixed::y(1, RepeatH("🭻"))))))), + Outer(true, Style::default().fg(Tui::g(96))) + .enclose(Fill::xy(browser))) + } + pub fn view_dialog_import <'a> (&'a self, browser: &'a Browser) -> impl Content + use<'a> { + Bsp::s( + Fill::x(Align::w(Margin::xy(1, 1, Bsp::e( + Tui::bold(true, " Import: "), + Shrink::x(3, Fixed::y(1, RepeatH("🭻"))))))), + Outer(true, Style::default().fg(Tui::g(96))) + .enclose(Fill::xy(browser))) + } + pub fn view_dialog_options <'a> (&'a self) -> impl Content + use<'a> { + "TODO" + } +} diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index 26131511..6041e16c 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -14,54 +14,27 @@ pub(crate) use std::error::Error; pub(crate) use std::ffi::OsString; pub(crate) use ::tengri::{from, has, maybe_has, Usually, Perhaps, Has, MaybeHas}; -pub(crate) use ::tengri::{dsl::*, input::*, output::*, tui::{*, ratatui::prelude::*}}; +pub(crate) use ::tengri::{dsl::*, input::*, output::{*, Margin}, tui::{*, ratatui::prelude::*}}; pub(crate) use ::tek_engine::*; pub(crate) use ::tek_engine::midi::{u7, LiveEvent, MidiMessage}; pub(crate) use ::tek_engine::jack::{Control, ProcessScope, MidiWriter, RawMidi}; pub(crate) use ratatui::{prelude::Rect, widgets::{Widget, canvas::{Canvas, Line}}}; pub(crate) use Color::*; -mod device; -pub use self::device::*; +mod device; pub use self::device::*; +mod dialog; pub use self::dialog::*; -#[cfg(feature = "arranger")] mod arranger; -#[cfg(feature = "arranger")] pub use self::arranger::*; - -#[cfg(feature = "browser")] mod browser; -#[cfg(feature = "browser")] pub use self::browser::*; - -#[cfg(feature = "clock")] mod clock; -#[cfg(feature = "clock")] pub use self::clock::*; - -#[cfg(feature = "editor")] mod editor; -#[cfg(feature = "editor")] pub use self::editor::*; - -#[cfg(feature = "pool")] mod pool; -#[cfg(feature = "pool")] pub use self::pool::*; - -#[cfg(feature = "sequencer")] mod sequencer; -#[cfg(feature = "sequencer")] pub use self::sequencer::*; - -#[cfg(feature = "sampler")] mod sampler; -#[cfg(feature = "sampler")] pub use self::sampler::*; - -#[cfg(feature = "meter")] mod meter; -#[cfg(feature = "meter")] pub use self::meter::*; - -#[cfg(feature = "mixer")] mod mixer; -#[cfg(feature = "mixer")] pub use self::mixer::*; - -#[cfg(feature = "lv2")] mod lv2; -#[cfg(feature = "lv2")] pub use self::lv2::*; - -#[cfg(feature = "sf2")] mod sf2; -#[cfg(feature = "sf2")] pub use self::sf2::*; - -#[cfg(feature = "vst2")] mod vst2; -#[cfg(feature = "vst2")] pub use self::vst2::*; - -#[cfg(feature = "vst3")] mod vst3; -#[cfg(feature = "vst3")] pub use self::vst3::*; - -#[cfg(feature = "clap")] mod clap; -#[cfg(feature = "clap")] pub use self::clap::*; +#[cfg(feature = "arranger")] mod arranger; #[cfg(feature = "arranger")] pub use self::arranger::*; +#[cfg(feature = "browser")] mod browser; #[cfg(feature = "browser")] pub use self::browser::*; +#[cfg(feature = "clock")] mod clock; #[cfg(feature = "clock")] pub use self::clock::*; +#[cfg(feature = "editor")] mod editor; #[cfg(feature = "editor")] pub use self::editor::*; +#[cfg(feature = "pool")] mod pool; #[cfg(feature = "pool")] pub use self::pool::*; +#[cfg(feature = "sequencer")] mod sequencer; #[cfg(feature = "sequencer")] pub use self::sequencer::*; +#[cfg(feature = "sampler")] mod sampler; #[cfg(feature = "sampler")] pub use self::sampler::*; +#[cfg(feature = "meter")] mod meter; #[cfg(feature = "meter")] pub use self::meter::*; +#[cfg(feature = "mixer")] mod mixer; #[cfg(feature = "mixer")] pub use self::mixer::*; +#[cfg(feature = "lv2")] mod lv2; #[cfg(feature = "lv2")] pub use self::lv2::*; +#[cfg(feature = "sf2")] mod sf2; #[cfg(feature = "sf2")] pub use self::sf2::*; +#[cfg(feature = "vst2")] mod vst2; #[cfg(feature = "vst2")] pub use self::vst2::*; +#[cfg(feature = "vst3")] mod vst3; #[cfg(feature = "vst3")] pub use self::vst3::*; +#[cfg(feature = "clap")] mod clap; #[cfg(feature = "clap")] pub use self::clap::*; diff --git a/deps/tengri b/deps/tengri index 12998a94..f21781e8 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit 12998a94ea02bc84c1a490783bc76b10789ce37f +Subproject commit f21781e81664e1991e3985e2377becca9c1d58cf