From 397e71edeeefaa73d38ac328b048d8a094ed0f4a Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 27 Apr 2025 16:33:00 +0300 Subject: [PATCH] midi: add pgup/pgdn; cleanup --- crates/app/src/api.rs | 6 +- crates/app/src/model.rs | 8 ++ crates/cli/tek.rs | 9 +- crates/jack/src/jack_client.rs | 36 ++++++-- crates/jack/src/jack_device.rs | 123 ++-------------------------- crates/jack/src/jack_event.rs | 3 + crates/jack/src/jack_port.rs | 13 +++ crates/midi/edn/keys_edit.edn | 39 +++++---- crates/midi/edn/keys_pool.edn | 12 +-- crates/midi/edn/piano-view-h.edn | 0 crates/midi/edn/piano-view-v.edn | 0 crates/midi/edn/view_pool.edn | 0 crates/midi/midi.scratch.rs | 55 ------------- crates/midi/src/clip/clip_editor.rs | 2 + deps/tengri | 2 +- 15 files changed, 96 insertions(+), 212 deletions(-) delete mode 100644 crates/midi/edn/piano-view-h.edn delete mode 100644 crates/midi/edn/piano-view-v.edn delete mode 100644 crates/midi/edn/view_pool.edn diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index 5881e667..6ad73389 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -117,8 +117,8 @@ defcom!([self, app: Tek] (Clock [cmd: ClockCommand] cmd.delegate(app, Self::Clock)?) (Editor [cmd: MidiEditCommand] delegate_to_editor(app, cmd)?) (Pool [cmd: PoolCommand] delegate_to_pool(app, cmd)?) - (ToggleHelp [] cmd!({ app.modal = match app.modal { Some(Modal::Help) => None, _ => Some(Modal::Help) }})) - (ToggleMenu [] cmd!({ app.modal = match app.modal { Some(Modal::Menu) => None, _ => Some(Modal::Menu) }})) + (ToggleHelp [] cmd!(app.toggle_modal(Some(Modal::Help)))) + (ToggleMenu [] cmd!(app.toggle_modal(Some(Modal::Menu)))) (Color [p: ItemTheme] app.set_color(Some(p)).map(Self::Color)) (Enqueue [c: MaybeClip] cmd_todo!("\n\rtodo: enqueue {c:?}")) (History [d: isize] cmd_todo!("\n\rtodo: history {d:?}")) @@ -144,7 +144,7 @@ defcom!([self, app: Tek] (Stop [index: usize] cmd!(app.tracks[index].player.enqueue_next(None))) (Add [] Some(Self::Del(app.track_add_focus()?))) (SetColor [i: usize, c: ItemTheme] Some(Self::SetColor(i, app.track_set_color(i, c)))) - (ToggleRec [] { app.track_toggle_record(); Some(Self::ToggleRec) }) + (ToggleRec [] { app.track_toggle_record(); Some(Self::ToggleRec) }) (ToggleMon [] { app.track_toggle_monitor(); Some(Self::ToggleMon) })) (SceneCommand diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index a4b323c0..ba3a36cb 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -182,6 +182,14 @@ impl Tek { } } + pub fn toggle_modal (&mut self, modal: Option) { + self.modal = if self.modal == modal { + None + } else { + modal + } + } + // Create new clip in pool when entering empty cell pub fn clip_auto_create (&mut self) { if let Some(ref pool) = self.pool diff --git a/crates/cli/tek.rs b/crates/cli/tek.rs index 0f38911b..33455dde 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -172,12 +172,9 @@ impl Cli { /// CLI header const HEADER: &'static str = r#" -░▒▓████████▓▒░▒▓███████▓▒░▒▓█▓▒░░▒▓█▓▒░░ -░░░░▒▓█▓▒░░░░░▒▓█▓▒░░░░░░░▒▓█▓▒░▒▓█▓▒░░░ -░░░░▒▓█▓▒░░░░░▒▓█████▓▒░░░▒▓██████▓▒░░░░ -░░░░▒▓█▓▒░░░░░▒▓█▓▒░░░░░░░▒▓█▓▒░▒▓█▓▒░░░ -░░░░▒▓█▓▒░░░░░▒▓█▓▒░░░░░░░▒▓█▓▒░░▒▓█▓▒░░ -░░░░▒▓█▓▒░░░░░▒▓███████▓▒░▒▓█▓▒░░▒▓█▓▒░░"#; + ╓─╥─╖ ╓──╖ ╥ ╖ + ║ ╟─╌ ╟─╡ + ╨ ╙──╜ ╨ ╜"#; #[cfg(test)] #[test] fn test_cli () { use clap::CommandFactory; diff --git a/crates/jack/src/jack_client.rs b/crates/jack/src/jack_client.rs index 2dadd691..80b97587 100644 --- a/crates/jack/src/jack_client.rs +++ b/crates/jack/src/jack_client.rs @@ -1,6 +1,7 @@ use crate::*; use ::jack::contrib::*; use self::JackState::*; + /// Things that can provide a [jack::Client] reference. pub trait HasJack { /// Return the internal [jack::Client] handle @@ -38,13 +39,25 @@ pub trait HasJack { Ok(()) } } -impl HasJack for Jack { fn jack (&self) -> &Jack { self } } -impl HasJack for &Jack { fn jack (&self) -> &Jack { self } } + +impl HasJack for Jack { + fn jack (&self) -> &Jack { + self + } +} + +impl HasJack for &Jack { + fn jack (&self) -> &Jack { + self + } +} + /// Wraps [JackState] and through it [jack::Client]. #[derive(Clone, Debug, Default)] pub struct Jack { state: Arc> } + impl Jack { pub fn new (name: &str) -> Usually { Ok(Self { @@ -82,6 +95,7 @@ impl Jack { Ok(app) } } + /// This is a connection which may be [Inactive], [Activating], or [Active]. /// In the [Active] and [Inactive] states, [JackState::client] returns a /// [jack::Client], which you can use to talk to the JACK API. @@ -95,33 +109,36 @@ impl Jack { /// After activation. Must not be dropped for JACK thread to persist. Active(DynamicAsyncClient<'static>), } + impl JackState { - fn new (client: Client) -> Arc> { Arc::new(RwLock::new(Self::Inactive(client))) } + fn new (client: Client) -> Arc> { + Arc::new(RwLock::new(Self::Inactive(client))) + } } -//has_jack_client!(|self: JackState|match self { - //Inert => panic!("jack client not activated"), - //Inactive(ref client) => client, - //Activating => panic!("jack client has not finished activation"), - //Active(ref client) => client.as_client(), -//}); + /// This is a boxed realtime callback. pub type BoxedAudioHandler<'j> = Box Control + Send + 'j>; + /// This is the notification handler wrapper for a boxed realtime callback. pub type DynamicAudioHandler<'j> = ClosureProcessHandler<(), BoxedAudioHandler<'j>>; + /// This is a boxed [JackEvent] callback. pub type BoxedJackEventHandler<'j> = Box; + /// This is the notification handler wrapper for a boxed [JackEvent] callback. pub type DynamicNotifications<'j> = Notifications>; + /// This is a running JACK [AsyncClient] with maximum type erasure. /// It has one [Box] containing a function that handles [JackEvent]s, /// and another [Box] containing a function that handles realtime IO, /// and that's all it knows about them. pub type DynamicAsyncClient<'j> = AsyncClient, DynamicAudioHandler<'j>>; + /// Implement [Audio]: provide JACK callbacks. #[macro_export] macro_rules! audio { (| @@ -134,6 +151,7 @@ pub type DynamicAsyncClient<'j> } } } + /// Trait for thing that has a JACK process callback. pub trait Audio: Send + Sync { fn handle (&mut self, _event: JackEvent) {} diff --git a/crates/jack/src/jack_device.rs b/crates/jack/src/jack_device.rs index 3ef67085..c415f36e 100644 --- a/crates/jack/src/jack_device.rs +++ b/crates/jack/src/jack_device.rs @@ -1,4 +1,5 @@ use crate::* + /// A [AudioComponent] bound to a JACK client and a set of ports. pub struct JackDevice { /// The active JACK client of this device. @@ -9,6 +10,7 @@ pub struct JackDevice { /// The "real" readable/writable `Port`s are owned by the `state`. pub ports: UnownedJackPorts, } + impl std::fmt::Debug for JackDevice { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("JackDevice") @@ -16,6 +18,7 @@ impl std::fmt::Debug for JackDevice { .finish() } } + impl Render for JackDevice { type Engine = E; fn min_size(&self, to: E::Size) -> Perhaps { @@ -25,11 +28,13 @@ impl Render for JackDevice { self.state.read().unwrap().render(to) } } + impl Handle for JackDevice { fn handle(&mut self, from: &E::Input) -> Perhaps { self.state.write().unwrap().handle(from) } } + impl Ports for JackDevice { fn audio_ins(&self) -> Usually>> { Ok(self.ports.audio_ins.values().collect()) @@ -44,6 +49,7 @@ impl Ports for JackDevice { Ok(self.ports.midi_outs.values().collect()) } } + impl JackDevice { /// Returns a locked mutex of the state's contents. pub fn state(&self) -> LockResult>>> { @@ -78,120 +84,3 @@ impl JackDevice { .connect_ports(self.audio_outs()?[index], port)?) } } -//////////////////////////////////////////////////////////////////////////////////// - -///// `JackDevice` factory. Creates JACK `Client`s, performs port registration -///// and activation, and encapsulates a `AudioComponent` into a `JackDevice`. -//pub struct Jack { - //pub client: Client, - //pub midi_ins: Vec, - //pub audio_ins: Vec, - //pub midi_outs: Vec, - //pub audio_outs: Vec, -//} - -//impl Jack { - //pub fn new(name: &str) -> Usually { - //Ok(Self { - //midi_ins: vec![], - //audio_ins: vec![], - //midi_outs: vec![], - //audio_outs: vec![], - //client: Client::new(name, ClientOptions::NO_START_SERVER)?.0, - //}) - //} - //pub fn run<'a: 'static, D, E>( - //self, - //state: impl FnOnce(JackPorts) -> Box, - //) -> Usually> - //where - //D: AudioComponent + Sized + 'static, - //E: Engine + 'static, - //{ - //let owned_ports = JackPorts { - //audio_ins: register_ports(&self.client, self.audio_ins, AudioIn::default())?, - //audio_outs: register_ports(&self.client, self.audio_outs, AudioOut::default())?, - //midi_ins: register_ports(&self.client, self.midi_ins, MidiIn::default())?, - //midi_outs: register_ports(&self.client, self.midi_outs, MidiOut::default())?, - //}; - //let midi_outs = owned_ports - //.midi_outs - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let midi_ins = owned_ports - //.midi_ins - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let audio_outs = owned_ports - //.audio_outs - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let audio_ins = owned_ports - //.audio_ins - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let state = Arc::new(RwLock::new(state(owned_ports) as Box>)); - //let client = self.client.activate_async( - //Notifications(Box::new({ - //let _state = state.clone(); - //move |_event| { - //// FIXME: this deadlocks - ////state.lock().unwrap().handle(&event).unwrap(); - //} - //}) as Box), - //ClosureProcessHandler::new(Box::new({ - //let state = state.clone(); - //move |c: &Client, s: &ProcessScope| state.write().unwrap().process(c, s) - //}) as BoxedAudioHandler), - //)?; - //Ok(JackDevice { - //ports: UnownedJackPorts { - //audio_ins: query_ports(&client.as_client(), audio_ins), - //audio_outs: query_ports(&client.as_client(), audio_outs), - //midi_ins: query_ports(&client.as_client(), midi_ins), - //midi_outs: query_ports(&client.as_client(), midi_outs), - //}, - //client, - //state, - //}) - //} - //pub fn audio_in(mut self, name: &str) -> Self { - //self.audio_ins.push(name.to_string()); - //self - //} - //pub fn audio_out(mut self, name: &str) -> Self { - //self.audio_outs.push(name.to_string()); - //self - //} - //pub fn midi_in(mut self, name: &str) -> Self { - //self.midi_ins.push(name.to_string()); - //self - //} - //pub fn midi_out(mut self, name: &str) -> Self { - //self.midi_outs.push(name.to_string()); - //self - //} -//} - -///// A UI component that may be associated with a JACK client by the `Jack` factory. -//pub trait AudioComponent: Component + Audio { - ///// Perform type erasure for collecting heterogeneous devices. - //fn boxed(self) -> Box> - //where - //Self: Sized + 'static, - //{ - //Box::new(self) - //} -//} - -///// All things that implement the required traits can be treated as `AudioComponent`. -//impl + Audio> AudioComponent for W {} - -///////// - -/* -*/ diff --git a/crates/jack/src/jack_event.rs b/crates/jack/src/jack_event.rs index 8884379a..43571f69 100644 --- a/crates/jack/src/jack_event.rs +++ b/crates/jack/src/jack_event.rs @@ -1,4 +1,5 @@ use crate::*; + /// Event enum for JACK events. #[derive(Debug, Clone, PartialEq)] pub enum JackEvent { ThreadInit, @@ -12,8 +13,10 @@ use crate::*; GraphReorder, XRun, } + /// Generic notification handler that emits [JackEvent] pub struct Notifications(pub T); + impl NotificationHandler for Notifications { fn thread_init(&self, _: &Client) { self.0(JackEvent::ThreadInit); diff --git a/crates/jack/src/jack_port.rs b/crates/jack/src/jack_port.rs index d699a7d6..a5f474ec 100644 --- a/crates/jack/src/jack_port.rs +++ b/crates/jack/src/jack_port.rs @@ -1,4 +1,5 @@ use crate::*; + macro_rules! impl_port { ($Name:ident : $Spec:ident -> $Pair:ident |$jack:ident, $name:ident|$port:expr) => { #[derive(Debug)] pub struct $Name { @@ -73,18 +74,25 @@ macro_rules! impl_port { } }; } + impl_port!(JackAudioIn: AudioIn -> AudioOut |j, n|j.register_port::(n)); + impl_port!(JackAudioOut: AudioOut -> AudioIn |j, n|j.register_port::(n)); + impl_port!(JackMidiIn: MidiIn -> MidiOut |j, n|j.register_port::(n)); + impl_port!(JackMidiOut: MidiOut -> MidiIn |j, n|j.register_port::(n)); + pub trait JackPort: HasJack { type Port: PortSpec; type Pair: PortSpec; fn port (&self) -> &Port; } + pub trait JackPortConnect: JackPort { fn connect_to (&self, to: T) -> Usually; } + pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port> { fn conn (&self) -> &[PortConnect]; fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec { @@ -146,6 +154,7 @@ pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port), } + #[derive(Clone, Copy, Debug, PartialEq)] pub enum PortConnectScope { One, All } + #[derive(Clone, Copy, Debug, PartialEq)] pub enum PortConnectStatus { Missing, Disconnected, Connected, Mismatch, } + #[derive(Clone, Debug)] pub struct PortConnect { pub name: PortConnectName, pub scope: PortConnectScope, pub status: Arc, Arc, PortConnectStatus)>>>, pub info: Arc, } + impl PortConnect { pub fn collect (exact: &[impl AsRef], re: &[impl AsRef], re_all: &[impl AsRef]) -> Vec diff --git a/crates/midi/edn/keys_edit.edn b/crates/midi/edn/keys_edit.edn index 0c9d68a5..d9ff8eb6 100644 --- a/crates/midi/edn/keys_edit.edn +++ b/crates/midi/edn/keys_edit.edn @@ -1,19 +1,28 @@ -(@up note/pos :note-pos-next) -(@w note/pos :note-pos-next) -(@down note/pos :note-pos-prev) -(@s note/pos :note-pos-prev) -(@comma note/len :note-len-prev) -(@period note/len :note-len-next) -(@plus note/range :note-range-next-) -(@underscore note/range :note-range-prev-) +(@up note/pos :note-pos-next) +(@w note/pos :note-pos-next) +(@down note/pos :note-pos-prev) +(@s note/pos :note-pos-prev) -(@left time/pos :time-pos-prev) -(@a time/pos :time-pos-prev) -(@right time/pos :time-pos-next) -(@d time/pos :time-pos-next) -(@equal time/zoom :time-zoom-prev) -(@minus time/zoom :time-zoom-next) -(@z time/lock) +(@pgup note/pos :note-pos-next-octave) +(@pgdn note/pos :note-pos-prev-octave) + +(@comma note/len :note-len-prev) +(@period note/len :note-len-next) +(@lt note/len :note-len-prev) +(@gt note/len :note-len-next) + +(@plus note/range :note-range-next) +(@underscore note/range :note-range-prev) + +(@left time/pos :time-pos-prev) +(@a time/pos :time-pos-prev) +(@right time/pos :time-pos-next) +(@d time/pos :time-pos-next) + +(@equal time/zoom :time-zoom-prev) +(@minus time/zoom :time-zoom-next) + +(@z time/lock) (@enter note/put) (@shift-enter note/append) diff --git a/crates/midi/edn/keys_pool.edn b/crates/midi/edn/keys_pool.edn index ad7e00f4..b06a4b17 100644 --- a/crates/midi/edn/keys_pool.edn +++ b/crates/midi/edn/keys_pool.edn @@ -2,11 +2,11 @@ (@t length begin) (@m import begin) (@x export begin) -(@c clip color :current :random-color) -(@openbracket select :previous) -(@closebracket select :next) -(@lt swap :current :previous) -(@gt swap :current :next) -(@delete clip/delete :current) +(@c clip color :clip :random-color) +(@openbracket select :clip-prev) +(@closebracket select :clip-next) +(@lt swap :clip :clip-prev) +(@gt swap :clip :clip-next) +(@delete clip/delete :clip) (@shift-A clip/add :after :new-clip) (@shift-D clip/add :after :cloned-clip) diff --git a/crates/midi/edn/piano-view-h.edn b/crates/midi/edn/piano-view-h.edn deleted file mode 100644 index e69de29b..00000000 diff --git a/crates/midi/edn/piano-view-v.edn b/crates/midi/edn/piano-view-v.edn deleted file mode 100644 index e69de29b..00000000 diff --git a/crates/midi/edn/view_pool.edn b/crates/midi/edn/view_pool.edn deleted file mode 100644 index e69de29b..00000000 diff --git a/crates/midi/midi.scratch.rs b/crates/midi/midi.scratch.rs index b4b350eb..8602766a 100644 --- a/crates/midi/midi.scratch.rs +++ b/crates/midi/midi.scratch.rs @@ -1,53 +1,4 @@ /////////////////////////////////////////////////////////////////////////////////////////////////// -//fn to_clips_command (state: &MidiPool, input: &Event) -> Option { - //use KeyCode::{Up, Down, Delete, Char}; - //use PoolCommand as Cmd; - //let index = state.clip_index(); - //let count = state.clips().len(); - //Some(match input { - //kpat!(Char('n')) => Cmd::Rename(ClipRenameCommand::Begin), - //kpat!(Char('t')) => Cmd::Length(ClipLengthCommand::Begin), - //kpat!(Char('m')) => Cmd::Import(FileBrowserCommand::Begin), - //kpat!(Char('x')) => Cmd::Export(FileBrowserCommand::Begin), - //kpat!(Char('c')) => Cmd::Clip(PoolClipCommand::SetColor(index, ItemColor::random())), - //kpat!(Char('[')) | kpat!(Up) => Cmd::Select( - //index.overflowing_sub(1).0.min(state.clips().len() - 1) - //), - //kpat!(Char(']')) | kpat!(Down) => Cmd::Select( - //index.saturating_add(1) % state.clips().len() - //), - //kpat!(Char('<')) => if index > 1 { - //state.set_clip_index(state.clip_index().saturating_sub(1)); - //Cmd::Clip(PoolClipCommand::Swap(index - 1, index)) - //} else { - //return None - //}, - //kpat!(Char('>')) => if index < count.saturating_sub(1) { - //state.set_clip_index(state.clip_index() + 1); - //Cmd::Clip(PoolClipCommand::Swap(index + 1, index)) - //} else { - //return None - //}, - //kpat!(Delete) => if index > 0 { - //state.set_clip_index(index.min(count.saturating_sub(1))); - //Cmd::Clip(PoolClipCommand::Delete(index)) - //} else { - //return None - //}, - //kpat!(Char('a')) | kpat!(Shift-Char('A')) => Cmd::Clip(PoolClipCommand::Add(count, MidiClip::new( - //"Clip", true, 4 * PPQ, None, Some(ItemTheme::random()) - //))), - //kpat!(Char('i')) => Cmd::Clip(PoolClipCommand::Add(index + 1, MidiClip::new( - //"Clip", true, 4 * PPQ, None, Some(ItemTheme::random()) - //))), - //kpat!(Char('d')) | kpat!(Shift-Char('D')) => { - //let mut clip = state.clips()[index].read().unwrap().duplicate(); - //clip.color = ItemTheme::random_near(clip.color, 0.25); - //Cmd::Clip(PoolClipCommand::Add(index + 1, clip)) - //}, - //_ => return None - //}) -//} //keymap!(KEYS_MIDI_EDITOR = |s: MidiEditor, _input: Event| MidiEditCommand { //key(Up) => SetNoteCursor(s.note_pos() + 1), //key(Char('w')) => SetNoteCursor(s.note_pos() + 1), @@ -74,12 +25,6 @@ //key(Char('_')) => SetTimeZoom(if s.time_lock().get() { s.time_zoom().get() } else { NoteDuration::next(s.time_zoom().get()) }), //key(Char('=')) => SetTimeZoom(if s.time_lock().get() { s.time_zoom().get() } else { NoteDuration::prev(s.time_zoom().get()) }), //key(Char('+')) => SetTimeZoom(if s.time_lock().get() { s.time_zoom().get() } else { NoteDuration::prev(s.time_zoom().get()) }), - //key(Enter) => PutNote, - //ctrl(key(Enter)) => AppendNote, - //key(Char(',')) => SetNoteLength(NoteDuration::prev(s.note_len())), - //key(Char('.')) => SetNoteLength(NoteDuration::next(s.note_len())), - //key(Char('<')) => SetNoteLength(NoteDuration::prev(s.note_len())), - //key(Char('>')) => SetNoteLength(NoteDuration::next(s.note_len())), ////// TODO: kpat!(Char('/')) => // toggle 3plet ////// TODO: kpat!(Char('?')) => // toggle dotted //}); diff --git a/crates/midi/src/clip/clip_editor.rs b/crates/midi/src/clip/clip_editor.rs index 99f98e33..4ec349d5 100644 --- a/crates/midi/src/clip/clip_editor.rs +++ b/crates/midi/src/clip/clip_editor.rs @@ -61,6 +61,8 @@ provide!(usize: |self: MidiEditor| { ":note-pos" => self.note_pos(), ":note-pos-next" => self.note_pos() + 1, ":note-pos-prev" => self.note_pos().saturating_sub(1), + ":note-pos-next-octave" => self.note_pos() + 12, + ":note-pos-prev-octave" => self.note_pos().saturating_sub(12), ":note-len" => self.note_len(), ":note-len-next" => self.note_len() + 1, diff --git a/deps/tengri b/deps/tengri index 7ba37f3f..95149b79 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit 7ba37f3f0255c2c61ed3371e2c437514f27f6d6d +Subproject commit 95149b79c4b0d013dcba1eda80e1d14a01087fea