diff --git a/config/config_arranger.edn b/config/config_arranger.edn index 51932ec3..07f0d65a 100644 --- a/config/config_arranger.edn +++ b/config/config_arranger.edn @@ -2,14 +2,6 @@ (info "A session grid.") -(view - (bsp/a :view-dialog - (bsp/s (fixed/y 1 :view-transport) - (bsp/n (fixed/y 1 :view-status) - (fill/xy (bsp/a - (fill/xy (align/e :view-pool)) - :view-arranger)))))) - (keys (layer-if :focus-message "./keys_message.edn") (layer-if :focus-device-add "./keys_device_add.edn") @@ -24,3 +16,17 @@ (layer "./keys_clock.edn") (layer "./keys_arranger.edn") (layer "./keys_global.edn")) + +(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-ports-status :view-editor-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))))))) + (bsp/w + (fixed/x 20 :view-pool) + (bsp/e + (fixed/x 20 (fill/xy (align/n :view-arranger-scene-names))) + (fill/xy (align/n :view-arranger-scene-clips))))))) diff --git a/config/config_groovebox.edn b/config/config_groovebox.edn index c313319f..3afc185b 100644 --- a/config/config_groovebox.edn +++ b/config/config_groovebox.edn @@ -2,18 +2,6 @@ (info "A sequencer with built-in sampler.") -(view - (bsp/a :view-dialog - (bsp/s (fixed/y 1 :view-transport) - (bsp/n (fixed/y 1 :view-status) - (bsp/w :view-meters-output - (bsp/e :view-meters-input - (bsp/n :view-sample-info - (bsp/n (fixed/y 5 :view-sample-viewer) - (bsp/w :view-pool - (bsp/e :view-samples-keys - (fill/y :view-editor))))))))))) - (keys (layer-if :focus-browser "./keys_browser.edn") (layer-if :focus-pool-rename "./keys_rename.edn") @@ -22,3 +10,11 @@ (layer "./keys_editor.edn") (layer "./keys_sampler.edn") (layer "./keys_global.edn")) + +(view (bsp/a :view-dialog (bsp/w :view-meters-output (bsp/e :view-meters-input + (bsp/n (fixed/y 6 (bsp/e :view-sample-status :view-sample-viewer)) + (bsp/e + (fill/y (align/n (bsp/s :view-status-v (bsp/s + (bsp/s :view-midi-ports-status :view-audio-ports-status) + (bsp/n :view-editor-status :view-pool))))) + (bsp/e :view-samples-keys :view-editor))))))) diff --git a/config/config_sampler.edn b/config/config_sampler.edn index c2486385..2739e312 100644 --- a/config/config_sampler.edn +++ b/config/config_sampler.edn @@ -2,12 +2,12 @@ (info "A sampling soundboard.") +(keys + (layer "./keys_sampler.edn") + (layer "./keys_global.edn")) + (view (bsp/a :view-dialog (bsp/s (fixed/y 1 :view-transport) (bsp/n (fixed/y 1 :view-status) (fill/xy :view-samples-grid))))) - -(keys - (layer "./keys_sampler.edn") - (layer "./keys_global.edn")) diff --git a/config/config_sequencer.edn b/config/config_sequencer.edn index a267cebd..868bc0ba 100644 --- a/config/config_sequencer.edn +++ b/config/config_sequencer.edn @@ -2,14 +2,6 @@ (info "A MIDI sequencer.") -(view - (bsp/a :view-dialog - (bsp/s (fixed/y 1 :view-transport) - (bsp/n (fixed/y 1 :view-status) - (fill/xy (bsp/a - (fill/xy (align/e :view-pool)) - :view-editor))))) - (keys (layer-if :focus-browser "./keys_browser.edn") (layer-if :mode-pool-rename "./keys_rename.edn") @@ -17,3 +9,11 @@ (layer "./keys_editor.edn") (layer "./keys_clock.edn") (layer "./keys_global.edn")) + +(view + (bsp/a :view-dialog + (bsp/s (fixed/y 1 :view-transport) + (bsp/n (fixed/y 1 :view-status) + (fill/xy (bsp/a + (fill/xy (align/e :view-pool)) + :view-editor))))) diff --git a/config/config_transport.edn b/config/config_transport.edn index 47324e7b..23ca4f6e 100644 --- a/config/config_transport.edn +++ b/config/config_transport.edn @@ -2,8 +2,8 @@ (info "A JACK transport controller.") -(view :view-transport) - (keys (layer "./keys_clock.edn") (layer "./keys_global.edn")) + +(view :view-transport) diff --git a/config/keys_global.edn b/config/keys_global.edn index 8e9f4233..6ea6d748 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/config/keys_sampler.edn b/config/keys_sampler.edn index 0e8aa127..bb4b5b57 100644 --- a/config/keys_sampler.edn +++ b/config/keys_sampler.edn @@ -2,7 +2,11 @@ (@down sampler select :sample-below) (@left sampler select :sample-to-left) (@right sampler select :sample-to-right) + (@r sampler record-toggle :sample-selected) (@shift-R sampler record-cancel) (@p sampler play-sample :sample-selected) (@P sampler stop-sample :sample-selected) + +(@shift-f6 dialog :dialog-import-sample) +(@shift-f9 dialog :dialog-export-sample) diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index 5a459295..4948c2c1 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -67,7 +67,7 @@ impl App { } pub fn browser (&self) -> Option<&Browser> { self.dialog.as_ref().and_then(|dialog|match dialog { - Dialog::Save(b) | Dialog::Load(b) => Some(b), + Dialog::Browser(_, b) => Some(b), _ => None }) } @@ -156,11 +156,20 @@ pub enum Dialog { Menu(usize), Device(usize), Message(Message), - Save(Browser), - Load(Browser), + 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 { @@ -235,10 +244,22 @@ impl App { Some(Dialog::Menu(0)) } fn dialog_save (&self) -> Option { - Some(Dialog::Save(Default::default())) + Some(Dialog::Browser(BrowserTarget::SaveProject, Browser::new(None).unwrap())) } fn dialog_load (&self) -> Option { - Some(Dialog::Load(Default::default())) + Some(Dialog::Browser(BrowserTarget::LoadProject, Browser::new(None).unwrap())) + } + fn dialog_import_clip (&self) -> Option { + Some(Dialog::Browser(BrowserTarget::ImportClip(Default::default()), Browser::new(None).unwrap())) + } + fn dialog_export_clip (&self) -> Option { + Some(Dialog::Browser(BrowserTarget::ExportClip(Default::default()), Browser::new(None).unwrap())) + } + fn dialog_import_sample (&self) -> Option { + Some(Dialog::Browser(BrowserTarget::ImportSample(Default::default()), Browser::new(None).unwrap())) + } + fn dialog_export_sample (&self) -> Option { + Some(Dialog::Browser(BrowserTarget::ExportSample(Default::default()), Browser::new(None).unwrap())) } fn dialog_options (&self) -> Option { Some(Dialog::Options) diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 3ffbd439..2d347a3b 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -14,6 +14,36 @@ impl App { pub fn view_nil (&self) -> impl Content + use<'_> { "nil" } + 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, col!( + Fill::x(Align::w(Bsp::e( + Align::w(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(" ▗▄▖ ", " ▝▀▘ ",)))) + ) + ) + )), + Bsp::s( + FieldH(theme, "Beat", cache.beat.view.clone()), + FieldH(theme, "Time", cache.time.view.clone()), + ), + ))), + 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()))), + ))) + } pub fn view_status (&self) -> impl Content + use<'_> { self.update_clock(); let cache = self.view_cache.read().unwrap(); @@ -27,16 +57,172 @@ impl App { cache.bpm.view.clone(), cache.beat.view.clone(), cache.time.view.clone()) } pub fn view_editor (&self) -> impl Content + use<'_> { - self.editor().map(|e|Bsp::n(Bsp::e(e.clip_status(), e.edit_status()), e)) + self.editor() + } + pub fn view_editor_status (&self) -> impl Content + use<'_> { + self.editor().map(|e|Bsp::s(e.clip_status(), e.edit_status())) + } + pub fn view_midi_ports_status (&self) -> impl Content + use<'_> { + self.project.get_track().map(|track|Bsp::s( + Fixed::xy(20, 1 + track.sequencer.midi_ins.len() as u16, FieldV(self.color, + format!("MIDI ins: "), + Map::south(1, ||track.sequencer.midi_ins.iter(), + |port, index|Fill::x(Align::w(format!(" {index} {}", port.name())))))), + Fixed::xy(20, 1 + track.sequencer.midi_outs.len() as u16, FieldV( + self.color, + format!("MIDI outs: "), + Map::south(1, ||track.sequencer.midi_outs.iter(), + |port, index|Fill::x(Align::w(format!(" {index} {}", port.name())))))) + )) + } + pub fn view_audio_ports_status (&self) -> impl Content + use<'_> { + self.project.get_track().map(|track|Bsp::s( + track.devices.get(0).map(|device| + Fixed::xy(20, 1 + device.audio_ins().len() as u16, FieldV(self.color, + format!("Audio ins: "), + Map::south(1, ||device.audio_ins().iter(), + |port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))), + track.devices.last().map(|device| + Fixed::xy(20, 1 + device.audio_outs().len() as u16, FieldV(self.color, + format!("Audio outs:"), + Map::south(1, ||device.audio_outs().iter(), + |port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))), + )) } pub fn view_arranger (&self) -> impl Content + use<'_> { ArrangerView::new(&self.project, self.editor.as_ref()) } + pub fn view_arranger_scene_names <'a> (&'a self) -> impl Content + use<'a> { + let h = self.project.scenes.len() as u16 * 2; + let bg = self.color.darker.rgb; + Fixed::y(h, Tui::bg(bg, Align::w(Fill::x(Map::new( + ||self.project.scenes.iter().skip(self.project.scene_scroll), + move|scene: &Scene, index| + Push::y(index as u16 * 2u16, Fixed::xy(20, 2, + Tui::bg(scene.color.dark.rgb, Align::nw(Bsp::e( + format!(" {index:2} "), + Tui::fg(Rgb(255, 255, 255), + Tui::bold(true, format!("{}", scene.name))))))))))))) + } + pub fn view_arranger_scene_clips <'a> (&'a self) -> impl Content + use<'a> { + let h = self.project.scenes.len() as u16 * 2; + let bg = self.color.darker.rgb; + Fixed::y(h, Tui::bg(bg, Align::w(Fill::x(Map::new( + ||self.project.scenes.iter().skip(self.project.scene_scroll), + move|scene: &'a Scene, index1| + Push::y(index1 as u16 * 2u16, Fixed::xy(20, 2, + Map::new( + move||scene.clips.iter().skip(self.project.track_scroll), + move|clip: &'a Option>>, index2|{ + 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(index2 as u16 * 14, Tui::bg(theme.dark.rgb, Bsp::e( + format!(" {index1:2} {index2:2} "), + Tui::fg(Rgb(255, 255, 255), + Tui::bold(true, format!("{}", text)))))) + })))))))) + } + pub fn view_arranger_track_names (&self) -> impl Content + use<'_> { + let mut max_outputs = 0u16; + for track in self.project.tracks.iter() { + max_outputs = max_outputs.max(track.sequencer.midi_outs.len() as u16); + } + Bsp::w( + Fixed::x(20, Tui::bg(self.color.darkest.rgb, + col!(Tui::bold(true, "[t]rack"), "[T] Add"))), + Align::w(Fixed::y(max_outputs + 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), + move|(index, track, x1, x2): (usize, &Track, usize, usize), _| + Push::x(index as u16 * 14, Fixed::xy(track.width as u16, max_outputs + 1, + Tui::bg(track.color.dark.rgb, Align::nw(Bsp::s( + Tui::fg(Rgb(255, 255, 255), Tui::bold(true, format!("{}", track.name))), + format!("{index} {x1} {x2}"))))))))))))) + } + pub fn view_arranger_track_outputs <'a> (&'a self) -> impl Content + use<'a> { + let mut max_outputs = 0u16; + for track in self.project.tracks.iter() { + max_outputs = max_outputs.max(track.sequencer.midi_outs.len() as u16); + } + Bsp::w( + Fixed::x(20, Tui::bg(self.color.darkest.rgb, + col!(Tui::bold(true, "[o]utput"), "[O] Add"))), + Align::w(Fixed::y(max_outputs + 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), + move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _| + Push::x(x2 as u16, Tui::bg(track.color.dark.rgb, Fixed::xy( + track.width as u16, + max_outputs + 1, + Align::nw(Bsp::s( + format!("[mut] [sol]"), + Map::south(1, ||track.sequencer.midi_outs.iter(), + |port, index|Tui::fg(Rgb(255, 255, 255), + format!("{index}: {}", port.name()))))))))))))))) + } + pub fn view_arranger_track_inputs <'a> (&'a self) -> impl Content + use<'a> { + let mut max_inputs = 0u16; + for track in self.project.tracks.iter() { + max_inputs = max_inputs.max(track.sequencer.midi_ins.len() as u16); + } + Bsp::w( + Fixed::x(20, Tui::bg(self.color.darkest.rgb, + col!(Tui::bold(true, "[i]nputs"), "[I] Add"))), + Fill::x(Align::w(Fixed::y(max_inputs + 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), + move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _| + Push::x(x2 as u16, Fixed::xy(track.width as u16, max_inputs + 1, + Tui::bg(track.color.dark.rgb, Align::nw(Bsp::s( + format!("[rec] [mon]"), + Map::south(1, ||track.sequencer.midi_ins.iter(), + |port, index|Tui::fg(Rgb(255, 255, 255), + format!("{index}: {}", port.name())))))))))))))))) + } + pub fn view_arranger_track_devices <'a> (&'a self) -> impl Content + use<'a> { + let mut max_devices = 2u16; + 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, "[d]evice"), "[D] Add"))), + Fixed::y(max_devices, 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), + move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _| + Push::x(x2 as u16, Fixed::xy(track.width as u16, max_devices + 1, + Tui::bg(track.color.dark.rgb, Align::nw(Map::south(1, move||0..max_devices, + |_, index|format!("{index}: {}", "--------")))))))))))) + } + 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(&self.project.selection, None) + .skip(self.project.track_scroll), + 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_pool (&self) -> impl Content + use<'_> { - PoolView(&self.project.pool) + Fixed::x(20, Bsp::s( + Fill::x(Align::w(FieldH(self.color, "Clip pool:", ""))), + Fill::y(Align::n(Tui::bg(Rgb(0, 0, 0), PoolView(&self.project.pool)))), + )) } pub fn view_samples_keys (&self) -> impl Content + use<'_> { - self.project.sampler().map(|s|s.view_list(false, self.editor().unwrap())) + self.project.sampler().map(|s|s.view_list(true, self.editor().unwrap())) } pub fn view_samples_grid (&self) -> impl Content + use<'_> { self.project.sampler().map(|s|s.view_grid()) @@ -47,6 +233,9 @@ impl App { pub fn view_sample_info (&self) -> impl Content + use<'_> { self.project.sampler().map(|s|s.view_sample_info(self.editor().unwrap().get_note_pos())) } + pub fn view_sample_status (&self) -> impl Content + use<'_> { + self.project.sampler().map(|s|s.view_sample_status(self.editor().unwrap().get_note_pos())) + } pub fn view_meters_input (&self) -> impl Content + use<'_> { self.project.sampler().map(|s|s.view_meters_input()) } @@ -62,10 +251,8 @@ impl App { self.view_dialog_menu().boxed(), Dialog::Help(offset) => self.view_dialog_help(*offset).boxed(), - Dialog::Save(browser) => - self.view_dialog_save().boxed(), - Dialog::Load(browser) => - self.view_dialog_load().boxed(), + Dialog::Browser(target, browser) => + self.view_dialog_browser(target, browser).boxed(), Dialog::Options => self.view_dialog_options().boxed(), Dialog::Device(index) => @@ -108,21 +295,45 @@ impl App { 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_save <'a> (&'a self) -> impl Content + use<'a> { + pub fn view_dialog_browser <'a> (&'a self, target: &BrowserTarget, browser: &'a Browser) -> impl Content + use<'a> { Bsp::s( - Fill::x(Align::w(Margin::xy(1, 1, Bsp::e( - Tui::bold(true, " Save project: "), - Shrink::x(3, Fixed::y(1, RepeatH("🭻"))))))), + 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("todo file browser"))) + .enclose(Fill::xy(browser))) } - pub fn view_dialog_load <'a> (&'a self) -> impl Content + use<'a> { + 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("todo file browser"))) + .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/cli/tek.rs b/crates/cli/tek.rs index 1d407373..97a7f499 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -60,7 +60,7 @@ pub enum LaunchMode { /// Number of tracks #[arg(short = 'x', long, default_value_t = 4)] tracks: usize, /// Width of tracks - #[arg(short = 'w', long, default_value_t = 12)] track_width: usize, + #[arg(short = 'w', long, default_value_t = 14)] track_width: usize, }, /// TODO: A MIDI-controlled audio mixer Mixer, diff --git a/crates/device/src/arranger.rs b/crates/device/src/arranger.rs index 2939b23e..38f5b313 100644 --- a/crates/device/src/arranger.rs +++ b/crates/device/src/arranger.rs @@ -3,7 +3,7 @@ use crate::*; /// Define a type alias for iterators of sized items (columns). macro_rules! def_sizes_iter { ($Type:ident => $($Item:ty),+) => { - pub(crate) trait $Type<'a> = + pub trait $Type<'a> = Iterator + Send + Sync + 'a; } } diff --git a/crates/device/src/arranger/arranger_model.rs b/crates/device/src/arranger/arranger_model.rs index 46687ab1..a34134d7 100644 --- a/crates/device/src/arranger/arranger_model.rs +++ b/crates/device/src/arranger/arranger_model.rs @@ -64,27 +64,12 @@ impl Arrangement { } /// Width available to display tracks. pub fn w_tracks_area (&self, is_editing: bool) -> u16 { - self.w().saturating_sub(2 * self.w_sidebar(is_editing)) + self.w().saturating_sub(self.w_sidebar(is_editing)) } /// Height of display pub fn h (&self) -> u16 { self.size.h() as u16 } - /// Height available to display track headers. - pub fn h_tracks_area (&self) -> u16 { - 5 // FIXME - //self.h().saturating_sub(self.h_inputs() + self.h_outputs()) - } - /// Height available to display tracks. - pub fn h_scenes_area (&self) -> u16 { - //15 - self.h().saturating_sub( - self.h_inputs() + - self.h_outputs() + - self.h_devices() + - 13 // FIXME - ) - } /// Height taken by all scenes. pub fn h_scenes (&self, is_editing: bool) -> u16 { self.scenes_with_sizes( @@ -111,27 +96,27 @@ impl Arrangement { //1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) } /// Get the active track - fn get_track (&self) -> Option<&Track> { + pub fn get_track (&self) -> Option<&Track> { let index = self.selection().track()?; Has::>::get(self).get(index) } /// Get a mutable reference to the active track - fn get_track_mut (&mut self) -> Option<&mut Track> { + pub fn get_track_mut (&mut self) -> Option<&mut Track> { let index = self.selection().track()?; Has::>::get_mut(self).get_mut(index) } /// Get the active scene - fn get_scene (&self) -> Option<&Scene> { + pub fn get_scene (&self) -> Option<&Scene> { let index = self.selection().scene()?; Has::>::get(self).get(index) } /// Get a mutable reference to the active scene - fn get_scene_mut (&mut self) -> Option<&mut Scene> { + pub fn get_scene_mut (&mut self) -> Option<&mut Scene> { let index = self.selection().scene()?; Has::>::get_mut(self).get_mut(index) } /// Get the active clip - fn get_clip (&self) -> Option>> { + pub fn get_clip (&self) -> Option>> { self.get_scene()?.clips.get(self.selection().track()?)?.clone() } /// Put a clip in a slot diff --git a/crates/device/src/arranger/arranger_port.rs b/crates/device/src/arranger/arranger_port.rs index 152edc73..a5a1a586 100644 --- a/crates/device/src/arranger/arranger_port.rs +++ b/crates/device/src/arranger/arranger_port.rs @@ -3,7 +3,7 @@ use crate::*; impl<'a> ArrangerView<'a> { pub(crate) fn input_routes (&'a self) -> impl Content + 'a { - Tryptich::top(self.inputs_height) + Tryptich::top(self.arrangement.h_inputs()) .left(self.width_side, io_ports(Tui::g(224), Tui::g(32), ||self.arrangement.midi_ins_with_sizes())) .middle(self.width_mid, @@ -127,7 +127,7 @@ impl<'a> ArrangerView<'a> { } pub(crate) fn output_conns (&'a self) -> impl Content + 'a { - Tryptich::top(self.outputs_height) + Tryptich::top(self.arrangement.h_outputs()) .left(self.width_side, io_ports( Tui::g(224), Tui::g(32), ||self.arrangement.midi_outs_with_sizes())) .middle(self.width_mid, per_track_top( diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index a903d26f..cbd3a4b1 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -2,26 +2,19 @@ use crate::*; pub struct ArrangerView<'a> { pub arrangement: &'a Arrangement, - pub is_editing: bool, - pub width: u16, pub width_mid: u16, pub width_side: u16, - - pub inputs_height: u16, - pub outputs_height: u16, - pub scene_last: usize, pub scene_scroll: Fill>, pub scene_selected: Option, + /// Height available to display scene/track content. pub scenes_height: u16, - pub track_scroll: Fill>, pub track_selected: Option, + /// Height available to display track headers. pub tracks_height: u16, - - pub show_debug_info: bool, } impl<'a> ArrangerView<'a> { @@ -30,20 +23,15 @@ impl<'a> ArrangerView<'a> { editor: Option<&'a MidiEditor> ) -> Self { let is_editing = editor.is_some(); - let h_tracks_area = arrangement.h_tracks_area(); - let h_scenes_area = arrangement.h_scenes_area(); + let h_tracks_area = 5; + let h_scenes_area = (arrangement.height() as u16).saturating_sub(20); let h_scenes = arrangement.h_scenes(is_editing); Self { arrangement, is_editing, - - width: arrangement.w(), - width_mid: arrangement.w_tracks_area(is_editing), - width_side: arrangement.w_sidebar(is_editing), - - inputs_height: arrangement.h_inputs(), - outputs_height: arrangement.h_outputs(), - + width: arrangement.w_tracks_area(is_editing), + width_mid: arrangement.w_tracks_area(is_editing).saturating_sub(20), + width_side: 20, scenes_height: h_scenes_area, scene_selected: arrangement.selection().scene(), scene_last: arrangement.scenes.len().saturating_sub(1), @@ -52,7 +40,6 @@ impl<'a> ArrangerView<'a> { length: h_scenes_area as usize, total: h_scenes as usize, })), - tracks_height: h_tracks_area, track_selected: arrangement.selection().track(), track_scroll: Fill::x(Fixed::y(1, ScrollbarH { @@ -60,8 +47,6 @@ impl<'a> ArrangerView<'a> { length: h_tracks_area as usize, total: h_scenes as usize, })), - - show_debug_info: false } } } @@ -75,7 +60,7 @@ impl<'a> Content for ArrangerView<'a> { 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); - outs(tracks(devices(ins(bg(self.scenes(&None)))))) + self.arrangement.size.of(outs(tracks(devices(ins(bg(self.scenes(&None))))))) } } @@ -148,7 +133,6 @@ impl<'a> ArrangerView<'a> { scenes_height, scene_last, scene_selected, track_selected, is_editing, .. } = self; - let selection = Has::::get(self.arrangement); let selected_track = selection.track(); let selected_scene = selection.scene(); diff --git a/crates/device/src/browser/browser_view.rs b/crates/device/src/browser/browser_view.rs index ae02f3c3..45f42863 100644 --- a/crates/device/src/browser/browser_view.rs +++ b/crates/device/src/browser/browser_view.rs @@ -1,19 +1,33 @@ use crate::*; -content!(TuiOut: |self: Browser| /*Stack::down(|add|{ - let mut i = 0; - for (_, name) in self.dirs.iter() { - if i >= self.scroll { - add(&Tui::bold(i == self.index, name.as_str()))?; +content!(TuiOut: |self: Browser|Map::south(1, ||EntriesIterator { + offset: 0, + index: 0, + length: self.dirs.len() + self.files.len(), + browser: self, +}, |entry, index|Fill::x(Align::w(entry)))); + +struct EntriesIterator<'a> { + browser: &'a Browser, + offset: usize, + length: usize, + index: usize, +} + +impl<'a> Iterator for EntriesIterator<'a> { + type Item = Modify<&'a str>; + fn next (&mut self) -> Option { + let dirs = self.browser.dirs.len(); + let files = self.browser.files.len(); + let index = self.index; + if self.index < dirs { + self.index += 1; + Some(Tui::bold(true, self.browser.dirs[index].1.as_str())) + } else if self.index < dirs + files { + self.index += 1; + Some(Tui::bold(false, self.browser.files[index - dirs].1.as_str())) + } else { + None } - i += 1; } - for (_, name) in self.files.iter() { - if i >= self.scroll { - add(&Tui::bold(i == self.index, name.as_str()))?; - } - i += 1; - } - add(&format!("{}/{i}", self.index))?; - Ok(()) -})*/"todo"); +} diff --git a/crates/device/src/device.rs b/crates/device/src/device.rs index 91e45fa0..0512e686 100644 --- a/crates/device/src/device.rs +++ b/crates/device/src/device.rs @@ -37,6 +37,30 @@ impl Device { _ => todo!(), } } + pub fn midi_ins (&self) -> &[JackMidiIn] { + match self { + //Self::Sampler(Sampler { midi_in, .. }) => &[midi_in], + _ => todo!() + } + } + pub fn midi_outs (&self) -> &[JackMidiOut] { + match self { + Self::Sampler(_) => &[], + _ => todo!() + } + } + pub fn audio_ins (&self) -> &[JackAudioIn] { + match self { + Self::Sampler(Sampler { audio_ins, .. }) => audio_ins.as_slice(), + _ => todo!() + } + } + pub fn audio_outs (&self) -> &[JackAudioOut] { + match self { + Self::Sampler(Sampler { audio_outs, .. }) => audio_outs.as_slice(), + _ => todo!() + } + } } pub struct DeviceAudio<'a>(pub &'a mut Device); diff --git a/crates/device/src/editor/editor_api.rs b/crates/device/src/editor/editor_api.rs index 3ef47b0b..8a588208 100644 --- a/crates/device/src/editor/editor_api.rs +++ b/crates/device/src/editor/editor_api.rs @@ -95,34 +95,36 @@ use crate::*; #[tengri_proc::command(MidiEditor)] impl MidiEditCommand { fn append_note (editor: &mut MidiEditor, advance: bool) -> Perhaps { editor.put_note(advance); + editor.redraw(); Ok(None) } - fn delete_note (_editor: &mut MidiEditor) -> Perhaps { + fn delete_note (editor: &mut MidiEditor) -> Perhaps { + editor.redraw(); todo!() } fn set_note_pos (editor: &mut MidiEditor, pos: usize) -> Perhaps { editor.set_note_pos(pos.min(127)); + editor.redraw(); Ok(None) } fn set_note_len (editor: &mut MidiEditor, value: usize) -> Perhaps { - //let note_len = editor.get_note_len(); - //let time_zoom = editor.get_time_zoom(); editor.set_note_len(value); - //if note_len / time_zoom != x / time_zoom { - editor.redraw(); - //} + editor.redraw(); Ok(None) } fn set_note_scroll (editor: &mut MidiEditor, value: usize) -> Perhaps { editor.set_note_lo(value.min(127)); + editor.redraw(); Ok(None) } fn set_time_pos (editor: &mut MidiEditor, value: usize) -> Perhaps { editor.set_time_pos(value); + editor.redraw(); Ok(None) } fn set_time_scroll (editor: &mut MidiEditor, value: usize) -> Perhaps { editor.set_time_start(value); + editor.redraw(); Ok(None) } fn set_time_zoom (editor: &mut MidiEditor, value: usize) -> Perhaps { @@ -132,10 +134,12 @@ use crate::*; } fn set_time_lock (editor: &mut MidiEditor, value: bool) -> Perhaps { editor.set_time_lock(value); + editor.redraw(); Ok(None) } fn show (editor: &mut MidiEditor, clip: Option>>) -> Perhaps { editor.set_clip(clip.as_ref()); + editor.redraw(); Ok(None) } // TODO: 1-9 seek markers that by default start every 8th of the clip diff --git a/crates/device/src/editor/editor_view.rs b/crates/device/src/editor/editor_view.rs index c91de260..454290a9 100644 --- a/crates/device/src/editor/editor_view.rs +++ b/crates/device/src/editor/editor_view.rs @@ -12,10 +12,11 @@ impl MidiEditor { let (color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) { (clip.color, clip.name.clone(), clip.length, clip.looped) } else { (ItemTheme::G[64], String::new().into(), 0, false) }; - Bsp::e( - FieldH(color, "Edit", format!("{name} ({length})")), - FieldH(color, "Loop", looped.to_string()) - ) + 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()))), + )) } pub fn edit_status (&self) -> impl Content + '_ { @@ -29,10 +30,11 @@ impl MidiEditor { let note_name = format!("{:4}", Note::pitch_to_name(note_pos)); let note_pos = format!("{:>3}", note_pos); let note_len = format!("{:>4}", self.get_note_len()); - Bsp::e( - FieldH(color, "Time", format!("{length}/{time_zoom}+{time_pos} {time_lock}")), - FieldH(color, "Note", format!("{note_name} {note_pos} {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}")))), + )) } } diff --git a/crates/device/src/editor/editor_view_h.rs b/crates/device/src/editor/editor_view_h.rs index 35f430bd..1223d57e 100644 --- a/crates/device/src/editor/editor_view_h.rs +++ b/crates/device/src/editor/editor_view_h.rs @@ -64,11 +64,22 @@ impl PianoHorizontal { /// Draw the piano roll background. /// /// This mode uses full blocks on note on and half blocks on legato: █▄ █▄ █▄ - fn draw_bg (buf: &mut BigBuffer, clip: &MidiClip, zoom: usize, note_len: usize) { + fn draw_bg ( + buf: &mut BigBuffer, + clip: &MidiClip, + zoom: usize, + note_len: usize, + note_point: usize, + time_point: usize, + ) { for (y, note) in (0..=127).rev().enumerate() { for (x, time) in (0..buf.width).map(|x|(x, x*zoom)) { let cell = buf.get_mut(x, y).unwrap(); - cell.set_bg(clip.color.darkest.rgb); + if note == (127-note_point) || time == time_point { + cell.set_bg(Rgb(0,0,0)); + } else { + cell.set_bg(clip.color.darkest.rgb); + } if time % 384 == 0 { cell.set_fg(clip.color.darker.rgb); cell.set_char('│'); @@ -267,10 +278,10 @@ impl MidiViewer for PianoHorizontal { let clip = clip.read().unwrap(); let buf_size = self.buffer_size(&clip); let mut buffer = BigBuffer::from(buf_size); - let note_len = self.get_note_len(); let time_zoom = self.get_time_zoom(); self.time_len().set(clip.length); - PianoHorizontal::draw_bg(&mut buffer, &clip, time_zoom, note_len); + PianoHorizontal::draw_bg(&mut buffer, &clip, time_zoom, + self.get_note_len(), self.get_note_pos(), self.get_time_pos()); PianoHorizontal::draw_fg(&mut buffer, &clip, time_zoom); buffer } else { diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index 5079ccd0..a9f2686e 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -1,6 +1,7 @@ #![feature(let_chains)] #![feature(trait_alias)] #![feature(if_let_guard)] +#![feature(impl_trait_in_assoc_type)] pub(crate) use std::cmp::Ord; pub(crate) use std::fmt::{Debug, Formatter}; diff --git a/crates/device/src/meter.rs b/crates/device/src/meter.rs index 13e63c5a..106975c7 100644 --- a/crates/device/src/meter.rs +++ b/crates/device/src/meter.rs @@ -53,7 +53,7 @@ pub fn to_rms (samples: &[f32]) -> f32 { (sum / samples.len() as f32).sqrt() } -pub (crate) fn view_meter <'a> (label: &'a str, value: f32) -> impl Content + 'a { +pub 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 } @@ -74,7 +74,7 @@ pub (crate) fn view_meter <'a> (label: &'a str, value: f32) -> impl Content impl Content + use<'_> { +pub 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) diff --git a/crates/device/src/pool/pool_view.rs b/crates/device/src/pool/pool_view.rs index 29e4aa01..39d868aa 100644 --- a/crates/device/src/pool/pool_view.rs +++ b/crates/device/src/pool/pool_view.rs @@ -8,7 +8,7 @@ content!(TuiOut: |self: PoolView<'a>| { //let on_bg = |x|x;//Bsp::b(Repeat(" "), Tui::bg(color.darkest.rgb, x)); //let border = |x|x;//Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)).enclose(x); let height = pool.clips.read().unwrap().len() as u16; - Fixed::x(20, Fill::y(Align::c(Map::new( + Fixed::x(20, Fill::y(Align::n(Map::new( ||pool.clips().clone().into_iter(), move|clip: Arc>, i: usize|{ let item_height = 1; diff --git a/crates/device/src/sampler/sampler_midi.rs b/crates/device/src/sampler/sampler_midi.rs index 7a875e2e..f8f3f3d8 100644 --- a/crates/device/src/sampler/sampler_midi.rs +++ b/crates/device/src/sampler/sampler_midi.rs @@ -7,20 +7,18 @@ impl Sampler { /// Create [Voice]s from [Sample]s in response to MIDI input. pub fn process_midi_in (&mut self, scope: &ProcessScope) { let Sampler { midi_in, mapped, voices, .. } = self; - if let Some(ref midi_in) = midi_in { - for RawMidi { time, bytes } in midi_in.port().iter(scope) { - if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { - match message { - MidiMessage::NoteOn { ref key, ref vel } => { - if let Some(ref sample) = mapped[key.as_int() as usize] { - voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); - } - }, - MidiMessage::Controller { controller: _, value: _ } => { - // TODO + for RawMidi { time, bytes } in midi_in.port().iter(scope) { + if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { + match message { + MidiMessage::NoteOn { ref key, ref vel } => { + if let Some(ref sample) = mapped[key.as_int() as usize] { + voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); } - _ => {} + }, + MidiMessage::Controller { controller: _, value: _ } => { + // TODO } + _ => {} } } } diff --git a/crates/device/src/sampler/sampler_model.rs b/crates/device/src/sampler/sampler_model.rs index bf5c624d..fcd5d0ac 100644 --- a/crates/device/src/sampler/sampler_model.rs +++ b/crates/device/src/sampler/sampler_model.rs @@ -22,7 +22,7 @@ pub struct Sampler { /// Sample currently being edited. pub editing: Option>>, /// MIDI input port. Triggers sample playback. - pub midi_in: Option, + pub midi_in: JackMidiIn, /// Collection of currently playing instances of samples. pub voices: Arc>>, /// Audio output ports. Voices get played here. @@ -47,15 +47,28 @@ pub struct Sampler { pub cursor: (AtomicUsize, AtomicUsize), } -impl Default for Sampler { - fn default () -> Self { - Self { - midi_in: None, - audio_ins: vec![], +impl Sampler { + pub fn new ( + jack: &Jack, + name: impl AsRef, + midi_from: &[PortConnect], + audio_from: &[&[PortConnect];2], + audio_to: &[&[PortConnect];2], + ) -> Usually { + let name = name.as_ref(); + Ok(Self { + name: name.into(), + midi_in: JackMidiIn::new(jack, format!("M/{name}"), midi_from)?, + audio_ins: vec![ + JackAudioIn::new(jack, &format!("L/{name}"), audio_from[0])?, + JackAudioIn::new(jack, &format!("R/{name}"), audio_from[1])?, + ], + audio_outs: vec![ + JackAudioOut::new(jack, &format!("{name}/L"), audio_to[0])?, + JackAudioOut::new(jack, &format!("{name}/R"), audio_to[1])?, + ], input_meters: vec![0.0;2], output_meters: vec![0.0;2], - audio_outs: vec![], - name: "tek_sampler".into(), mapped: [const { None };128], unmapped: vec![], voices: Arc::new(RwLock::new(vec![])), @@ -71,31 +84,6 @@ impl Default for Sampler { color: Default::default(), mixing_mode: Default::default(), metering_mode: Default::default(), - } - } -} - -impl Sampler { - pub fn new ( - jack: &Jack, - name: impl AsRef, - midi_from: &[PortConnect], - audio_from: &[&[PortConnect];2], - audio_to: &[&[PortConnect];2], - ) -> Usually { - let name = name.as_ref(); - Ok(Self { - name: name.into(), - midi_in: Some(JackMidiIn::new(jack, format!("M/{name}"), midi_from)?), - audio_ins: vec![ - JackAudioIn::new(jack, &format!("L/{name}"), audio_from[0])?, - JackAudioIn::new(jack, &format!("R/{name}"), audio_from[1])?, - ], - audio_outs: vec![ - JackAudioOut::new(jack, &format!("{name}/L"), audio_to[0])?, - JackAudioOut::new(jack, &format!("{name}/R"), audio_to[1])?, - ], - ..Default::default() }) } /// Value of cursor @@ -146,11 +134,20 @@ pub struct Sample { pub channels: Vec>, pub rate: Option, pub gain: f32, + pub color: ItemTheme, } impl Sample { pub fn new (name: impl AsRef, start: usize, end: usize, channels: Vec>) -> Self { - Self { name: name.as_ref().into(), start, end, channels, rate: None, gain: 1.0 } + Self { + name: name.as_ref().into(), + start, + end, + channels, + rate: None, + gain: 1.0, + color: ItemTheme::random(), + } } pub fn play (sample: &Arc>, after: usize, velocity: &u7) -> Voice { Voice { diff --git a/crates/device/src/sampler/sampler_view.rs b/crates/device/src/sampler/sampler_view.rs index ee67807b..7b21233d 100644 --- a/crates/device/src/sampler/sampler_view.rs +++ b/crates/device/src/sampler/sampler_view.rs @@ -59,17 +59,25 @@ impl Sampler { let note_lo = editor.get_note_lo(); let note_pt = editor.get_note_pos(); let note_hi = editor.get_note_hi(); - Fixed::x(12, Map::south( + Fixed::x(if compact { 4 } else { 12 }, Map::south( 1, move||(note_lo..=note_hi).rev(), move|note, _index| { //let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a)))); let mut bg = if note == note_pt { Tui::g(64) } else { Color::Reset }; let mut fg = Tui::g(160); - let mapped: &Option>> = &self.mapped[note]; - if mapped.is_some() { - fg = Tui::g(224); - bg = Color::Rgb(0, if note == note_pt { 96 } else { 64 }, 0); + if let Some(mapped) = &self.mapped[note] { + let sample = mapped.read().unwrap(); + fg = if note == note_pt { + sample.color.lightest.rgb + } else { + Tui::g(224) + }; + bg = if note == note_pt { + sample.color.light.rgb + } else { + sample.color.base.rgb + }; } if let Some((index, _)) = self.recording { if note == index { @@ -110,6 +118,16 @@ impl Sampler { }))) } + pub fn view_sample_status (&self, note_pt: usize) -> impl Content + use<'_> { + Fixed::x(20, draw_info_v(if let Some((_, sample)) = &self.recording { + Some(sample) + } else if let Some(sample) = &self.mapped[note_pt] { + Some(sample) + } else { + None + })) + } + pub fn view_status (&self, index: usize) -> impl Content { draw_status(self.mapped[index].as_ref()) } @@ -189,11 +207,11 @@ fn draw_viewer (sample: Option<&Arc>>) -> impl Content + .y_bounds([0.0, height as f64]) .paint(|ctx| { let text = "press record to begin sampling"; - ctx.print( - (width - text.len() as u16) as f64 / 2.0, - height as f64 / 2.0, - text.red() - ); + //ctx.print( + //(width - text.len() as u16) as f64 / 2.0, + //height as f64 / 2.0, + //text.red() + //); }) .render(area, &mut to.buffer); } @@ -203,18 +221,37 @@ fn draw_viewer (sample: Option<&Arc>>) -> impl Content + fn draw_info (sample: Option<&Arc>>) -> impl Content + use<'_> { When(sample.is_some(), Thunk::new(move||{ let sample = sample.unwrap().read().unwrap(); - let theme = ItemTheme::G[96]; + let theme = sample.color; row!( - FieldH(theme, "Name", format!("{:<10}", sample.name.clone())), - FieldH(theme, "Length", format!("{:<8}", sample.channels[0].len())), - FieldH(theme, "Start", format!("{:<8}", sample.start)), - FieldH(theme, "End", format!("{:<8}", sample.end)), - FieldH(theme, "Transpose", " 0 "), - FieldH(theme, "Gain", format!("{}", sample.gain)), + FieldH(theme, "Name", format!("{:<10}", sample.name.clone())), + FieldH(theme, "Length", format!("{:<8}", sample.channels[0].len())), + FieldH(theme, "Start", format!("{:<8}", sample.start)), + FieldH(theme, "End", format!("{:<8}", sample.end)), + FieldH(theme, "Trans", "0"), + FieldH(theme, "Gain", format!("{}", sample.gain)), ) })) } +fn draw_info_v (sample: Option<&Arc>>) -> impl Content + use<'_> { + Either(sample.is_some(), Thunk::new(move||{ + let sample = sample.unwrap().read().unwrap(); + let theme = sample.color; + Fixed::x(20, col!( + Fill::x(Align::w(FieldH(theme, "Name ", format!("{:<10}", sample.name.clone())))), + Fill::x(Align::w(FieldH(theme, "Length", format!("{:<8}", sample.channels[0].len())))), + Fill::x(Align::w(FieldH(theme, "Start ", format!("{:<8}", sample.start)))), + Fill::x(Align::w(FieldH(theme, "End ", format!("{:<8}", sample.end)))), + Fill::x(Align::w(FieldH(theme, "Trans ", "0"))), + Fill::x(Align::w(FieldH(theme, "Gain ", format!("{}", sample.gain)))), + )) + }), Thunk::new(move||Tui::fg(Red, col!( + Tui::bold(true, "× No sample."), + "[r] record", + "[Shift-F9] import", + )))) +} + fn draw_status (sample: Option<&Arc>>) -> impl Content { Tui::bold(true, Tui::fg(Tui::g(224), sample .map(|sample|{ diff --git a/crates/engine/src/jack/jack_device.rs b/crates/engine/src/jack/jack_device.rs index c415f36e..7aa3c188 100644 --- a/crates/engine/src/jack/jack_device.rs +++ b/crates/engine/src/jack/jack_device.rs @@ -36,16 +36,16 @@ impl Handle for JackDevice { } impl Ports for JackDevice { - fn audio_ins(&self) -> Usually>> { + fn audio_ins (&self) -> Usually>> { Ok(self.ports.audio_ins.values().collect()) } - fn audio_outs(&self) -> Usually>> { + fn audio_outs (&self) -> Usually>> { Ok(self.ports.audio_outs.values().collect()) } - fn midi_ins(&self) -> Usually>> { + fn midi_ins (&self) -> Usually>> { Ok(self.ports.midi_ins.values().collect()) } - fn midi_outs(&self) -> Usually>> { + fn midi_outs (&self) -> Usually>> { Ok(self.ports.midi_outs.values().collect()) } } diff --git a/deps/tengri b/deps/tengri index 8bfd1a23..c954965a 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit 8bfd1a23a1f880a1d2fb104a158fc51f244acd6e +Subproject commit c954965ae125136286291e5f0d4532edf98f46ad