diff --git a/config/keys_arranger.edn b/config/keys_arranger.edn index 4f52a767..2ab14fb8 100644 --- a/config/keys_arranger.edn +++ b/config/keys_arranger.edn @@ -6,7 +6,7 @@ (@shift-O output add) (@shift-S scene add) (@shift-T track add) -(@shift-D device picker) +(@shift-Z device picker) (@up select :select-scene-prev) (@down select :select-scene-next) diff --git a/config/keys_device_add.edn b/config/keys_device_add.edn index 139cf84e..0b3e1f7d 100644 --- a/config/keys_device_add.edn +++ b/config/keys_device_add.edn @@ -1,4 +1,4 @@ -(@esc device cancel) +(@esc device select-cancel) (@up device pick :device-kind-prev) (@down device pick :device-kind-next) (@enter device add :device-kind) diff --git a/config/keys_global.edn b/config/keys_global.edn index 773a139d..bcb16252 100644 --- a/config/keys_global.edn +++ b/config/keys_global.edn @@ -1,9 +1,5 @@ -(@esc toggle-dialog :dialog-menu) -(@f1 toggle-dialog :dialog-help) -(@f6 toggle-dialog :dialog-save) -(@f8 toggle-dialog :dialog-options) -(@f9 toggle-dialog :dialog-load) -(@f10 toggle-dialog :dialog-quit) +(@esc toggle-menu) +(@f1 toggle-help) -(@u undo 1) -(@r redo 1) +(@u undo 1) +(@shift-u redo 1) diff --git a/config/keys_groovebox.edn b/config/keys_groovebox.edn index 4aa17d4a..62608d87 100644 --- a/config/keys_groovebox.edn +++ b/config/keys_groovebox.edn @@ -1,3 +1 @@ -(@r sampler record/toggle :sample) -(@tab focus-next) -(@shift-tab focus-prev) +(@r sampler record/toggle :sample) diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index 8e2e26fb..3b2acbb6 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -57,21 +57,6 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm fn focus_pool_length (&self) -> bool { matches!(self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), Some(PoolMode::Length(..))) } - fn dialog_help (&self) -> Dialog { - Dialog::Help - } - fn dialog_menu (&self) -> Dialog { - Dialog::Menu - } - fn dialog_save (&self) -> Dialog { - Dialog::Save - } - fn dialog_load (&self) -> Dialog { - Dialog::Load - } - fn dialog_options (&self) -> Dialog { - Dialog::Options - } fn editor_pitch (&self) -> Option { Some((self.editor().map(|e|e.get_note_pos()).unwrap() as u8).into()) } @@ -218,11 +203,15 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm } #[tengri_proc::command(App)] impl AppCommand { - fn toggle_dialog (app: &mut App, dialog: Dialog) -> Perhaps { - app.toggle_dialog(Some(dialog)); + fn toggle_help (app: &mut App) -> Perhaps { + app.toggle_dialog(Some(Dialog::Help)); Ok(None) } - fn toggle_editor (app: &mut App, value: bool) -> Perhaps { + fn toggle_menu (app: &mut App) -> Perhaps { + app.toggle_dialog(Some(Dialog::Menu)); + Ok(None) + } + fn toggle_edit (app: &mut App, value: bool) -> Perhaps { app.toggle_editor(Some(value)); Ok(None) } @@ -357,10 +346,6 @@ impl<'state> Context<'state, SamplerCommand> for App { } #[tengri_proc::command(App)] impl DeviceCommand { - fn cancel (app: &mut App) -> Perhaps { - app.dialog = None; - Ok(None) - } fn picker (app: &mut App) -> Perhaps { app.device_picker_show(); Ok(None) diff --git a/crates/app/src/model/dialog.rs b/crates/app/src/model/dialog.rs index c2149659..5b0313dd 100644 --- a/crates/app/src/model/dialog.rs +++ b/crates/app/src/model/dialog.rs @@ -19,10 +19,7 @@ pub enum Dialog { Help, Menu, Device(usize), - Message(Message), - Save, - Load, - Options, + Message(Message) } /// Various possible messages diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 81ab2239..c6b55c84 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -2,8 +2,6 @@ use crate::*; pub(crate) use std::fmt::Write; pub(crate) use ::tengri::tui::ratatui::prelude::Position; -mod view_output; pub use self::view_output::*; - #[tengri_proc::view(TuiOut)] impl App { pub fn view_nil (&self) -> impl Content + use<'_> { @@ -12,14 +10,18 @@ impl App { pub fn view_status (&self) -> impl Content + use<'_> { self.update_clock(); let cache = self.view_cache.read().unwrap(); - view_status(self.selected.describe(&self.tracks, &self.scenes), - cache.sr.view.clone(), cache.buf.view.clone(), cache.lat.view.clone()) + view_status( + self.selected.describe(&self.tracks, &self.scenes), + cache.sr.view.clone(), cache.buf.view.clone(), cache.lat.view.clone(), + ) } pub fn view_transport (&self) -> impl Content + use<'_> { self.update_clock(); let cache = self.view_cache.read().unwrap(); - view_transport(self.clock.is_rolling(), - cache.bpm.view.clone(), cache.beat.view.clone(), cache.time.view.clone()) + view_transport( + self.clock.is_rolling(), + cache.bpm.view.clone(), cache.beat.view.clone(), cache.time.view.clone(), + ) } pub fn view_arranger (&self) -> impl Content + use<'_> { ArrangerView::new(self) @@ -57,9 +59,6 @@ impl App { .enclose(self.dialog.as_ref().map(|dialog|match dialog { Dialog::Menu => self.view_dialog_menu().boxed(), Dialog::Help => self.view_dialog_help().boxed(), - Dialog::Save => self.view_dialog_save().boxed(), - Dialog::Load => self.view_dialog_load().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(), })) @@ -83,24 +82,23 @@ impl App { } else { None }); - //let binding = ;[> Bsp::e( - //Fixed::x(15, Align::w(Tui::bold(true, Tui::fg(Rgb(255,192,0), if let Some(Token { - //value: Value::Sym(key), .. - //}) = binding.next() { - //Some(key.to_string()) - //} else { - //None - //})))), - //Bsp::e(" ", Tui::fg(Rgb(255,255,255), if let Some(Token { - //value: Value::Key(command), .. - //}) = binding.next() { - //Some(command.to_string()) - //} else { - //None - //})), - //);*/ - Bsp::s(Tui::bold(true, "Help"), Bsp::s("", Map::south(1, bindings, |b,i|format!("{i}:{b:?}")))) - //|mut binding: TokenIter, _|Map::east(5, move||binding.clone(), |_,_|"kyp")))) + let binding = |mut binding: TokenIter, _|Bsp::e( + Fixed::x(15, Align::w(Tui::bold(true, Tui::fg(Rgb(255,192,0), if let Some(Token { + value: Value::Sym(key), .. + }) = binding.next() { + Some(key.to_string()) + } else { + None + })))), + Bsp::e(" ", Tui::fg(Rgb(255,255,255), if let Some(Token { + value: Value::Key(command), .. + }) = binding.next() { + Some(command.to_string()) + } else { + None + })), + ); + Bsp::s(Tui::bold(true, "Help"), Bsp::s("", Map::south(1, bindings, binding))) } fn view_dialog_device (&self, index: usize) -> impl Content + use<'_> { @@ -117,18 +115,6 @@ impl App { Bsp::s(message, Bsp::s("", "[ OK ]")) } - fn view_dialog_save <'a> (&'a self) -> impl Content + use<'a> { - "WIP: SAVE" - } - - fn view_dialog_load <'a> (&'a self) -> impl Content + use<'a> { - "WIP: LOAD" - } - - fn view_dialog_options <'a> (&'a self) -> impl Content + use<'a> { - "WIP: OPTIONS" - } - /// Spacing between tracks. pub(crate) const TRACK_SPACING: usize = 0; @@ -148,6 +134,16 @@ impl App { }) } + pub(crate) fn outputs_with_sizes (&self) -> impl PortsSizes<'_> { + let mut y = 0; + self.midi_outs.iter().enumerate().map(move|(i, output)|{ + let height = 1 + output.conn().len(); + let data = (i, output.name(), output.conn(), y, y + height); + y += height; + data + }) + } + pub(crate) fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { use Selection::*; let mut x = 0; @@ -281,12 +277,20 @@ impl<'a> ArrangerView<'a> { )) } + /// Render output matrix. + pub(crate) fn outputs (&'a self) -> impl Content + 'a { + Tui::bg(Reset, Align::n(Bsp::s( + Bsp::s(self.output_ports(), self.output_conns()), + Bsp::s(self.output_nexts(), self.output_froms()), + ))) + } + /// Render device switches. pub(crate) fn devices (&'a self) -> impl Content + 'a { let Self { width_side, width_mid, track_count, track_selected, is_editing, .. } = self; Tryptich::top(1) - .left(*width_side, button_3("d", "devices", format!("{}", 0), *is_editing)) - .right(*width_side, button_2("D", "add device", *is_editing)) + .left(*width_side, button_3("z", "devices", format!("{}", 0), *is_editing)) + .right(*width_side, button_2("Z", "add device", *is_editing)) .middle(*width_mid, per_track_top(*width_mid, ||self.tracks_with_sizes_scrolled(), move|index, track|{ let bg = if *track_selected == Some(index) { @@ -369,6 +373,72 @@ impl<'a> ArrangerView<'a> { |_, _|Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ "))))) } + fn output_nexts (&'a self) -> impl Content + 'a { + Tryptich::top(2) + .left(self.width_side, Align::ne("From clip:")) + .middle(self.width_mid, per_track_top( + self.width_mid, + ||self.tracks_with_sizes_scrolled(), + |_, _|Tui::bg(Reset, Align::c(Bsp::s(" ------ ", OctaveVertical::default()))))) + } + + fn output_froms (&'a self) -> impl Content + 'a { + let label = Align::ne("Next clip:"); + Tryptich::top(2).left(self.width_side, label).middle(self.width_mid, per_track_top( + self.width_mid, ||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) + })) + } + + fn output_ports (&'a self) -> impl Content + 'a { + Tryptich::top(1) + .left(self.width_side, + button_3("o", "midi outs", format!("{}", self.outputs_count), self.is_editing)) + .right(self.width_side, + button_2("O", "add midi out", self.is_editing)) + .middle(self.width_mid, + per_track_top(self.width_mid, ||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.track_selected == 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 output_conns (&'a self) -> impl Content + 'a { + Tryptich::top(self.outputs_height) + .left(self.width_side, + io_ports(Tui::g(224), Tui::g(32), ||self.app.outputs_with_sizes())) + .middle(self.width_mid, + per_track_top(self.width_mid, ||self.tracks_with_sizes_scrolled(), |_, t|io_conns( + t.color.dark.rgb, + t.color.darker.rgb, + ||self.app.outputs_with_sizes() + ))) + } + /// Render scenes with clips pub(crate) fn scenes (&'a self) -> impl Content + 'a { @@ -527,6 +597,83 @@ def_sizes_iter!(OutputsSizes => JackMidiOut); def_sizes_iter!(PortsSizes => Arc, [PortConnect]); +fn view_transport ( + play: bool, + bpm: Arc>, + beat: Arc>, + time: Arc>, +) -> impl Content { + let theme = ItemTheme::G[96]; + Tui::bg(Black, row!(Bsp::a( + Fill::xy(Align::w(button_play_pause(play))), + Fill::xy(Align::e(row!( + FieldH(theme, "BPM", bpm), + FieldH(theme, "Beat", beat), + FieldH(theme, "Time", time), + ))) + ))) +} + +fn view_status ( + sel: Arc, + sr: Arc>, + buf: Arc>, + lat: Arc>, +) -> impl Content { + let theme = ItemTheme::G[96]; + Tui::bg(Black, row!(Bsp::a( + Fill::xy(Align::w(FieldH(theme, "Selected", sel))), + Fill::xy(Align::e(row!( + FieldH(theme, "SR", sr), + FieldH(theme, "Buf", buf), + FieldH(theme, "Lat", lat), + ))) + ))) +} + +pub(crate) fn button_play_pause (playing: bool) -> impl Content { + let compact = true;//self.is_editing(); + Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) }, + Either::new(compact, + 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(" ▗▄▖ ", " ▝▀▘ ",)))) + ) + ) + ) +} + +pub (crate) fn view_meter <'a> (label: &'a str, value: f32) -> impl Content + 'a { + col!( + FieldH(ItemTheme::G[128], label, format!("{:>+9.3}", value)), + Fixed::xy(if value >= 0.0 { 13 } + else if value >= -1.0 { 12 } + else if value >= -2.0 { 11 } + else if value >= -3.0 { 10 } + else if value >= -4.0 { 9 } + else if value >= -6.0 { 8 } + else if value >= -9.0 { 7 } + else if value >= -12.0 { 6 } + else if value >= -15.0 { 5 } + else if value >= -20.0 { 4 } + else if value >= -25.0 { 3 } + else if value >= -30.0 { 2 } + else if value >= -40.0 { 1 } + else { 0 }, 1, Tui::bg(if value >= 0.0 { Red } + else if value >= -3.0 { Yellow } + else { Green }, ()))) +} + +pub(crate) fn view_meters (values: &[f32;2]) -> impl Content + use<'_> { + let left = format!("L/{:>+9.3}", values[0]); + let right = format!("R/{:>+9.3}", values[1]); + Bsp::s(left, right) +} + pub(crate) fn wrap (bg: Color, fg: Color, content: impl Content) -> impl Content { let left = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("▐"))); let right = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("▌"))); diff --git a/crates/app/src/view/view_output.rs b/crates/app/src/view/view_output.rs deleted file mode 100644 index 938b90fb..00000000 --- a/crates/app/src/view/view_output.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::*; - -/// Outputs. -impl App { - pub(crate) fn outputs_with_sizes (&self) -> impl PortsSizes<'_> { - let mut y = 0; - self.midi_outs.iter().enumerate().map(move|(i, output)|{ - let height = 1 + output.conn().len(); - let data = (i, output.name(), output.conn(), y, y + height); - y += height; - data - }) - } -} -impl<'a> ArrangerView<'a> { - fn output_nexts (&self) -> impl Content + '_ { - Tryptich::top(2) - .left(self.width_side, Align::ne("From clip:")) - .middle(self.width_mid, per_track_top( - self.width_mid, - ||self.tracks_with_sizes_scrolled(), - |_, _|Tui::bg(Reset, Align::c(Bsp::s(" ------ ", OctaveVertical::default()))))) - } - /// Render output matrix. - pub(crate) fn outputs (&'a self) -> impl Content + 'a { - Tui::bg(Reset, Align::n(Bsp::s( - Bsp::s(self.output_ports(), self.output_conns()), - Bsp::s(self.output_nexts(), self.output_froms()), - ))) - } - fn output_froms (&'a self) -> impl Content + 'a { - let label = Align::ne("Next clip:"); - Tryptich::top(2).left(self.width_side, label).middle(self.width_mid, per_track_top( - self.width_mid, ||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) - })) - } - fn output_ports (&'a self) -> impl Content + 'a { - Tryptich::top(1) - .left(self.width_side, self.output_count()) - .right(self.width_side, self.output_add()) - .middle(self.width_mid, self.output_map()) - } - fn output_count (&'a self) -> impl Content + 'a { - button_3("o", "midi outs", format!("{}", self.outputs_count), self.is_editing) - } - fn output_add (&'a self) -> impl Content + 'a { - button_2("O", "add midi out", self.is_editing) - } - fn output_map (&'a self) -> impl Content + 'a { - per_track_top(self.width_mid, ||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.track_selected == 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 output_conns (&'a self) -> impl Content + 'a { - Tryptich::top(self.outputs_height) - .left(self.width_side, io_ports(Tui::g(224), Tui::g(32), ||self.app.outputs_with_sizes())) - .middle(self.width_mid, per_track_top( - self.width_mid, - ||self.tracks_with_sizes_scrolled(), - |_, t|io_conns( - t.color.dark.rgb, - t.color.darker.rgb, - ||self.app.outputs_with_sizes() - ))) - } -} diff --git a/crates/device/src/clock.rs b/crates/device/src/clock.rs index 31b19c44..8a16808d 100644 --- a/crates/device/src/clock.rs +++ b/crates/device/src/clock.rs @@ -1,6 +1,5 @@ mod clock_api; pub use self::clock_api::*; mod clock_model; pub use self::clock_model::*; -mod clock_view; pub use self::clock_view::*; pub trait HasClock: Send + Sync { fn clock (&self) -> &Clock; diff --git a/crates/device/src/clock/clock_view.rs b/crates/device/src/clock/clock_view.rs deleted file mode 100644 index ab2acc3e..00000000 --- a/crates/device/src/clock/clock_view.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::*; - -pub fn view_transport ( - play: bool, - bpm: Arc>, - beat: Arc>, - time: Arc>, -) -> impl Content { - let theme = ItemTheme::G[96]; - Tui::bg(Black, row!(Bsp::a( - Fill::xy(Align::w(button_play_pause(play))), - Fill::xy(Align::e(row!( - FieldH(theme, "BPM", bpm), - FieldH(theme, "Beat", beat), - FieldH(theme, "Time", time), - ))) - ))) -} - -pub fn view_status ( - sel: Arc, - sr: Arc>, - buf: Arc>, - lat: Arc>, -) -> impl Content { - let theme = ItemTheme::G[96]; - Tui::bg(Black, row!(Bsp::a( - Fill::xy(Align::w(FieldH(theme, "Selected", sel))), - Fill::xy(Align::e(row!( - FieldH(theme, "SR", sr), - FieldH(theme, "Buf", buf), - FieldH(theme, "Lat", lat), - ))) - ))) -} - -pub(crate) fn button_play_pause (playing: bool) -> impl Content { - let compact = true;//self.is_editing(); - Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) }, - Either::new(compact, - 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(" ▗▄▖ ", " ▝▀▘ ",)))) - ) - ) - ) -} diff --git a/crates/device/src/meter.rs b/crates/device/src/meter.rs index 13e63c5a..0c2e749a 100644 --- a/crates/device/src/meter.rs +++ b/crates/device/src/meter.rs @@ -52,30 +52,3 @@ pub fn to_rms (samples: &[f32]) -> f32 { .unwrap_or(0.0); (sum / samples.len() as f32).sqrt() } - -pub (crate) fn view_meter <'a> (label: &'a str, value: f32) -> impl Content + 'a { - col!( - FieldH(ItemTheme::G[128], label, format!("{:>+9.3}", value)), - Fixed::xy(if value >= 0.0 { 13 } - else if value >= -1.0 { 12 } - else if value >= -2.0 { 11 } - else if value >= -3.0 { 10 } - else if value >= -4.0 { 9 } - else if value >= -6.0 { 8 } - else if value >= -9.0 { 7 } - else if value >= -12.0 { 6 } - else if value >= -15.0 { 5 } - else if value >= -20.0 { 4 } - else if value >= -25.0 { 3 } - else if value >= -30.0 { 2 } - else if value >= -40.0 { 1 } - else { 0 }, 1, Tui::bg(if value >= 0.0 { Red } - else if value >= -3.0 { Yellow } - else { Green }, ()))) -} - -pub(crate) fn view_meters (values: &[f32;2]) -> impl Content + use<'_> { - let left = format!("L/{:>+9.3}", values[0]); - let right = format!("R/{:>+9.3}", values[1]); - Bsp::s(left, right) -}