From 03024f8a14a81aa2b494fd52db793e259be5127e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 14 May 2025 22:17:42 +0300 Subject: [PATCH] groovebox: looks surprisingly well --- config/config_groovebox.edn | 4 +- crates/app/src/model.rs | 12 ++--- crates/app/src/view.rs | 45 +++++++++++------- crates/device/src/browser/browser_view.rs | 44 ++++++++++++------ crates/device/src/device.rs | 24 ++++++++++ crates/device/src/editor/editor_view.rs | 5 +- crates/device/src/lib.rs | 1 + crates/device/src/sampler/sampler_midi.rs | 22 ++++----- crates/device/src/sampler/sampler_model.rs | 54 +++++++++------------- crates/engine/src/jack/jack_device.rs | 8 ++-- deps/tengri | 2 +- 11 files changed, 130 insertions(+), 91 deletions(-) diff --git a/config/config_groovebox.edn b/config/config_groovebox.edn index 318f1d16..3afc185b 100644 --- a/config/config_groovebox.edn +++ b/config/config_groovebox.edn @@ -14,5 +14,7 @@ (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 :view-ports-status (bsp/s :view-editor-status :view-pool))))) + (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/crates/app/src/model.rs b/crates/app/src/model.rs index 148a7142..4948c2c1 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -244,22 +244,22 @@ impl App { Some(Dialog::Menu(0)) } fn dialog_save (&self) -> Option { - Some(Dialog::Browser(BrowserTarget::SaveProject, Default::default())) + Some(Dialog::Browser(BrowserTarget::SaveProject, Browser::new(None).unwrap())) } fn dialog_load (&self) -> Option { - Some(Dialog::Browser(BrowserTarget::LoadProject, Default::default())) + Some(Dialog::Browser(BrowserTarget::LoadProject, Browser::new(None).unwrap())) } fn dialog_import_clip (&self) -> Option { - Some(Dialog::Browser(BrowserTarget::ImportClip(Default::default()), Default::default())) + Some(Dialog::Browser(BrowserTarget::ImportClip(Default::default()), Browser::new(None).unwrap())) } fn dialog_export_clip (&self) -> Option { - Some(Dialog::Browser(BrowserTarget::ExportClip(Default::default()), Default::default())) + Some(Dialog::Browser(BrowserTarget::ExportClip(Default::default()), Browser::new(None).unwrap())) } fn dialog_import_sample (&self) -> Option { - Some(Dialog::Browser(BrowserTarget::ImportSample(Default::default()), Default::default())) + Some(Dialog::Browser(BrowserTarget::ImportSample(Default::default()), Browser::new(None).unwrap())) } fn dialog_export_sample (&self) -> Option { - Some(Dialog::Browser(BrowserTarget::ExportSample(Default::default()), Default::default())) + 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 4c8099c5..1d72d059 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -19,7 +19,7 @@ impl App { let cache = self.view_cache.read().unwrap(); let theme = self.color; let playing = self.clock().is_rolling(); - Fixed::xy(20, 7, col!( + 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 @@ -60,22 +60,33 @@ impl App { self.editor() } pub fn view_editor_status (&self) -> impl Content + use<'_> { - Fixed::y(5, self.editor().map(|e|Bsp::s(e.clip_status(), e.edit_status()))) + self.editor().map(|e|Bsp::s(e.clip_status(), e.edit_status())) } - pub fn view_ports_status (&self) -> impl Content + use<'_> { + pub fn view_midi_ports_status (&self) -> impl Content + use<'_> { self.project.get_track().map(|track|Bsp::s( - Fixed::y(2.max(track.sequencer.midi_ins.len() as u16), Align::n(Map::south(1, - ||track.sequencer.midi_ins.iter(), - |port, index|Fixed::x(20, FieldV( - self.color, - format!("MIDI in {index}: "), - format!("{}", port.name())))))), - Fixed::y(2.max(track.sequencer.midi_outs.len() as u16), Align::n(Map::south(1, - ||track.sequencer.midi_outs.iter(), - |port, index|Fixed::x(20, FieldV( - self.color, - format!("MIDI out {index}:"), - format!("{}", port.name())))))) + 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<'_> { @@ -83,8 +94,8 @@ impl App { } pub fn view_pool (&self) -> impl Content + use<'_> { Fixed::x(20, Bsp::s( - Fill::x(Align::w(Bsp::s("", FieldH(self.color, "Pool", "")))), - Fill::y(Align::n(Tui::bg(Black, PoolView(&self.project.pool)))), + Fill::x(Align::w(FieldH(self.color, "MIDI 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<'_> { 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_view.rs b/crates/device/src/editor/editor_view.rs index ab0ca78f..454290a9 100644 --- a/crates/device/src/editor/editor_view.rs +++ b/crates/device/src/editor/editor_view.rs @@ -30,8 +30,9 @@ 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()); - Fixed::x(20, Bsp::s( - Fill::x(Align::w(FieldH(color, "Time", format!("{length}/{time_zoom}+{time_pos} {time_lock}")))), + 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/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/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 b9137636..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 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..f7306de5 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit 8bfd1a23a1f880a1d2fb104a158fc51f244acd6e +Subproject commit f7306de55faea42bb1531955a92220fd48d9aadb