From cb7e4f7a95752b124722b8c95c016f301953c203 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 21 May 2025 00:07:35 +0300 Subject: [PATCH 1/2] wip: port: make device --- crates/app/src/model.rs | 46 ++-- crates/app/src/view.rs | 2 +- crates/device/Cargo.toml | 18 +- crates/device/src/arranger.rs | 10 +- crates/device/src/arranger/arranger_api.rs | 4 +- crates/device/src/arranger/arranger_model.rs | 94 ++----- crates/device/src/arranger/arranger_tracks.rs | 81 +++++- crates/device/src/arranger/arranger_view.rs | 8 +- crates/device/src/clock/clock_model.rs | 8 +- crates/device/src/lib.rs | 19 +- crates/device/src/lv2/lv2_model.rs | 4 +- crates/device/src/pool/pool_model.rs | 2 +- crates/device/src/port.rs | 8 + crates/device/src/port/port_audio_in.rs | 44 ++++ crates/device/src/port/port_audio_out.rs | 44 ++++ .../src/port/port_connect.rs} | 82 +++--- .../src/port/port_midi_in.rs} | 117 +++------ .../src/port/port_midi_out.rs} | 109 +++----- crates/device/src/sampler/sampler_model.rs | 10 +- crates/device/src/sequencer/seq_model.rs | 16 +- crates/engine/src/audio.rs | 2 - crates/engine/src/audio/audio_in.rs | 86 ------- crates/engine/src/audio/audio_out.rs | 77 ------ crates/engine/src/jack.rs | 237 ++++++++++++++++-- crates/engine/src/jack/jack_client.rs | 122 --------- crates/engine/src/jack/jack_device.rs | 86 ------- crates/engine/src/jack/jack_handler.rs | 105 -------- crates/engine/src/lib.rs | 1 - crates/engine/src/midi.rs | 13 +- crates/engine/src/midi/midi_hold.rs | 10 - deps/tengri | 2 +- 31 files changed, 602 insertions(+), 865 deletions(-) create mode 100644 crates/device/src/port.rs create mode 100644 crates/device/src/port/port_audio_in.rs create mode 100644 crates/device/src/port/port_audio_out.rs rename crates/{engine/src/jack/jack_port.rs => device/src/port/port_connect.rs} (67%) rename crates/{engine/src/midi/midi_in.rs => device/src/port/port_midi_in.rs} (50%) rename crates/{engine/src/midi/midi_out.rs => device/src/port/port_midi_out.rs} (65%) delete mode 100644 crates/engine/src/audio.rs delete mode 100644 crates/engine/src/audio/audio_in.rs delete mode 100644 crates/engine/src/audio/audio_out.rs delete mode 100644 crates/engine/src/jack/jack_client.rs delete mode 100644 crates/engine/src/jack/jack_device.rs delete mode 100644 crates/engine/src/jack/jack_handler.rs delete mode 100644 crates/engine/src/midi/midi_hold.rs diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index e81da8da..80d7f80b 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; #[derive(Default, Debug)] pub struct App { /// Must not be dropped for the duration of the process - pub jack: Jack, + pub jack: Jack<'static>, /// Port handles pub ports: std::collections::BTreeMap>, /// Display size @@ -27,45 +27,33 @@ pub struct App { pub color: ItemTheme, } -has!(Jack: |self: App|self.jack); +has!(Jack<'static>: |self: App|self.jack); has!(Pool: |self: App|self.pool); has!(Option: |self: App|self.dialog); has!(Clock: |self: App|self.project.clock); has!(Option: |self: App|self.project.editor); has!(Selection: |self: App|self.project.selection); -has!(Vec: |self: App|self.project.midi_ins); -has!(Vec: |self: App|self.project.midi_outs); +has!(Vec: |self: App|self.project.midi_ins); +has!(Vec: |self: App|self.project.midi_outs); has!(Vec: |self: App|self.project.scenes); has!(Vec: |self: App|self.project.tracks); has!(Measure: |self: App|self.size); -maybe_has!(Track: |self: App| +maybe_has!(Track: |self: App| { MaybeHas::::get(&self.project) }; { MaybeHas::::get_mut(&mut self.project) }); -impl HasTrackScroll for App { - fn track_scroll (&self) -> usize { self.project.track_scroll() } -} -maybe_has!(Scene: |self: App| +impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } } +maybe_has!(Scene: |self: App| { MaybeHas::::get(&self.project) }; { MaybeHas::::get_mut(&mut self.project) }); -impl HasSceneScroll for App { - fn scene_scroll (&self) -> usize { self.project.scene_scroll() } -} +impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } } has_clips!(|self: App|self.pool.clips); -impl HasClipsSize for App { - fn clips_size (&self) -> &Measure { &self.project.inner_size } -} -from_dsl!(ClockCommand: - |state: App, iter|FromDsl::take_from(state.clock(), iter)); -from_dsl!(MidiEditCommand: - |state: App, iter|Ok(state.editor().map(|x|FromDsl::take_from(x, iter)).transpose()?.flatten())); -from_dsl!(PoolCommand: - |state: App, iter|FromDsl::take_from(&state.pool, iter)); -from_dsl!(SamplerCommand: - |state: App, iter|Ok(state.project.sampler().map(|x|FromDsl::take_from(x, iter)).transpose()?.flatten())); -from_dsl!(ArrangementCommand: - |state: App, iter|FromDsl::take_from(&state.project, iter)); -from_dsl!(DialogCommand: - |state: App, iter|FromDsl::take_from(&state.dialog, iter)); +impl HasClipsSize for App { fn clips_size (&self) -> &Measure { &self.project.inner_size } } +from_dsl!(ClockCommand: |state: App, iter|Namespace::take_from(state.clock(), iter)); +from_dsl!(MidiEditCommand: |state: App, iter|Ok(state.editor().map(|x|Namespace::take_from(x, iter)).transpose()?.flatten())); +from_dsl!(PoolCommand: |state: App, iter|Namespace::take_from(&state.pool, iter)); +from_dsl!(SamplerCommand: |state: App, iter|Ok(state.project.sampler().map(|x|Namespace::take_from(x, iter)).transpose()?.flatten())); +from_dsl!(ArrangementCommand: |state: App, iter|Namespace::take_from(&state.project, iter)); +from_dsl!(DialogCommand: |state: App, iter|Namespace::take_from(&state.dialog, iter)); //has_editor!(|self: App|{ //editor = self.editor; //editor_w = { @@ -121,7 +109,7 @@ impl App { let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].name(); let track = self.track().expect("no active track"); let port = format!("{}/Sampler", &track.name); - let connect = PortConnect::exact(format!("{name}:{midi}")); + let connect = Connect::exact(format!("{name}:{midi}")); let sampler = if let Ok(sampler) = Sampler::new( &self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]] ) { @@ -464,7 +452,7 @@ impl Configuration { let cond = cond.unwrap(); println!("ok"); map.add_layer_if( - Box::new(move |state: &App|FromDsl::take_from_or_fail( + Box::new(move |state: &App|Namespace::take_from_or_fail( state, &mut exp.clone(), format!("missing input layer conditional") )), keys diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index a30ae1ef..6d1c2549 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -5,7 +5,7 @@ pub(crate) use ::tengri::tui::ratatui::prelude::Position; impl App { pub fn view (&self) -> impl Content + '_ { let view: Perhaps>> = - FromDsl::take_from(self, &mut self.config.view.clone()); + Namespace::take_from(self, &mut self.config.view.clone()); Either(view.is_ok(), ThunkRender::new(move|to|if let Some(view) = view.as_ref().unwrap().as_ref() { Content::render(view, to) diff --git a/crates/device/Cargo.toml b/crates/device/Cargo.toml index 6b702f98..d0e66b23 100644 --- a/crates/device/Cargo.toml +++ b/crates/device/Cargo.toml @@ -16,18 +16,20 @@ wavers = { workspace = true, optional = true } winit = { workspace = true, optional = true } [features] -default = [ "browser", "clock", "editor", "sequencer", "sampler", "lv2", "arranger" ] +default = [ "arranger", "sampler", "lv2" ] + +arranger = [ "port", "editor", "sequencer", "editor" ] +browser = [] +clap = [] clock = [] -arranger = [ "editor" ] editor = [] +lv2 = [ "port", "livi", "winit" ] meter = [] mixer = [] -browser = [] pool = [] -sequencer = [ "clock", "uuid", "pool" ] -sampler = [ "meter", "mixer", "browser", "symphonia", "wavers" ] -lv2 = [ "livi", "winit" ] +port = [] +sampler = [ "port", "meter", "mixer", "browser", "symphonia", "wavers" ] +sequencer = [ "port", "clock", "uuid", "pool" ] +sf2 = [] vst2 = [] vst3 = [] -clap = [] -sf2 = [] diff --git a/crates/device/src/arranger.rs b/crates/device/src/arranger.rs index a3e25c42..11305006 100644 --- a/crates/device/src/arranger.rs +++ b/crates/device/src/arranger.rs @@ -1,13 +1,5 @@ use crate::*; -/// Define a type alias for iterators of sized items (columns). -macro_rules! def_sizes_iter { - ($Type:ident => $($Item:ty),+) => { - pub trait $Type<'a> = - Iterator + Send + Sync + 'a; - } -} - mod arranger_api; pub use self::arranger_api::*; mod arranger_clip; pub use self::arranger_clip::*; mod arranger_model; pub use self::arranger_model::*; @@ -20,7 +12,7 @@ def_sizes_iter!(ScenesSizes => Scene); def_sizes_iter!(TracksSizes => Track); def_sizes_iter!(InputsSizes => MidiInput); def_sizes_iter!(OutputsSizes => MidiOutput); -def_sizes_iter!(PortsSizes => Arc, [PortConnect]); +def_sizes_iter!(PortsSizes => Arc, [Connect]); pub(crate) fn wrap (bg: Color, fg: Color, content: impl Content) -> impl Content { let left = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("▐"))); diff --git a/crates/device/src/arranger/arranger_api.rs b/crates/device/src/arranger/arranger_api.rs index 10b79cd2..1df4dc13 100644 --- a/crates/device/src/arranger/arranger_api.rs +++ b/crates/device/src/arranger/arranger_api.rs @@ -156,7 +156,7 @@ impl ArrangementCommand { fn output_add (arranger: &mut Arrangement) -> Perhaps { arranger.midi_outs.push(MidiOutput::new( arranger.jack(), - format!("/M{}", arranger.midi_outs.len() + 1), + &format!("/M{}", arranger.midi_outs.len() + 1), &[] )?); Ok(None) @@ -164,7 +164,7 @@ impl ArrangementCommand { fn input_add (arranger: &mut Arrangement) -> Perhaps { arranger.midi_ins.push(MidiInput::new( arranger.jack(), - format!("M{}/", arranger.midi_ins.len() + 1), + &format!("M{}/", arranger.midi_ins.len() + 1), &[] )?); Ok(None) diff --git a/crates/device/src/arranger/arranger_model.rs b/crates/device/src/arranger/arranger_model.rs index a11032fb..3e0a42c5 100644 --- a/crates/device/src/arranger/arranger_model.rs +++ b/crates/device/src/arranger/arranger_model.rs @@ -1,9 +1,9 @@ use crate::*; -has!(Jack: |self: Arrangement|self.jack); +has!(Jack<'static>: |self: Arrangement|self.jack); has!(Clock: |self: Arrangement|self.clock); has!(Selection: |self: Arrangement|self.selection); -has!(Vec: |self: Arrangement|self.midi_ins); -has!(Vec: |self: Arrangement|self.midi_outs); +has!(Vec: |self: Arrangement|self.midi_ins); +has!(Vec: |self: Arrangement|self.midi_outs); has!(Vec: |self: Arrangement|self.scenes); has!(Vec: |self: Arrangement|self.tracks); has!(Measure: |self: Arrangement|self.size); @@ -15,24 +15,24 @@ maybe_has!(Scene: |self: Arrangement| { Has::::get(self).track().map(|index|Has::>::get(self).get(index)).flatten() }; { Has::::get(self).track().map(|index|Has::>::get_mut(self).get_mut(index)).flatten() }); from_dsl!(MidiInputCommand: |state: Arrangement, iter|state.selected_midi_in().as_ref() - .map(|t|FromDsl::take_from(t, iter)).transpose().map(|x|x.flatten())); + .map(|t|Namespace::take_from(t, iter)).transpose().map(|x|x.flatten())); from_dsl!(MidiOutputCommand: |state: Arrangement, iter|state.selected_midi_out().as_ref() - .map(|t|FromDsl::take_from(t, iter)).transpose().map(|x|x.flatten())); -from_dsl!(DeviceCommand: |state: Arrangement, iter|state.selected_device().as_ref() - .map(|t|FromDsl::take_from(t, iter)).transpose().map(|x|x.flatten())); -from_dsl!(TrackCommand: |state: Arrangement, iter|state.selected_track().as_ref() - .map(|t|FromDsl::take_from(t, iter)).transpose().map(|x|x.flatten())); -from_dsl!(SceneCommand: |state: Arrangement, iter|state.selected_scene().as_ref() - .map(|t|FromDsl::take_from(t, iter)).transpose().map(|x|x.flatten())); -from_dsl!(ClipCommand: |state: Arrangement, iter|state.selected_clip().as_ref() - .map(|t|FromDsl::take_from(t, iter)).transpose().map(|x|x.flatten())); + .map(|t|Namespace::take_from(t, iter)).transpose().map(|x|x.flatten())); +from_dsl!(DeviceCommand:|state: Arrangement, iter|state.selected_device().as_ref() + .map(|t|Namespace::take_from(t, iter)).transpose().map(|x|x.flatten())); +from_dsl!(TrackCommand: |state: Arrangement, iter|state.selected_track().as_ref() + .map(|t|Namespace::take_from(t, iter)).transpose().map(|x|x.flatten())); +from_dsl!(SceneCommand: |state: Arrangement, iter|state.selected_scene().as_ref() + .map(|t|Namespace::take_from(t, iter)).transpose().map(|x|x.flatten())); +from_dsl!(ClipCommand: |state: Arrangement, iter|state.selected_clip().as_ref() + .map(|t|Namespace::take_from(t, iter)).transpose().map(|x|x.flatten())); #[tengri_proc::expose] impl Arrangement { fn selected_midi_in (&self) -> Option { todo!() } fn selected_midi_out (&self) -> Option { todo!() } - fn selected_device (&self) -> Option { todo!() } - fn selected_track (&self) -> Option { todo!() } - fn selected_scene (&self) -> Option { todo!() } - fn selected_clip (&self) -> Option { todo!() } + fn selected_device (&self) -> Option { todo!() } + fn selected_track (&self) -> Option { todo!() } + fn selected_scene (&self) -> Option { todo!() } + fn selected_clip (&self) -> Option { todo!() } fn _todo_usize_stub_ (&self) -> usize { todo!() } fn _todo_arc_str_stub_ (&self) -> Arc { todo!() } fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } @@ -47,7 +47,7 @@ from_dsl!(ClipCommand: |state: Arrangement, iter|state.selected_clip().as_ /// Base color. pub color: ItemTheme, /// Jack client handle - pub jack: Jack, + pub jack: Jack<'static>, /// Source of time pub clock: Clock, /// Allows one MIDI clip to be edited @@ -152,64 +152,6 @@ impl Arrangement { clip.write().unwrap().toggle_loop() } } - /// Add multiple tracks - pub fn tracks_add ( - &mut self, - count: usize, - width: Option, - mins: &[PortConnect], - mouts: &[PortConnect], - ) -> Usually<()> { - let jack = self.jack().clone(); - let track_color_1 = ItemColor::random(); - let track_color_2 = ItemColor::random(); - for i in 0..count { - let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into(); - let mut track = self.track_add(None, Some(color), mins, mouts)?.1; - if let Some(width) = width { - track.width = width; - } - } - Ok(()) - } - - /// Add a track - pub fn track_add ( - &mut self, - name: Option<&str>, - color: Option, - mins: &[PortConnect], - mouts: &[PortConnect], - ) -> Usually<(usize, &mut Track)> { - let name: Arc = name.map_or_else( - ||format!("trk{:02}", self.track_last).into(), - |x|x.to_string().into() - ); - self.track_last += 1; - let mut track = Track { - width: (name.len() + 2).max(12), - color: color.unwrap_or_else(ItemTheme::random), - sequencer: Sequencer::new( - &format!("{name}"), - self.jack(), - Some(self.clock()), - None, - mins, - mouts - )?, - name, - ..Default::default() - }; - self.tracks_mut().push(track); - let len = self.tracks().len(); - let index = len - 1; - for scene in self.scenes_mut().iter_mut() { - while scene.clips.len() < len { - scene.clips.push(None); - } - } - Ok((index, &mut self.tracks_mut()[index])) - } } #[cfg(feature = "sampler")] diff --git a/crates/device/src/arranger/arranger_tracks.rs b/crates/device/src/arranger/arranger_tracks.rs index aeb7ed91..71a9e3ed 100644 --- a/crates/device/src/arranger/arranger_tracks.rs +++ b/crates/device/src/arranger/arranger_tracks.rs @@ -1,5 +1,62 @@ use crate::*; +impl Arrangement { + /// Add multiple tracks + pub fn tracks_add ( + &mut self, + count: usize, width: Option, + mins: &[Connect], mouts: &[Connect], + ) -> Usually<()> { + let jack = self.jack().clone(); + let track_color_1 = ItemColor::random(); + let track_color_2 = ItemColor::random(); + for i in 0..count { + let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into(); + let mut track = self.track_add(None, Some(color), mins, mouts)?.1; + if let Some(width) = width { + track.width = width; + } + } + Ok(()) + } + + /// Add a track + pub fn track_add ( + &mut self, + name: Option<&str>, color: Option, + mins: &[Connect], mouts: &[Connect], + ) -> Usually<(usize, &mut Track)> { + let name: Arc = name.map_or_else( + ||format!("trk{:02}", self.track_last).into(), + |x|x.to_string().into() + ); + self.track_last += 1; + let mut track = Track { + width: (name.len() + 2).max(12), + color: color.unwrap_or_else(ItemTheme::random), + sequencer: Sequencer::new( + &format!("{name}"), + self.jack(), + Some(self.clock()), + None, + mins, + mouts + )?, + name, + ..Default::default() + }; + self.tracks_mut().push(track); + let len = self.tracks().len(); + let index = len - 1; + for scene in self.scenes_mut().iter_mut() { + while scene.clips.len() < len { + scene.clips.push(None); + } + } + Ok((index, &mut self.tracks_mut()[index])) + } +} + impl> + Send + Sync> HasTracks for T {} pub trait HasTracks: Has> + Send + Sync { @@ -132,11 +189,11 @@ impl Track { pub fn new ( name: &impl AsRef, color: Option, - jack: &Jack, + jack: &Jack<'static>, clock: Option<&Clock>, clip: Option<&Arc>>, - midi_from: &[PortConnect], - midi_to: &[PortConnect], + midi_from: &[Connect], + midi_to: &[Connect], ) -> Usually { Ok(Self { name: name.as_ref().into(), @@ -156,19 +213,19 @@ impl Track { pub fn new_with_sampler ( name: &impl AsRef, color: Option, - jack: &Jack, + jack: &Jack<'static>, clock: Option<&Clock>, clip: Option<&Arc>>, - midi_from: &[PortConnect], - midi_to: &[PortConnect], - audio_from: &[&[PortConnect];2], - audio_to: &[&[PortConnect];2], + midi_from: &[Connect], + midi_to: &[Connect], + audio_from: &[&[Connect];2], + audio_to: &[&[Connect];2], ) -> Usually { let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?; track.devices.push(Device::Sampler(Sampler::new( jack, &format!("{}/sampler", name.as_ref()), - &[PortConnect::exact(format!("{}:{}", + &[Connect::exact(format!("{}:{}", jack.with_client(|c|c.name().to_string()), track.sequencer.midi_outs[0].name() ))], @@ -284,10 +341,10 @@ pub(crate) fn io_ports <'a, T: PortsSizes<'a>> ( ) -> impl Content + 'a { Map::new(iter, move|( index, name, connections, y, y2 - ): (usize, &'a Arc, &'a [PortConnect], usize, usize), _| + ): (usize, &'a Arc, &'a [Connect], usize, usize), _| map_south(y as u16, (y2-y) as u16, Bsp::s( Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(" 󰣲 ", name))))), - Map::new(||connections.iter(), move|connect: &'a PortConnect, index|map_south(index as u16, 1, + Map::new(||connections.iter(), move|connect: &'a Connect, index|map_south(index as u16, 1, Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, &connect.info))))))))) } @@ -297,7 +354,7 @@ pub(crate) fn io_conns <'a, T: PortsSizes<'a>> ( ) -> impl Content + 'a { Map::new(iter, move|( index, name, connections, y, y2 - ): (usize, &'a Arc, &'a [PortConnect], usize, usize), _| + ): (usize, &'a Arc, &'a [Connect], usize, usize), _| map_south(y as u16, (y2-y) as u16, Bsp::s( Fill::x(Tui::bold(true, wrap(bg, fg, Fill::x(Align::w("▞▞▞▞ ▞▞▞▞"))))), Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1, diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index 5e7bd877..7a5cc937 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -39,7 +39,7 @@ impl Arrangement { pub fn view_outputs <'a> (&'a self, theme: ItemTheme) -> impl Content + 'a { let mut h = 1; for output in self.midi_outs().iter() { - h += 1 + output.conn().len(); + h += 1 + output.connections.len(); } let h = h as u16; let list = Bsp::s( @@ -50,8 +50,8 @@ impl Arrangement { Align::w(Bsp::e(" ● ", Tui::fg(Rgb(255,255,255),Tui::bold(true, port.name())))), Fill::x(Align::e(format!("{}/{} ", port.port().get_connections().len(), - port.conn().len()))))))); - for (index, conn) in port.conn().iter().enumerate() { + port.connections.len()))))))); + for (index, conn) in port.connections.iter().enumerate() { add(&Fixed::y(1, Fill::x(Align::w(format!(" c{index:02}{}", conn.info()))))); } } @@ -72,7 +72,7 @@ impl Arrangement { Either(true, Tui::fg(Green, " ● "), " · "), Either(false, Tui::fg(Yellow, " ● "), " · "), )))); - for (index, conn) in port.conn().iter().enumerate() { + for (index, conn) in port.connections.iter().enumerate() { add(&Fixed::y(1, Fill::x(""))); } }})))}})))))) diff --git a/crates/device/src/clock/clock_model.rs b/crates/device/src/clock/clock_model.rs index ef1bff86..2eca6b99 100644 --- a/crates/device/src/clock/clock_model.rs +++ b/crates/device/src/clock/clock_model.rs @@ -43,7 +43,7 @@ impl std::fmt::Debug for Clock { } impl Clock { - pub fn new (jack: &Jack, bpm: Option) -> Usually { + pub fn new (jack: &Jack<'static>, bpm: Option) -> Usually { let (chunk, transport) = jack.with_client(|c|(c.buffer_size(), c.transport())); let timebase = Arc::new(Timebase::default()); let clock = Self { @@ -56,9 +56,9 @@ impl Clock { offset: Arc::new(Moment::zero(&timebase)), started: RwLock::new(None).into(), timebase, - midi_in: Arc::new(RwLock::new(Some(MidiInput::new(jack, "M/clock", &[])?))), - midi_out: Arc::new(RwLock::new(Some(MidiOutput::new(jack, "clock/M", &[])?))), - click_out: Arc::new(RwLock::new(Some(AudioOutput::new(jack, "click", &[])?))), + midi_in: Arc::new(RwLock::new(Some(MidiInput::new(jack, &"M/clock", &[])?))), + midi_out: Arc::new(RwLock::new(Some(MidiOutput::new(jack, &"clock/M", &[])?))), + click_out: Arc::new(RwLock::new(Some(AudioOutput::new(jack, &"click", &[])?))), }; if let Some(bpm) = bpm { clock.timebase.bpm.set(bpm); diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index a4ca5930..0a8eb346 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -24,20 +24,29 @@ pub(crate) use Color::*; mod device; pub use self::device::*; mod dialog; pub use self::dialog::*; +/// Define a type alias for iterators of sized items (columns). +macro_rules! def_sizes_iter { + ($Type:ident => $($Item:ty),+) => { + pub trait $Type<'a> = + Iterator + Send + Sync + 'a; + } +} + #[cfg(feature = "arranger")] mod arranger; #[cfg(feature = "arranger")] pub use self::arranger::*; #[cfg(feature = "browser")] mod browser; #[cfg(feature = "browser")] pub use self::browser::*; +#[cfg(feature = "clap")] mod clap; #[cfg(feature = "clap")] pub use self::clap::*; #[cfg(feature = "clock")] mod clock; #[cfg(feature = "clock")] pub use self::clock::*; #[cfg(feature = "editor")] mod editor; #[cfg(feature = "editor")] pub use self::editor::*; -#[cfg(feature = "pool")] mod pool; #[cfg(feature = "pool")] pub use self::pool::*; -#[cfg(feature = "sequencer")] mod sequencer; #[cfg(feature = "sequencer")] pub use self::sequencer::*; -#[cfg(feature = "sampler")] mod sampler; #[cfg(feature = "sampler")] pub use self::sampler::*; +#[cfg(feature = "lv2")] mod lv2; #[cfg(feature = "lv2")] pub use self::lv2::*; #[cfg(feature = "meter")] mod meter; #[cfg(feature = "meter")] pub use self::meter::*; #[cfg(feature = "mixer")] mod mixer; #[cfg(feature = "mixer")] pub use self::mixer::*; -#[cfg(feature = "lv2")] mod lv2; #[cfg(feature = "lv2")] pub use self::lv2::*; +#[cfg(feature = "pool")] mod pool; #[cfg(feature = "pool")] pub use self::pool::*; +#[cfg(feature = "port")] mod port; #[cfg(feature = "port")] pub use self::port::*; +#[cfg(feature = "sampler")] mod sampler; #[cfg(feature = "sampler")] pub use self::sampler::*; +#[cfg(feature = "sequencer")] mod sequencer; #[cfg(feature = "sequencer")] pub use self::sequencer::*; #[cfg(feature = "sf2")] mod sf2; #[cfg(feature = "sf2")] pub use self::sf2::*; #[cfg(feature = "vst2")] mod vst2; #[cfg(feature = "vst2")] pub use self::vst2::*; #[cfg(feature = "vst3")] mod vst3; #[cfg(feature = "vst3")] pub use self::vst3::*; -#[cfg(feature = "clap")] mod clap; #[cfg(feature = "clap")] pub use self::clap::*; pub fn button_2 <'a> ( key: impl Content + 'a, label: impl Content + 'a, editing: bool, diff --git a/crates/device/src/lv2/lv2_model.rs b/crates/device/src/lv2/lv2_model.rs index 64679f75..bbbe0663 100644 --- a/crates/device/src/lv2/lv2_model.rs +++ b/crates/device/src/lv2/lv2_model.rs @@ -4,7 +4,7 @@ use crate::*; #[derive(Debug)] pub struct Lv2 { /// JACK client handle (needs to not be dropped for standalone mode to work). - pub jack: Jack, + pub jack: Jack<'static>, pub name: Arc, pub path: Option>, pub selected: usize, @@ -26,7 +26,7 @@ pub struct Lv2 { impl Lv2 { pub fn new ( - jack: &Jack, + jack: &Jack<'static>, name: &str, uri: &str, ) -> Usually { diff --git a/crates/device/src/pool/pool_model.rs b/crates/device/src/pool/pool_model.rs index 40ac6632..4c7234ec 100644 --- a/crates/device/src/pool/pool_model.rs +++ b/crates/device/src/pool/pool_model.rs @@ -14,7 +14,7 @@ pub struct Pool { } from_dsl!(BrowserCommand: |state: Pool, iter|Ok(state.browser .as_ref() - .map(|p|FromDsl::take_from(p, iter)) + .map(|p|Namespace::take_from(p, iter)) .transpose()? .flatten())); impl Default for Pool { diff --git a/crates/device/src/port.rs b/crates/device/src/port.rs new file mode 100644 index 00000000..d8735ae1 --- /dev/null +++ b/crates/device/src/port.rs @@ -0,0 +1,8 @@ +mod port_audio_out; pub use self::port_audio_out::*; +mod port_audio_in; pub use self::port_audio_in::*; +mod port_connect; pub use self::port_connect::*; +mod port_midi_out; pub use self::port_midi_out::*; +mod port_midi_in; pub use self::port_midi_in::*; +pub(crate) use ConnectName::*; +pub(crate) use ConnectScope::*; +pub(crate) use ConnectStatus::*; diff --git a/crates/device/src/port/port_audio_in.rs b/crates/device/src/port/port_audio_in.rs new file mode 100644 index 00000000..dcec321c --- /dev/null +++ b/crates/device/src/port/port_audio_in.rs @@ -0,0 +1,44 @@ +use crate::*; + +#[derive(Debug)] pub struct AudioInput { + /// Handle to JACK client, for receiving reconnect events. + jack: Jack<'static>, + /// Port name + name: Arc, + /// Port handle. + port: Port, + /// List of ports to connect to. + pub connections: Vec, +} +has!(Jack<'static>: |self: AudioInput|self.jack); +impl JackPort for AudioInput { + type Port = AudioIn; + type Pair = AudioOut; + fn name (&self) -> &Arc { + &self.name + } + fn port (&self) -> &Port { + &self.port + } + fn port_mut (&mut self) -> &mut Port { + &mut self.port + } + fn into_port (self) -> Port { + self.port + } + fn connections (&self) -> &[Connect] { + self.connections.as_slice() + } + fn new (jack: &Jack<'static>, name: &impl AsRef, connect: &[Connect]) + -> Usually where Self: Sized + { + let port = Self { + port: Self::register(jack, name)?, + jack: jack.clone(), + name: name.as_ref().into(), + connections: connect.to_vec() + }; + port.connect_to_matching()?; + Ok(port) + } +} diff --git a/crates/device/src/port/port_audio_out.rs b/crates/device/src/port/port_audio_out.rs new file mode 100644 index 00000000..9984cf13 --- /dev/null +++ b/crates/device/src/port/port_audio_out.rs @@ -0,0 +1,44 @@ +use crate::*; + +#[derive(Debug)] pub struct AudioOutput { + /// Handle to JACK client, for receiving reconnect events. + jack: Jack<'static>, + /// Port name + name: Arc, + /// Port handle. + port: Port, + /// List of ports to connect to. + pub connections: Vec, +} +has!(Jack<'static>: |self: AudioOutput|self.jack); +impl JackPort for AudioOutput { + type Port = AudioOut; + type Pair = AudioIn; + fn name (&self) -> &Arc { + &self.name + } + fn port (&self) -> &Port { + &self.port + } + fn port_mut (&mut self) -> &mut Port { + &mut self.port + } + fn into_port (self) -> Port { + self.port + } + fn connections (&self) -> &[Connect] { + self.connections.as_slice() + } + fn new (jack: &Jack<'static>, name: &impl AsRef, connect: &[Connect]) + -> Usually where Self: Sized + { + let port = Self { + port: Self::register(jack, name)?, + jack: jack.clone(), + name: name.as_ref().into(), + connections: connect.to_vec() + }; + port.connect_to_matching()?; + Ok(port) + } +} diff --git a/crates/engine/src/jack/jack_port.rs b/crates/device/src/port/port_connect.rs similarity index 67% rename from crates/engine/src/jack/jack_port.rs rename to crates/device/src/port/port_connect.rs index 13e7aefd..cd0a9192 100644 --- a/crates/engine/src/jack/jack_port.rs +++ b/crates/device/src/port/port_connect.rs @@ -1,28 +1,22 @@ use crate::*; -use super::*; -pub trait JackPort<'j>: HasJack<'j> { - type Port: PortSpec; - type Pair: PortSpec; - fn port (&self) -> &Port; -} - -pub trait ConnectTo<'j, T>: JackPort<'j> { - fn connect_to (&'j self, to: &T) -> Usually; -} - -#[macro_export] macro_rules! connect_to { - (<$lt:lifetime>|$self:ident:$Self:ty, $port:ident:$Port:ty|$expr:expr) => { - impl<$lt> ConnectTo<$lt, &$Port> for $Self { - fn connect_to (&$self, $port: &$Port) -> Usually { - $expr - } - } - }; -} - -pub trait ConnectAuto<'j>: JackPort<'j> + ConnectTo<'j, &'j Port> { +pub trait JackPort: HasJack<'static> { + type Port: PortSpec + Default; + type Pair: PortSpec + Default; + fn new (jack: &Jack<'static>, name: &impl AsRef, connect: &[Connect]) + -> Usually where Self: Sized; + fn register (jack: &Jack<'static>, name: &impl AsRef) -> Usually> { + Ok(jack.register_port::(name.as_ref())?) + } + fn name (&self) -> &Arc; fn connections (&self) -> &[Connect]; + fn port (&self) -> &Port; + fn port_mut (&mut self) -> &mut Port; + fn into_port (self) -> Port where Self: Sized; + fn close (self) -> Usually<()> where Self: Sized { + let jack = self.jack().clone(); + Ok(jack.with_client(|c|c.unregister_port(self.into_port()))?) + } fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec { self.with_client(|c|c.ports(re_name, re_type, flags)) } @@ -32,7 +26,7 @@ pub trait ConnectAuto<'j>: JackPort<'j> + ConnectTo<'j, &'j Port> { fn port_by_name (&self, name: impl AsRef) -> Option> { self.with_client(|c|c.port_by_name(name.as_ref())) } - fn connect_to_matching (&self) -> Usually<()> { + fn connect_to_matching <'k> (&'k self) -> Usually<()> { for connect in self.connections().iter() { //panic!("{connect:?}"); let status = match &connect.name { @@ -43,15 +37,15 @@ pub trait ConnectAuto<'j>: JackPort<'j> + ConnectTo<'j, &'j Port> { } Ok(()) } - fn connect_exact (&self, name: &str) -> + fn connect_exact <'k> (&'k self, name: &str) -> Usually, Arc, ConnectStatus)>> { - self.with_client(|c|{ + self.with_client(move|c|{ let mut status = vec![]; for port in c.ports(None, None, PortFlags::empty()).iter() { if port.as_str() == &*name { if let Some(port) = c.port_by_name(port.as_str()) { - let port_status = self.connect_to(&port)?; + let port_status = self.connect_to_unowned(&port)?; let name = port.name()?.into(); status.push((port, name, port_status)); if port_status == Connected { @@ -63,15 +57,15 @@ pub trait ConnectAuto<'j>: JackPort<'j> + ConnectTo<'j, &'j Port> { Ok(status) }) } - fn connect_regexp ( - &self, re: &str, scope: ConnectScope + fn connect_regexp <'k> ( + &'k self, re: &str, scope: ConnectScope ) -> Usually, Arc, ConnectStatus)>> { - self.with_client(|c|{ + self.with_client(move|c|{ let mut status = vec![]; let ports = c.ports(Some(&re), None, PortFlags::empty()); for port in ports.iter() { if let Some(port) = c.port_by_name(port.as_str()) { - let port_status = self.connect_to(&port)?; + let port_status = self.connect_to_unowned(&port)?; let name = port.name()?.into(); status.push((port, name, port_status)); if port_status == Connected && scope == One { @@ -82,6 +76,34 @@ pub trait ConnectAuto<'j>: JackPort<'j> + ConnectTo<'j, &'j Port> { Ok(status) }) } + /** Connect to a matching port by name. */ + fn connect_to_name (&self, name: impl AsRef) -> Usually { + self.with_client(|c|if let Some(ref port) = c.port_by_name(name.as_ref()) { + self.connect_to_unowned(port) + } else { + Ok(Missing) + }) + } + /** Connect to a matching port by reference. */ + fn connect_to_unowned (&self, port: &Port) -> Usually { + self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) { + Connected + } else if let Ok(_) = c.connect_ports(port, self.port()) { + Connected + } else { + Mismatch + })) + } + /** Connect to an owned matching port by reference. */ + fn connect_to_owned (&self, port: &Port) -> Usually { + self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) { + Connected + } else if let Ok(_) = c.connect_ports(port, self.port()) { + Connected + } else { + Mismatch + })) + } } #[derive(Clone, Debug, PartialEq)] diff --git a/crates/engine/src/midi/midi_in.rs b/crates/device/src/port/port_midi_in.rs similarity index 50% rename from crates/engine/src/midi/midi_in.rs rename to crates/device/src/port/port_midi_in.rs index dab3d026..d3a3d91b 100644 --- a/crates/engine/src/midi/midi_in.rs +++ b/crates/device/src/port/port_midi_in.rs @@ -2,99 +2,56 @@ use crate::*; //impl_port!(MidiInput: MidiOut -> MidiIn |j, n|j.register_port::(n)); -#[derive(Debug)] pub struct MidiInput<'j> { +#[derive(Debug)] pub struct MidiInput { /// Handle to JACK client, for receiving reconnect events. - jack: Jack<'j>, + jack: Jack<'static>, /// Port name name: Arc, /// Port handle. - port: Port, + port: Port, + /// List of currently held notes. + held: Arc>, /// List of ports to connect to. - connections: Vec + pub connections: Vec, } -impl<'j> AsRef> for MidiInput<'j> { - fn as_ref (&self) -> &Port { &self.port } -} -impl<'j> MidiInput<'j> { - pub fn new (jack: &Jack, name: impl AsRef, connect: &[Connect]) - -> Usually +has!(Jack<'static>: |self: MidiInput|self.jack); +impl JackPort for MidiInput { + type Port = MidiIn; + type Pair = MidiOut; + fn name (&self) -> &Arc { + &self.name + } + fn port (&self) -> &Port { + &self.port + } + fn port_mut (&mut self) -> &mut Port { + &mut self.port + } + fn into_port (self) -> Port { + self.port + } + fn connections (&self) -> &[Connect] { + self.connections.as_slice() + } + fn new (jack: &Jack<'static>, name: &impl AsRef, connect: &[Connect]) + -> Usually where Self: Sized { let port = Self { - port: jack.register_port::(name.as_ref())?, - jack, - name: name.into(), - connections: connect.to_vec() + port: Self::register(jack, name)?, + jack: jack.clone(), + name: name.as_ref().into(), + connections: connect.to_vec(), + held: Arc::new(RwLock::new([false;128])) }; port.connect_to_matching()?; Ok(port) } - pub fn name (&self) -> &Arc { - &self.name - } - pub fn port (&self) -> &Port { - &self.port - } - pub fn port_mut (&mut self) -> &mut Port { - &mut self.port - } - pub fn into_port (self) -> Port { - self.port - } - pub fn close (self) -> Usually<()> { - let Self { jack, port, .. } = self; - Ok(jack.with_client(|client|client.unregister_port(port))?) - } +} +impl MidiInput { pub fn parsed <'a> (&'a self, scope: &'a ProcessScope) -> impl Iterator, &'a [u8])> { parse_midi_input(self.port().iter(scope)) } } -impl<'j> HasJack<'j> for MidiInput<'j> { - fn jack (&self) -> &'j Jack<'j> { &self.jack } -} -impl<'j> JackPort<'j> for MidiInput<'j> { - type Port = MidiIn; - type Pair = MidiOut; - fn port (&self) -> &Port { &self.port } -} -//impl<'j, T: AsRef> ConnectTo<'j, T> for MidiInput<'j> { - //fn connect_to (&self, to: &T) -> Usually { - //self.with_client(|c|if let Some(ref port) = c.port_by_name(to.as_ref()) { - //self.connect_to(port) - //} else { - //Ok(Missing) - //}) - //} -//} -connect_to!(<'j>|self: MidiInput<'j>, port: &str|{ - self.with_client(|c|if let Some(ref port) = c.port_by_name(port.as_ref()) { - self.connect_to(port) - } else { - Ok(Missing) - }) -}); -connect_to!(<'j>|self: MidiInput<'j>, port: Port|{ - self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) { - Connected - } else if let Ok(_) = c.connect_ports(port, &self.port) { - Connected - } else { - Mismatch - })) -}); -connect_to!(<'j>|self: MidiInput<'j>, port: Port|{ - self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) { - Connected - } else if let Ok(_) = c.connect_ports(port, &self.port) { - Connected - } else { - Mismatch - })) -}); -impl<'j> ConnectAuto<'j> for MidiInput<'j> { - fn connections (&self) -> &[Connect] { - &self.connections - } -} #[tengri_proc::command(MidiInput)] impl MidiInputCommand { @@ -127,8 +84,8 @@ pub trait HasMidiIns { { let mut y = 0; self.midi_ins().iter().enumerate().map(move|(i, input)|{ - let height = 1 + input.conn().len(); - let data = (i, input.name(), input.conn(), y, y + height); + let height = 1 + input.connections().len(); + let data = (i, input.name(), input.connections(), y, y + height); y += height; data }) @@ -137,7 +94,7 @@ pub trait HasMidiIns { pub type CollectedMidiInput<'a> = Vec, MidiError>)>>; -impl<'j, T: HasMidiIns + HasJack<'j>> AddMidiIn for T { +impl> AddMidiIn for T { fn midi_in_add (&mut self) -> Usually<()> { let index = self.midi_ins().len(); let port = MidiInput::new(self.jack(), &format!("M/{index}"), &[])?; diff --git a/crates/engine/src/midi/midi_out.rs b/crates/device/src/port/port_midi_out.rs similarity index 65% rename from crates/engine/src/midi/midi_out.rs rename to crates/device/src/port/port_midi_out.rs index 5a0fba73..ecfc2e0b 100644 --- a/crates/engine/src/midi/midi_out.rs +++ b/crates/device/src/port/port_midi_out.rs @@ -8,7 +8,7 @@ use crate::*; /// Port handle. port: Port, /// List of ports to connect to. - conn: Vec, + pub connections: Vec, /// List of currently held notes. held: Arc>, /// Buffer @@ -16,22 +16,37 @@ use crate::*; /// Buffer output_buffer: Vec>>, } - has!(Jack<'static>: |self: MidiOutput|self.jack); - -impl MidiOutput { - pub fn new (jack: &Jack, name: impl AsRef, connect: &[Connect]) - -> Usually +impl JackPort for MidiOutput { + type Port = MidiOut; + type Pair = MidiIn; + fn name (&self) -> &Arc { + &self.name + } + fn port (&self) -> &Port { + &self.port + } + fn port_mut (&mut self) -> &mut Port { + &mut self.port + } + fn into_port (self) -> Port { + self.port + } + fn connections (&self) -> &[Connect] { + self.connections.as_slice() + } + fn new (jack: &Jack<'static>, name: &impl AsRef, connect: &[Connect]) + -> Usually where Self: Sized { + let port = Self::register(jack, name)?; let jack = jack.clone(); - let port = jack.register_port::(name.as_ref())?; let name = name.as_ref().into(); - let conn = connect.to_vec(); + let connections = connect.to_vec(); let port = Self { jack, port, name, - conn, + connections, held: Arc::new([false;128].into()), note_buffer: vec![0;8], output_buffer: vec![vec![];65536], @@ -39,22 +54,8 @@ impl MidiOutput { port.connect_to_matching()?; Ok(port) } - pub fn name (&self) -> &Arc { - &self.name - } - pub fn port (&self) -> &Port { - &self.port - } - pub fn port_mut (&mut self) -> &mut Port { - &mut self.port - } - pub fn into_port (self) -> Port { - self.port - } - pub fn close (self) -> Usually<()> { - let Self { jack, port, .. } = self; - Ok(jack.with_client(|client|client.unregister_port(port))?) - } +} +impl MidiOutput { /// Clear the section of the output buffer that we will be using, /// emitting "all notes off" at start of buffer if requested. pub fn buffer_clear (&mut self, scope: &ProcessScope, reset: bool) { @@ -94,58 +95,6 @@ impl MidiOutput { } } -impl AsRef> for MidiOutput { - fn as_ref (&self) -> &Port { - &self.port - } -} - -impl JackPort<'static> for MidiOutput { - type Port = MidiOut; - type Pair = MidiIn; - fn port (&self) -> &Port { &self.port } -} - -impl ConnectTo<'static, &str> for MidiOutput { - fn connect_to (&self, to: &str) -> Usually { - self.with_client(|c|if let Some(ref port) = c.port_by_name(to.as_ref()) { - self.connect_to(port) - } else { - Ok(Missing) - }) - } -} - -impl ConnectTo<'static, &Port> for MidiOutput { - fn connect_to (&self, port: &Port) -> Usually { - self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) { - Connected - } else if let Ok(_) = c.connect_ports(port, &self.port) { - Connected - } else { - Mismatch - })) - } -} - -impl ConnectTo<'static, &Port> for MidiOutput { - fn connect_to (&self, port: &Port) -> Usually { - self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) { - Connected - } else if let Ok(_) = c.connect_ports(port, &self.port) { - Connected - } else { - Mismatch - })) - } -} - -impl ConnectAuto<'static> for MidiOutput { - fn connections (&self) -> &[Connect] { - &self.conn - } -} - #[tengri_proc::command(MidiOutput)] impl MidiOutputCommand { fn _todo_ (_port: &mut MidiOutput) -> Perhaps { Ok(None) } @@ -170,8 +119,8 @@ pub trait HasMidiOuts { { 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); + let height = 1 + output.connections().len(); + let data = (i, output.name(), output.connections(), y, y + height); y += height; data }) @@ -184,7 +133,7 @@ pub trait HasMidiOuts { } /// Trail for thing that may gain new MIDI ports. -impl<'j, T: HasMidiOuts + HasJack<'j>> AddMidiOut for T { +impl> AddMidiOut for T { fn midi_out_add (&mut self) -> Usually<()> { let index = self.midi_outs().len(); let port = MidiOutput::new(self.jack(), &format!("{index}/M"), &[])?; diff --git a/crates/device/src/sampler/sampler_model.rs b/crates/device/src/sampler/sampler_model.rs index 937d4208..3983e1c0 100644 --- a/crates/device/src/sampler/sampler_model.rs +++ b/crates/device/src/sampler/sampler_model.rs @@ -49,16 +49,16 @@ pub struct Sampler { impl Sampler { pub fn new ( - jack: &Jack, + jack: &Jack<'static>, name: impl AsRef, - midi_from: &[PortConnect], - audio_from: &[&[PortConnect];2], - audio_to: &[&[PortConnect];2], + midi_from: &[Connect], + audio_from: &[&[Connect];2], + audio_to: &[&[Connect];2], ) -> Usually { let name = name.as_ref(); Ok(Self { name: name.into(), - midi_in: MidiInput::new(jack, format!("M/{name}"), midi_from)?, + midi_in: MidiInput::new(jack, &format!("M/{name}"), midi_from)?, audio_ins: vec![ AudioInput::new(jack, &format!("L/{name}"), audio_from[0])?, AudioInput::new(jack, &format!("R/{name}"), audio_from[1])?, diff --git a/crates/device/src/sequencer/seq_model.rs b/crates/device/src/sequencer/seq_model.rs index 3b411708..8b92885b 100644 --- a/crates/device/src/sequencer/seq_model.rs +++ b/crates/device/src/sequencer/seq_model.rs @@ -70,17 +70,17 @@ impl Default for Sequencer { impl Sequencer { pub fn new ( name: impl AsRef, - jack: &Jack, + jack: &Jack<'static>, clock: Option<&Clock>, clip: Option<&Arc>>, - midi_from: &[PortConnect], - midi_to: &[PortConnect], + midi_from: &[Connect], + midi_to: &[Connect], ) -> Usually { let _name = name.as_ref(); let clock = clock.cloned().unwrap_or_default(); Ok(Self { - midi_ins: vec![MidiInput::new(jack, format!("M/{}", name.as_ref()), midi_from)?,], - midi_outs: vec![MidiOutput::new(jack, format!("{}/M", name.as_ref()), midi_to)?, ], + midi_ins: vec![MidiInput::new(jack, &format!("M/{}", name.as_ref()), midi_from)?,], + midi_outs: vec![MidiOutput::new(jack, &format!("{}/M", name.as_ref()), midi_to)?, ], play_clip: clip.map(|clip|(Moment::zero(&clock.timebase), Some(clip.clone()))), clock, reset: true, @@ -101,9 +101,9 @@ impl std::fmt::Debug for Sequencer { } } -has!(Clock: |self: Sequencer|self.clock); -has!(Vec: |self:Sequencer| self.midi_ins); -has!(Vec: |self:Sequencer| self.midi_outs); +has!(Clock: |self:Sequencer|self.clock); +has!(Vec: |self:Sequencer|self.midi_ins); +has!(Vec: |self:Sequencer|self.midi_outs); impl MidiMonitor for Sequencer { fn notes_in (&self) -> &Arc> { diff --git a/crates/engine/src/audio.rs b/crates/engine/src/audio.rs deleted file mode 100644 index 565da268..00000000 --- a/crates/engine/src/audio.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod audio_in; pub use self::audio_in::*; -mod audio_out; pub use self::audio_out::*; diff --git a/crates/engine/src/audio/audio_in.rs b/crates/engine/src/audio/audio_in.rs deleted file mode 100644 index 771e53d2..00000000 --- a/crates/engine/src/audio/audio_in.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::*; - -//impl_port!(AudioInput: AudioOut -> AudioIn |j, n|j.register_port::(n)); - -#[derive(Debug)] pub struct AudioInput<'j> { - /// Handle to JACK client, for receiving reconnect events. - jack: Jack<'j>, - /// Port name - name: Arc, - /// Port handle. - port: Port, - /// List of ports to connect to. - connections: Vec -} -impl<'j> AsRef> for AudioInput<'j> { - fn as_ref (&self) -> &Port { &self.port } -} -impl<'j> AudioInput<'j> { - pub fn new (jack: &Jack, name: impl AsRef, connect: &[PortConnect]) - -> Usually - { - let port = Self { - port: jack.register_port::(name.as_ref())?, - jack, - name: name.into(), - connections: connect.to_vec() - }; - port.connect_to_matching()?; - Ok(port) - } - pub fn name (&self) -> &Arc { &self.name } - pub fn port (&self) -> &Port { &self.port } - pub fn port_mut (&mut self) -> &mut Port { &mut self.port } - pub fn into_port (self) -> Port { self.port } - pub fn close (self) -> Usually<()> { - let Self { jack, port, .. } = self; - Ok(jack.with_client(|client|client.unregister_port(port))?) - } -} -impl<'j> HasJack<'j> for AudioInput<'j> { - fn jack (&self) -> &'j Jack<'j> { &self.jack } -} -impl<'j> JackPort<'j> for AudioInput<'j> { - type Port = AudioIn; - type Pair = AudioOut; - fn port (&self) -> &Port { &self.port } -} -//impl<'j, T: AsRef> ConnectTo<'j, T> for AudioInput<'j> { - //fn connect_to (&self, to: &T) -> Usually { - //self.with_client(|c|if let Some(ref port) = c.port_by_name(to.as_ref()) { - //self.connect_to(port) - //} else { - //Ok(Missing) - //}) - //} -//} -connect_to!(<'j>|self: AudioInput<'j>, port: &str|{ - self.with_client(|c|if let Some(ref port) = c.port_by_name(port.as_ref()) { - self.connect_to(port) - } else { - Ok(Missing) - }) -}); -connect_to!(<'j>|self: AudioInput<'j>, port: Port|{ - self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) { - Connected - } else if let Ok(_) = c.connect_ports(port, &self.port) { - Connected - } else { - Mismatch - })) -}); -connect_to!(<'j>|self: AudioInput<'j>, port: Port|{ - self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) { - Connected - } else if let Ok(_) = c.connect_ports(port, &self.port) { - Connected - } else { - Mismatch - })) -}); -impl<'j> ConnectAuto<'j> for AudioInput<'j> { - fn connections (&self) -> &[PortConnect] { - &self.connections - } -} diff --git a/crates/engine/src/audio/audio_out.rs b/crates/engine/src/audio/audio_out.rs deleted file mode 100644 index 35e3c6c8..00000000 --- a/crates/engine/src/audio/audio_out.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::*; - -//impl_port!(AudioOutput: AudioOut -> AudioIn |j, n|j.register_port::(n)); - -#[derive(Debug)] pub struct AudioOutput<'j> { - /// Handle to JACK client, for receiving reconnect events. - jack: Jack<'j>, - /// Port name - name: Arc, - /// Port handle. - port: Port, - /// List of ports to connect to. - connections: Vec -} -impl<'j> AsRef> for AudioOutput<'j> { - fn as_ref (&self) -> &Port { &self.port } -} -impl<'j> AudioOutput<'j> { - pub fn new (jack: &Jack, name: impl AsRef, connect: &[PortConnect]) - -> Usually - { - let port = Self { - port: jack.register_port::(name.as_ref())?, - jack, - name: name.into(), - connections: connect.to_vec() - }; - port.connect_to_matching()?; - Ok(port) - } - pub fn name (&self) -> &Arc { &self.name } - pub fn port (&self) -> &Port { &self.port } - pub fn port_mut (&mut self) -> &mut Port { &mut self.port } - pub fn into_port (self) -> Port { self.port } - pub fn close (self) -> Usually<()> { - let Self { jack, port, .. } = self; - Ok(jack.with_client(|client|client.unregister_port(port))?) - } -} -impl<'j> HasJack<'j> for AudioOutput<'j> { - fn jack (&self) -> &'j Jack<'j> { &self.jack } -} -impl<'j> JackPort<'j> for AudioOutput<'j> { - type Port = AudioOut; - type Pair = AudioIn; - fn port (&self) -> &Port { &self.port } -} -connect_to!(<'j>|self: AudioOutput<'j>, port: &str|{ - self.with_client(|c|if let Some(ref port) = c.port_by_name(port.as_ref()) { - self.connect_to(port) - } else { - Ok(Missing) - }) -}); -connect_to!(<'j>|self: AudioOutput<'j>, port: Port|{ - self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) { - Connected - } else if let Ok(_) = c.connect_ports(port, &self.port) { - Connected - } else { - Mismatch - })) -}); -connect_to!(<'j>|self: AudioOutput<'j>, port: Port|{ - self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) { - Connected - } else if let Ok(_) = c.connect_ports(port, &self.port) { - Connected - } else { - Mismatch - })) -}); -impl<'j> ConnectAuto<'j> for AudioOutput<'j> { - fn connections (&self) -> &[PortConnect] { - &self.connections - } -} diff --git a/crates/engine/src/jack.rs b/crates/engine/src/jack.rs index 0f3e9dd8..1691e13d 100644 --- a/crates/engine/src/jack.rs +++ b/crates/engine/src/jack.rs @@ -1,18 +1,225 @@ +use crate::*; pub use ::jack::{*, contrib::{*, ClosureProcessHandler}}; - //contrib::ClosureProcessHandler, - //NotificationHandler, - //Client, AsyncClient, ClientOptions, ClientStatus, - //ProcessScope, Control, Frames, - //Port, PortId, PortSpec, PortFlags, - //Unowned, MidiIn, MidiOut, AudioIn, AudioOut, -//}; - -pub(crate) use ConnectName::*; -pub(crate) use ConnectScope::*; -pub(crate) use ConnectStatus::*; pub(crate) use std::sync::{Arc, RwLock}; +/// Trait for thing that has a JACK process callback. +pub trait Audio { + fn handle (&mut self, _event: JackEvent) {} + fn process (&mut self, _: &Client, _: &ProcessScope) -> Control { + Control::Continue + } + fn callback ( + state: &Arc>, client: &Client, scope: &ProcessScope + ) -> Control where Self: Sized { + if let Ok(mut state) = state.write() { + state.process(client, scope) + } else { + Control::Quit + } + } +} -mod jack_client; pub use self::jack_client::*; -mod jack_device; pub use self::jack_device::*; -mod jack_handler; pub use self::jack_handler::*; -mod jack_port; pub use self::jack_port::*; +/// Implement [Audio]: provide JACK callbacks. +#[macro_export] macro_rules! audio { + (| + $self1:ident: + $Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident + |$cb:expr$(;|$self2:ident,$e:ident|$cb2:expr)?) => { + impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? { + #[inline] fn process (&mut $self1, $c: &Client, $s: &ProcessScope) -> Control { $cb } + $(#[inline] fn handle (&mut $self2, $e: JackEvent) { $cb2 })? + } + } +} + +/// Event enum for JACK events. +#[derive(Debug, Clone, PartialEq)] pub enum JackEvent { + ThreadInit, + Shutdown(ClientStatus, Arc), + Freewheel(bool), + SampleRate(Frames), + ClientRegistration(Arc, bool), + PortRegistration(PortId, bool), + PortRename(PortId, Arc, Arc), + PortsConnected(PortId, PortId, bool), + 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); + } + unsafe fn shutdown(&mut self, status: ClientStatus, reason: &str) { + self.0(JackEvent::Shutdown(status, reason.into())); + } + fn freewheel(&mut self, _: &Client, enabled: bool) { + self.0(JackEvent::Freewheel(enabled)); + } + fn sample_rate(&mut self, _: &Client, frames: Frames) -> Control { + self.0(JackEvent::SampleRate(frames)); + Control::Quit + } + fn client_registration(&mut self, _: &Client, name: &str, reg: bool) { + self.0(JackEvent::ClientRegistration(name.into(), reg)); + } + fn port_registration(&mut self, _: &Client, id: PortId, reg: bool) { + self.0(JackEvent::PortRegistration(id, reg)); + } + fn port_rename(&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { + self.0(JackEvent::PortRename(id, old.into(), new.into())); + Control::Continue + } + fn ports_connected(&mut self, _: &Client, a: PortId, b: PortId, are: bool) { + self.0(JackEvent::PortsConnected(a, b, are)); + } + fn graph_reorder(&mut self, _: &Client) -> Control { + self.0(JackEvent::GraphReorder); + Control::Continue + } + fn xrun(&mut self, _: &Client) -> Control { + self.0(JackEvent::XRun); + Control::Continue + } +} + +/// 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>>; +/// This is the notification handler wrapper for a boxed realtime callback. +pub type DynamicAudioHandler<'j> = + ClosureProcessHandler<(), BoxedAudioHandler<'j>>; +/// This is a boxed realtime callback. +pub type BoxedAudioHandler<'j> = + Box Control + Send + Sync + 'j>; +/// This is the notification handler wrapper for a boxed [JackEvent] callback. +pub type DynamicNotifications<'j> = + Notifications>; +/// This is a boxed [JackEvent] callback. +pub type BoxedJackEventHandler<'j> = + Box; +use self::JackState::*; + +impl<'j, T: Has>> HasJack<'j> for T { + fn jack (&self) -> &Jack<'j> { self.get() } +} +impl<'j> HasJack<'j> for Jack<'j> { + fn jack (&self) -> &Jack<'j> { self } +} +impl<'j> HasJack<'j> for &Jack<'j> { + fn jack (&self) -> &Jack<'j> { self } +} + +/// Things that can provide a [jack::Client] reference. +pub trait HasJack<'j> { + /// Return the internal [jack::Client] handle + /// that lets you call the JACK API. + fn jack (&self) -> &Jack<'j>; + /// Run the JACK thread. + fn run ( + &self, callback: impl FnOnce(&Jack)->Usually + ) -> Usually>> { + let jack = self.jack(); + let app = Arc::new(RwLock::new(callback(jack)?)); + let mut state = Activating; + std::mem::swap(&mut*jack.state.write().unwrap(), &mut state); + if let Inactive(client) = state { + let client = client.activate_async( + // This is the misc notifications handler. It's a struct that wraps a [Box] + // which performs type erasure on a callback that takes [JackEvent], which is + // one of the available misc notifications. + Notifications(Box::new({ + let app = app.clone(); + move|event|app.write().unwrap().handle(event) + }) as BoxedJackEventHandler), + // This is the main processing handler. It's a struct that wraps a [Box] + // which performs type erasure on a callback that takes [Client] and [ProcessScope] + // and passes them down to the `app`'s `process` callback, which in turn + // implements audio and MIDI input and output on a realtime basis. + ClosureProcessHandler::new(Box::new({ + let app = app.clone(); + move|c: &_, s: &_|if let Ok(mut app) = app.write() { + app.process(c, s) + } else { + Control::Quit + } + }) as BoxedAudioHandler), + )?; + *jack.state.write().unwrap() = Active(client); + } else { + unreachable!(); + } + Ok(app) + } + /// Run something with the client. + fn with_client (&self, op: impl FnOnce(&Client)->T) -> T { + match &*self.jack().state.read().unwrap() { + Inert => panic!("jack client not activated"), + Inactive(ref client) => op(client), + Activating => panic!("jack client has not finished activation"), + Active(ref client) => op(client.as_client()), + } + } + fn port_by_name (&self, name: &str) -> Option> { + self.with_client(|client|client.port_by_name(name)) + } + fn port_by_id (&self, id: u32) -> Option> { + self.with_client(|c|c.port_by_id(id)) + } + fn register_port (&self, name: impl AsRef) -> Usually> { + self.with_client(|client|Ok(client.register_port(name.as_ref(), PS::default())?)) + } + fn sync_lead (&self, enable: bool, callback: impl Fn(TimebaseInfo)->Position) -> Usually<()> { + if enable { + self.with_client(|client|match client.register_timebase_callback(false, callback) { + Ok(_) => Ok(()), + Err(e) => Err(e) + })? + } + Ok(()) + } + fn sync_follow (&self, _enable: bool) -> Usually<()> { + // TODO: sync follow + Ok(()) + } +} + +/// Wraps [JackState] and through it [jack::Client]. +#[derive(Clone, Debug, Default)] +pub struct Jack<'j> { + pub state: Arc>> +} + +impl<'j> Jack<'j> { + pub fn new (name: &str) -> Usually { + Ok(Self { + state: JackState::new(Client::new(name, ClientOptions::NO_START_SERVER)?.0) + }) + } +} + +/// 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. +#[derive(Debug, Default)] +pub enum JackState<'j> { + /// Unused + #[default] Inert, + /// Before activation. + Inactive(Client), + /// During activation. + Activating, + /// After activation. Must not be dropped for JACK thread to persist. + Active(DynamicAsyncClient<'j>), +} + +impl<'j> JackState<'j> { + fn new (client: Client) -> Arc> { + Arc::new(RwLock::new(Self::Inactive(client))) + } +} diff --git a/crates/engine/src/jack/jack_client.rs b/crates/engine/src/jack/jack_client.rs deleted file mode 100644 index abf67158..00000000 --- a/crates/engine/src/jack/jack_client.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::*; -use super::*; -use self::JackState::*; - -impl<'j, T: Has>> HasJack<'j> for T { - fn jack (&'j self) -> &'j Jack<'j> { self.get() } -} -impl<'j> HasJack<'j> for Jack<'j> { - fn jack (&'j self) -> &'j Jack<'j> { self } -} -impl<'j> HasJack<'j> for &Jack<'j> { - fn jack (&'j self) -> &'j Jack<'j> { self } -} - -/// Things that can provide a [jack::Client] reference. -pub trait HasJack<'j> { - /// Return the internal [jack::Client] handle - /// that lets you call the JACK API. - fn jack (&'j self) -> &'j Jack<'j>; - /// Run the JACK thread. - fn run ( - &self, cb: impl FnOnce(&Jack)->Usually - ) -> Usually>> { - let jack = self.jack(); - let app = Arc::new(RwLock::new(cb(jack)?)); - let mut state = Activating; - std::mem::swap(&mut*jack.state.write().unwrap(), &mut state); - if let Inactive(client) = state { - let client = client.activate_async( - // This is the misc notifications handler. It's a struct that wraps a [Box] - // which performs type erasure on a callback that takes [JackEvent], which is - // one of the available misc notifications. - Notifications(Box::new({ - let app = app.clone(); - move|event|app.write().unwrap().handle(event) - }) as BoxedJackEventHandler<'j>), - // This is the main processing handler. It's a struct that wraps a [Box] - // which performs type erasure on a callback that takes [Client] and [ProcessScope] - // and passes them down to the `app`'s `process` callback, which in turn - // implements audio and MIDI input and output on a realtime basis. - ClosureProcessHandler::new(Box::new({ - let app = app.clone(); - move|c: &_, s: &_|if let Ok(mut app) = app.write() { - app.process(c, s) - } else { - Control::Quit - } - }) as BoxedAudioHandler), - )?; - *jack.state.write().unwrap() = Active(client); - } else { - unreachable!(); - } - Ok(app) - } - /// Run something with the client. - fn with_client (&self, op: impl FnOnce(&Client)->T) -> T { - match &*self.jack().state.read().unwrap() { - Inert => panic!("jack client not activated"), - Inactive(ref client) => op(client), - Activating => panic!("jack client has not finished activation"), - Active(ref client) => op(client.as_client()), - } - } - fn port_by_name (&self, name: &str) -> Option> { - self.with_client(|client|client.port_by_name(name)) - } - fn port_by_id (&self, id: u32) -> Option> { - self.with_client(|c|c.port_by_id(id)) - } - fn register_port (&self, name: impl AsRef) -> Usually> { - self.with_client(|client|Ok(client.register_port(name.as_ref(), PS::default())?)) - } - fn sync_lead (&self, enable: bool, cb: impl Fn(TimebaseInfo)->Position) -> Usually<()> { - if enable { - self.with_client(|client|match client.register_timebase_callback(false, cb) { - Ok(_) => Ok(()), - Err(e) => Err(e) - })? - } - Ok(()) - } - fn sync_follow (&self, _enable: bool) -> Usually<()> { - // TODO: sync follow - Ok(()) - } -} - -/// Wraps [JackState] and through it [jack::Client]. -#[derive(Clone, Debug, Default)] -pub struct Jack<'j> { - pub state: Arc>> -} - -impl<'j> Jack<'j> { - pub fn new (name: &str) -> Usually { - Ok(Self { - state: JackState::new(Client::new(name, ClientOptions::NO_START_SERVER)?.0) - }) - } -} - -/// 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. -#[derive(Debug, Default)] -pub enum JackState<'j> { - /// Unused - #[default] Inert, - /// Before activation. - Inactive(Client), - /// During activation. - Activating, - /// After activation. Must not be dropped for JACK thread to persist. - Active(DynamicAsyncClient<'j>), -} - -impl<'j> JackState<'j> { - fn new (client: Client) -> Arc> { - Arc::new(RwLock::new(Self::Inactive(client))) - } -} diff --git a/crates/engine/src/jack/jack_device.rs b/crates/engine/src/jack/jack_device.rs deleted file mode 100644 index 01ed254f..00000000 --- a/crates/engine/src/jack/jack_device.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::*; - -///// A [AudioComponent] bound to a JACK client and a set of ports. -//pub struct JackDevice { - ///// The active JACK client of this device. - //pub client: DynamicAsyncClient, - ///// The device state, encapsulated for sharing between threads. - //pub state: Arc>>>, - ///// Unowned copies of the device's JACK ports, for connecting to the device. - ///// 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") - //.field("ports", &self.ports) - //.finish() - //} -//} - -//impl Render for JackDevice { - //type Engine = E; - //fn min_size(&self, to: E::Size) -> Perhaps { - //self.state.read().unwrap().layout(to) - //} - //fn render(&self, to: &mut E::Output) -> Usually<()> { - //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()) - //} - //fn audio_outs (&self) -> Usually>> { - //Ok(self.ports.audio_outs.values().collect()) - //} - //fn midi_ins (&self) -> Usually>> { - //Ok(self.ports.midi_ins.values().collect()) - //} - //fn midi_outs (&self) -> Usually>> { - //Ok(self.ports.midi_outs.values().collect()) - //} -//} - -//impl JackDevice { - ///// Returns a locked mutex of the state's contents. - //pub fn state(&self) -> LockResult>>> { - //self.state.read() - //} - ///// Returns a locked mutex of the state's contents. - //pub fn state_mut(&self) -> LockResult>>> { - //self.state.write() - //} - //pub fn connect_midi_in(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(port, self.midi_ins()?[index])?) - //} - //pub fn connect_midi_out(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(self.midi_outs()?[index], port)?) - //} - //pub fn connect_audio_in(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(port, self.audio_ins()?[index])?) - //} - //pub fn connect_audio_out(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(self.audio_outs()?[index], port)?) - //} -//} diff --git a/crates/engine/src/jack/jack_handler.rs b/crates/engine/src/jack/jack_handler.rs deleted file mode 100644 index 557f97e4..00000000 --- a/crates/engine/src/jack/jack_handler.rs +++ /dev/null @@ -1,105 +0,0 @@ -use crate::*; -use super::*; - -/// Trait for thing that has a JACK process callback. -pub trait Audio { - fn handle (&mut self, _event: JackEvent) {} - fn process (&mut self, _: &Client, _: &ProcessScope) -> Control { - Control::Continue - } - fn callback ( - state: &Arc>, client: &Client, scope: &ProcessScope - ) -> Control where Self: Sized { - if let Ok(mut state) = state.write() { - state.process(client, scope) - } else { - Control::Quit - } - } -} - -/// Implement [Audio]: provide JACK callbacks. -#[macro_export] macro_rules! audio { - (| - $self1:ident: - $Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident - |$cb:expr$(;|$self2:ident,$e:ident|$cb2:expr)?) => { - impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? { - #[inline] fn process (&mut $self1, $c: &Client, $s: &ProcessScope) -> Control { $cb } - $(#[inline] fn handle (&mut $self2, $e: JackEvent) { $cb2 })? - } - } -} - -/// Event enum for JACK events. -#[derive(Debug, Clone, PartialEq)] pub enum JackEvent { - ThreadInit, - Shutdown(ClientStatus, Arc), - Freewheel(bool), - SampleRate(Frames), - ClientRegistration(Arc, bool), - PortRegistration(PortId, bool), - PortRename(PortId, Arc, Arc), - PortsConnected(PortId, PortId, bool), - 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); - } - unsafe fn shutdown(&mut self, status: ClientStatus, reason: &str) { - self.0(JackEvent::Shutdown(status, reason.into())); - } - fn freewheel(&mut self, _: &Client, enabled: bool) { - self.0(JackEvent::Freewheel(enabled)); - } - fn sample_rate(&mut self, _: &Client, frames: Frames) -> Control { - self.0(JackEvent::SampleRate(frames)); - Control::Quit - } - fn client_registration(&mut self, _: &Client, name: &str, reg: bool) { - self.0(JackEvent::ClientRegistration(name.into(), reg)); - } - fn port_registration(&mut self, _: &Client, id: PortId, reg: bool) { - self.0(JackEvent::PortRegistration(id, reg)); - } - fn port_rename(&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { - self.0(JackEvent::PortRename(id, old.into(), new.into())); - Control::Continue - } - fn ports_connected(&mut self, _: &Client, a: PortId, b: PortId, are: bool) { - self.0(JackEvent::PortsConnected(a, b, are)); - } - fn graph_reorder(&mut self, _: &Client) -> Control { - self.0(JackEvent::GraphReorder); - Control::Continue - } - fn xrun(&mut self, _: &Client) -> Control { - self.0(JackEvent::XRun); - Control::Continue - } -} - -/// 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>>; -/// This is the notification handler wrapper for a boxed realtime callback. -pub type DynamicAudioHandler<'j> = - ClosureProcessHandler<(), BoxedAudioHandler<'j>>; -/// This is a boxed realtime callback. -pub type BoxedAudioHandler<'j> = - Box Control + Send + Sync + 'j>; -/// This is the notification handler wrapper for a boxed [JackEvent] callback. -pub type DynamicNotifications<'j> = - Notifications>; -/// This is a boxed [JackEvent] callback. -pub type BoxedJackEventHandler<'j> = - Box; diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 24c4b15f..8ecfdb75 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -88,7 +88,6 @@ mod time; pub use self::time::*; mod note; pub use self::note::*; pub mod jack; pub use self::jack::*; pub mod midi; pub use self::midi::*; -pub mod audio; pub use self::audio::*; pub(crate) use std::sync::{Arc, atomic::{AtomicUsize, AtomicBool, Ordering::Relaxed}}; pub(crate) use std::fmt::Debug; diff --git a/crates/engine/src/midi.rs b/crates/engine/src/midi.rs index c3588d1c..62b132b7 100644 --- a/crates/engine/src/midi.rs +++ b/crates/engine/src/midi.rs @@ -9,10 +9,6 @@ pub use ::midly::{ live::*, }; -mod midi_in; pub use self::midi_in::*; -mod midi_out; pub use self::midi_out::*; -mod midi_hold; pub use self::midi_hold::*; - /// Return boxed iterator of MIDI events pub fn parse_midi_input <'a> (input: ::jack::MidiIter<'a>) -> Box, &'a [u8])> + 'a> { Box::new(input.map(|::jack::RawMidi { time, bytes }|( @@ -30,3 +26,12 @@ pub fn all_notes_off (output: &mut [Vec>]) { evt.write(&mut buf).unwrap(); output[0].push(buf); } + +/// Update notes_in array +pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) { + match message { + MidiMessage::NoteOn { key, .. } => { keys[key.as_int() as usize] = true; } + MidiMessage::NoteOff { key, .. } => { keys[key.as_int() as usize] = false; }, + _ => {} + } +} diff --git a/crates/engine/src/midi/midi_hold.rs b/crates/engine/src/midi/midi_hold.rs deleted file mode 100644 index 0717e90c..00000000 --- a/crates/engine/src/midi/midi_hold.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::*; - -/// Update notes_in array -pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) { - match message { - MidiMessage::NoteOn { key, .. } => { keys[key.as_int() as usize] = true; } - MidiMessage::NoteOff { key, .. } => { keys[key.as_int() as usize] = false; }, - _ => {} - } -} diff --git a/deps/tengri b/deps/tengri index 455d6d00..f714302f 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit 455d6d00d5f91e7f9f6b9d3711aa47e09900ad46 +Subproject commit f714302f21d64ef5f6dcbee09aa913e8e3b9fbc5 From 0192d85a19b1705e41a26a280c5fe15c21c63f81 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 21 May 2025 01:45:23 +0300 Subject: [PATCH 2/2] wip: compiles again, after extensive jack rework --- crates/app/src/audio.rs | 6 +- crates/app/src/model.rs | 12 +- crates/app/src/view.rs | 127 +-------- crates/cli/tek.rs | 33 +-- crates/device/src/arranger/arranger_model.rs | 83 +++--- crates/device/src/arranger/arranger_tracks.rs | 20 +- crates/device/src/arranger/arranger_view.rs | 11 +- crates/device/src/clock/clock_model.rs | 3 + crates/device/src/clock/clock_view.rs | 84 ++++++ crates/device/src/port.rs | 210 ++++++++++++++- crates/device/src/port/port_audio_in.rs | 8 +- crates/device/src/port/port_audio_out.rs | 8 +- crates/device/src/port/port_connect.rs | 181 ------------- crates/device/src/port/port_midi_in.rs | 10 +- crates/device/src/port/port_midi_out.rs | 10 +- crates/engine/src/jack.rs | 241 +++++++++--------- crates/engine/src/lib.rs | 2 +- deps/tengri | 2 +- 18 files changed, 526 insertions(+), 525 deletions(-) delete mode 100644 crates/device/src/port/port_connect.rs diff --git a/crates/app/src/audio.rs b/crates/app/src/audio.rs index b8d7be61..099238bc 100644 --- a/crates/app/src/audio.rs +++ b/crates/app/src/audio.rs @@ -1,5 +1,9 @@ use crate::*; - +impl HasJack<'static> for App { + fn jack (&self) -> &Jack<'static> { + &self.jack + } +} audio!( |self: App, client, scope|{ let t0 = self.perf.get_t0(); diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index 80d7f80b..48285fc1 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -1,12 +1,9 @@ use crate::*; use std::path::PathBuf; - #[derive(Default, Debug)] pub struct App { /// Must not be dropped for the duration of the process pub jack: Jack<'static>, - /// Port handles - pub ports: std::collections::BTreeMap>, /// Display size pub size: Measure, /// Performance counter @@ -21,12 +18,9 @@ pub struct App { pub history: Vec<(AppCommand, Option)>, // Dialog overlay pub dialog: Option, - // Cache of formatted strings - pub view_cache: Arc>, /// Base color. pub color: ItemTheme, } - has!(Jack<'static>: |self: App|self.jack); has!(Pool: |self: App|self.pool); has!(Option: |self: App|self.dialog); @@ -69,7 +63,7 @@ from_dsl!(DialogCommand: |state: App, iter|Namespace::take_from(&state.dial impl App { pub fn update_clock (&self) { - ViewCache::update_clock(&self.view_cache, self.clock(), self.size.w() > 80) + ViewCache::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80) } pub fn toggle_dialog (&mut self, mut dialog: Option) -> Option { std::mem::swap(&mut self.dialog, &mut dialog); @@ -106,10 +100,10 @@ impl App { } fn device_add_sampler (&mut self) -> Usually<()> { let name = self.jack.with_client(|c|c.name().to_string()); - let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].name(); + let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].port_name(); let track = self.track().expect("no active track"); let port = format!("{}/Sampler", &track.name); - let connect = Connect::exact(format!("{name}:{midi}")); + let connect = Connect::exact(format!("{name}:{midi}")); let sampler = if let Ok(sampler) = Sampler::new( &self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]] ) { diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 6d1c2549..f590466a 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -60,7 +60,7 @@ impl App { ))); add(&" "); { - let cache = self.view_cache.read().unwrap(); + let cache = self.project.clock.view_cache.read().unwrap(); add(&Fixed::x(15, Align::w(Bsp::s( FieldH(theme, "Beat", cache.beat.view.clone()), FieldH(theme, "Time", cache.time.view.clone()), @@ -90,7 +90,7 @@ impl App { } pub fn view_status_v (&self) -> impl Content + use<'_> { self.update_clock(); - let cache = self.view_cache.read().unwrap(); + let cache = self.project.clock.view_cache.read().unwrap(); let theme = self.color; let playing = self.clock().is_rolling(); Tui::bg(theme.darker.rgb, Fixed::xy(20, 5, Outer(true, Style::default().fg(Tui::g(96))).enclose( @@ -120,13 +120,13 @@ impl App { } pub fn view_status (&self) -> impl Content + use<'_> { self.update_clock(); - let cache = self.view_cache.read().unwrap(); + let cache = self.project.clock.view_cache.read().unwrap(); view_status(Some(self.project.selection.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(); + let cache = self.project.clock.view_cache.read().unwrap(); view_transport(self.project.clock.is_rolling(), cache.bpm.view.clone(), cache.beat.view.clone(), cache.time.view.clone()) } @@ -225,122 +225,3 @@ impl ScenesView for App { (self.width() as u16).saturating_sub(self.w_side()) } } - -/// Clear a pre-allocated buffer, then write into it. -#[macro_export] macro_rules! rewrite { - ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } } -} - -#[derive(Debug, Default)] pub(crate) struct ViewMemo { - pub(crate) value: T, - pub(crate) view: Arc> -} - -impl ViewMemo { - fn new (value: T, view: U) -> Self { - Self { value, view: Arc::new(view.into()) } - } - pub(crate) fn update ( - &mut self, - newval: T, - render: impl Fn(&mut U, &T, &T)->R - ) -> Option { - if newval != self.value { - let result = render(&mut*self.view.write().unwrap(), &newval, &self.value); - self.value = newval; - return Some(result); - } - None - } -} - -#[derive(Debug)] pub struct ViewCache { - pub(crate) sr: ViewMemo, String>, - pub(crate) buf: ViewMemo, String>, - pub(crate) lat: ViewMemo, String>, - pub(crate) bpm: ViewMemo, String>, - pub(crate) beat: ViewMemo, String>, - pub(crate) time: ViewMemo, String>, - pub(crate) scns: ViewMemo, String>, - pub(crate) trks: ViewMemo, String>, - pub(crate) stop: Arc, - pub(crate) edit: Arc, -} - -impl Default for ViewCache { - fn default () -> Self { - let mut beat = String::with_capacity(16); - write!(beat, "{}", Self::BEAT_EMPTY); - let mut time = String::with_capacity(16); - write!(time, "{}", Self::TIME_EMPTY); - let mut bpm = String::with_capacity(16); - write!(bpm, "{}", Self::BPM_EMPTY); - Self { - beat: ViewMemo::new(None, beat), - time: ViewMemo::new(None, time), - bpm: ViewMemo::new(None, bpm), - sr: ViewMemo::new(None, String::with_capacity(16)), - buf: ViewMemo::new(None, String::with_capacity(16)), - lat: ViewMemo::new(None, String::with_capacity(16)), - scns: ViewMemo::new(None, String::with_capacity(16)), - trks: ViewMemo::new(None, String::with_capacity(16)), - stop: "⏹".into(), - edit: "edit".into(), - } - } -} - -impl ViewCache { - pub const BEAT_EMPTY: &'static str = "-.-.--"; - pub const TIME_EMPTY: &'static str = "-.---s"; - pub const BPM_EMPTY: &'static str = "---.---"; - - pub fn track_counter (cache: &Arc>, track: usize, tracks: usize) - -> Arc> - { - let data = (track, tracks); - cache.write().unwrap().trks.update(Some(data), rewrite!(buf, "{}/{}", data.0, data.1)); - cache.read().unwrap().trks.view.clone() - } - - pub fn scene_add (cache: &Arc>, scene: usize, scenes: usize, is_editing: bool) - -> impl Content - { - let data = (scene, scenes); - cache.write().unwrap().scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1)); - button_3("S", "add scene", cache.read().unwrap().scns.view.clone(), is_editing) - } - - pub fn update_clock (cache: &Arc>, clock: &Clock, compact: bool) { - let rate = clock.timebase.sr.get(); - let chunk = clock.chunk.load(Relaxed) as f64; - let lat = chunk / rate * 1000.; - let delta = |start: &Moment|clock.global.usec.get() - start.usec.get(); - let mut cache = cache.write().unwrap(); - cache.buf.update(Some(chunk), rewrite!(buf, "{chunk}")); - cache.lat.update(Some(lat), rewrite!(buf, "{lat:.1}ms")); - cache.sr.update(Some((compact, rate)), |buf,_,_|{ - buf.clear(); - if compact { - write!(buf, "{:.1}kHz", rate / 1000.) - } else { - write!(buf, "{:.0}Hz", rate) - } - }); - if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) { - let pulse = clock.timebase.usecs_to_pulse(now); - let time = now/1000000.; - let bpm = clock.timebase.bpm.get(); - cache.beat.update(Some(pulse), |buf, _, _|{ - buf.clear(); - clock.timebase.format_beats_1_to(buf, pulse) - }); - cache.time.update(Some(time), rewrite!(buf, "{:.3}s", time)); - cache.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm)); - } else { - cache.beat.update(None, rewrite!(buf, "{}", ViewCache::BEAT_EMPTY)); - cache.time.update(None, rewrite!(buf, "{}", ViewCache::TIME_EMPTY)); - cache.bpm.update(None, rewrite!(buf, "{}", ViewCache::BPM_EMPTY)); - } - } -} diff --git a/crates/cli/tek.rs b/crates/cli/tek.rs index fd580d00..1e0ec536 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -78,25 +78,23 @@ impl Cli { let mut midi_outs = vec![]; let mut tracks = vec![]; let mut scenes = vec![]; - let midi_froms = PortConnect::collect(&self.midi_from, empty, &self.midi_from_re); - let midi_tos = PortConnect::collect(&self.midi_to, empty, &self.midi_to_re); - let left_froms = PortConnect::collect(&self.left_from, empty, empty); - let left_tos = PortConnect::collect(&self.left_to, empty, empty); - let right_froms = PortConnect::collect(&self.right_from, empty, empty); - let right_tos = PortConnect::collect(&self.right_to, empty, empty); + let midi_froms = Connect::collect(&self.midi_from, empty, &self.midi_from_re); + let midi_tos = Connect::collect(&self.midi_to, empty, &self.midi_to_re); + let left_froms = Connect::collect(&self.left_from, empty, empty); + let left_tos = Connect::collect(&self.left_to, empty, empty); + let right_froms = Connect::collect(&self.right_from, empty, empty); + let right_tos = Connect::collect(&self.right_to, empty, empty); let audio_froms = &[left_froms.as_slice(), right_froms.as_slice()]; let audio_tos = &[left_tos.as_slice(), right_tos.as_slice()]; let clip = Arc::new(RwLock::new(MidiClip::new( "Clip", true, 384usize, None, Some(ItemColor::random().into())), )); - Tui::new()?.run(&Jack::new(name)?.run(|jack|{ + Tui::new()?.run(&Jack::new_run(&name, move|jack|{ for (index, connect) in midi_froms.iter().enumerate() { - let port = MidiInput::new(jack, &format!("M/{index}"), &[connect.clone()])?; - midi_ins.push(port); + midi_ins.push(jack.midi_in(&format!("M/{index}"), &[connect.clone()])?); } for (index, connect) in midi_tos.iter().enumerate() { - let port = MidiOutput::new(jack, &format!("{index}/M"), &[connect.clone()])?; - midi_outs.push(port); + midi_outs.push(jack.midi_out(&format!("{index}/M"), &[connect.clone()])?); }; let config = Configuration::new(&match self.mode { LaunchMode::Clock => "config/config_transport.edn", @@ -106,14 +104,14 @@ impl Cli { LaunchMode::Sampler => "config/config_sampler.edn", _ => todo!("{:?}", self.mode), }, false)?; - let clock = Clock::new(jack, self.bpm)?; + let clock = Clock::new(&jack, self.bpm)?; match self.mode { LaunchMode::Sequencer => tracks.push(Track::new( - &name, None, jack, Some(&clock), Some(&clip), + &name, None, &jack, Some(&clock), Some(&clip), midi_froms.as_slice(), midi_tos.as_slice() )?), LaunchMode::Groovebox | LaunchMode::Sampler => tracks.push(Track::new_with_sampler( - &name, None, jack, Some(&clock), Some(&clip), + &name, None, &jack, Some(&clock), Some(&clip), midi_froms.as_slice(), midi_tos.as_slice(), audio_froms, audio_tos, )?), _ => {} @@ -172,10 +170,5 @@ const HEADER: &'static str = r#" #[cfg(test)] #[test] fn test_cli () { use clap::CommandFactory; Cli::command().debug_assert(); - let jack = Jack::default(); - //TODO: - //let _ = App::new_clock(&jack, None, false, false, &[], &[]); - //let _ = App::new_sequencer(&jack, None, false, false, &[], &[]); - //let _ = App::new_groovebox(&jack, None, false, false, &[], &[], &[&[], &[]], &[&[], &[]]); - //let _ = App::new_arranger(&jack, None, false, false, &[], &[], &[&[], &[]], &[&[], &[]], 0, 0, 0); + //let jack = Jack::default(); } diff --git a/crates/device/src/arranger/arranger_model.rs b/crates/device/src/arranger/arranger_model.rs index 3e0a42c5..0f26c58e 100644 --- a/crates/device/src/arranger/arranger_model.rs +++ b/crates/device/src/arranger/arranger_model.rs @@ -1,4 +1,48 @@ use crate::*; +#[derive(Default, Debug)] pub struct Arrangement { + /// Project name. + pub name: Arc, + /// Base color. + pub color: ItemTheme, + /// Jack client handle + pub jack: Jack<'static>, + /// Source of time + pub clock: Clock, + /// Allows one MIDI clip to be edited + pub editor: Option, + /// List of global midi inputs + pub midi_ins: Vec, + /// List of global midi outputs + pub midi_outs: Vec, + /// List of global audio inputs + pub audio_ins: Vec, + /// List of global audio outputs + pub audio_outs: Vec, + /// Last track number (to avoid duplicate port names) + pub track_last: usize, + /// List of tracks + pub tracks: Vec, + /// Scroll offset of tracks + pub track_scroll: usize, + /// List of scenes + pub scenes: Vec, + /// Scroll offset of scenes + pub scene_scroll: usize, + /// Selected UI element + pub selection: Selection, + /// Contains a render of the project arrangement, redrawn on update. + /// TODO rename to "render_cache" or smth + pub arranger: Arc>, + /// Display size + pub size: Measure, + /// Display size of clips area + pub inner_size: Measure, +} +impl HasJack<'static> for Arrangement { + fn jack (&self) -> &Jack<'static> { + &self.jack + } +} has!(Jack<'static>: |self: Arrangement|self.jack); has!(Clock: |self: Arrangement|self.clock); has!(Selection: |self: Arrangement|self.selection); @@ -41,45 +85,6 @@ from_dsl!(ClipCommand: |state: Arrangement, iter|state.selected_clip().as_ref() Selection::Nothing } } -#[derive(Default, Debug)] pub struct Arrangement { - /// Project name. - pub name: Arc, - /// Base color. - pub color: ItemTheme, - /// Jack client handle - pub jack: Jack<'static>, - /// Source of time - pub clock: Clock, - /// Allows one MIDI clip to be edited - pub editor: Option, - /// List of global midi inputs - pub midi_ins: Vec, - /// List of global midi outputs - pub midi_outs: Vec, - /// List of global audio inputs - pub audio_ins: Vec, - /// List of global audio outputs - pub audio_outs: Vec, - /// Last track number (to avoid duplicate port names) - pub track_last: usize, - /// List of tracks - pub tracks: Vec, - /// Scroll offset of tracks - pub track_scroll: usize, - /// List of scenes - pub scenes: Vec, - /// Scroll offset of scenes - pub scene_scroll: usize, - /// Selected UI element - pub selection: Selection, - /// Contains a render of the project arrangement, redrawn on update. - /// TODO rename to "render_cache" or smth - pub arranger: Arc>, - /// Display size - pub size: Measure, - /// Display size of clips area - pub inner_size: Measure, -} impl Arrangement { /// Width of display pub fn w (&self) -> u16 { diff --git a/crates/device/src/arranger/arranger_tracks.rs b/crates/device/src/arranger/arranger_tracks.rs index 71a9e3ed..332a1aa8 100644 --- a/crates/device/src/arranger/arranger_tracks.rs +++ b/crates/device/src/arranger/arranger_tracks.rs @@ -222,15 +222,11 @@ impl Track { audio_to: &[&[Connect];2], ) -> Usually { let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?; + let client_name = jack.with_client(|c|c.name().to_string()); + let port_name = track.sequencer.midi_outs[0].port_name(); + let connect = [Connect::exact(format!("{client_name}:{}", port_name))]; track.devices.push(Device::Sampler(Sampler::new( - jack, - &format!("{}/sampler", name.as_ref()), - &[Connect::exact(format!("{}:{}", - jack.with_client(|c|c.name().to_string()), - track.sequencer.midi_outs[0].name() - ))], - audio_from, - audio_to + jack, &format!("{}/sampler", name.as_ref()), &connect, audio_from, audio_to )?)); Ok(track) } @@ -273,7 +269,7 @@ pub trait HasTrack { Fixed::xy(20, 1 + ins, Outer(true, Style::default().fg(Tui::g(96))).enclose( Fixed::xy(20, 1 + ins, FieldV(theme, format!("MIDI ins: "), Map::south(1, ||track.sequencer.midi_ins.iter(), - |port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))))}) + |port, index|Fill::x(Align::w(format!(" {index} {}", port.port_name()))))))))}) } fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Content { self.track().map(|track|{ @@ -281,7 +277,7 @@ pub trait HasTrack { Fixed::xy(20, 1 + outs, Outer(true, Style::default().fg(Tui::g(96))).enclose( Fixed::xy(20, 1 + outs, FieldV(theme, format!("MIDI outs: "), Map::south(1, ||track.sequencer.midi_outs.iter(), - |port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))))}) + |port, index|Fill::x(Align::w(format!(" {index} {}", port.port_name()))))))))}) } fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Content { self.track().and_then(|track|track.devices.get(0)).map(|device|{ @@ -289,7 +285,7 @@ pub trait HasTrack { Fixed::xy(20, 1 + ins, Outer(true, Style::default().fg(Tui::g(96))).enclose( Fixed::xy(20, 1 + ins, FieldV(theme, format!("Audio ins: "), Map::south(1, ||device.audio_ins().iter(), - |port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))))}) + |port, index|Fill::x(Align::w(format!(" {index} {}", port.port_name()))))))))}) } fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Content { self.track().and_then(|track|track.devices.last()).map(|device|{ @@ -297,7 +293,7 @@ pub trait HasTrack { Fixed::xy(20, 1 + outs, Outer(true, Style::default().fg(Tui::g(96))).enclose( Fixed::xy(20, 1 + outs, FieldV(theme, format!("Audio outs: "), Map::south(1, ||device.audio_outs().iter(), - |port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))))}) + |port, index|Fill::x(Align::w(format!(" {index} {}", port.port_name()))))))))}) } } diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index 7a5cc937..89fef966 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -22,7 +22,8 @@ impl Arrangement { }))))); for (index, port) in self.midi_ins().iter().enumerate() { add(&Fixed::y(1, Bsp::e( - Fixed::x(20, Align::w(Bsp::e(" ● ", Tui::bold(true, Tui::fg(Rgb(255,255,255),port.name()))))), + Fixed::x(20, Align::w(Bsp::e(" ● ", + Tui::bold(true, Tui::fg(Rgb(255,255,255), port.port_name()))))), Bsp::w(Fixed::x(4, ()), Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ for (index, track, x1, x2) in self.tracks_with_sizes() { @@ -47,7 +48,7 @@ impl Arrangement { Fixed::y(h - 1, Fill::xy(Align::nw(Stack::south(|add: &mut dyn FnMut(&dyn Render)|{ for (index, port) in self.midi_outs().iter().enumerate() { add(&Fixed::y(1,Fill::x(Bsp::e( - Align::w(Bsp::e(" ● ", Tui::fg(Rgb(255,255,255),Tui::bold(true, port.name())))), + Align::w(Bsp::e(" ● ", Tui::fg(Rgb(255,255,255),Tui::bold(true, port.port_name())))), Fill::x(Align::e(format!("{}/{} ", port.port().get_connections().len(), port.connections.len()))))))); @@ -195,7 +196,7 @@ pub trait TracksView: Fill::x(Align::w(button_2("o", "utput", false))), Fill::xy(Stack::south(|add: &mut dyn FnMut(&dyn Render)|{ for port in self.midi_outs().iter() { - add(&Fill::x(Align::w(port.name()))); + add(&Fill::x(Align::w(port.port_name()))); } }))), button_2("O", "+", false), @@ -206,7 +207,7 @@ pub trait TracksView: Align::nw(Fill::y(Map::south(1, ||track.sequencer.midi_outs.iter(), |port, index|Tui::fg(Rgb(255, 255, 255), Fixed::y(1, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( - format!("·o{index:02} {}", port.name()))))))))))); + format!("·o{index:02} {}", port.port_name()))))))))))); } }))))) } @@ -232,7 +233,7 @@ pub trait TracksView: )))), Map::south(1, ||track.sequencer.midi_ins.iter(), |port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb, - Fill::x(Align::w(format!("·i{index:02} {}", port.name()))))))))); + Fill::x(Align::w(format!("·i{index:02} {}", port.port_name()))))))))); }}))))) } fn track_width (&self, index: usize, track: &Track) -> u16 { diff --git a/crates/device/src/clock/clock_model.rs b/crates/device/src/clock/clock_model.rs index 2eca6b99..0a881025 100644 --- a/crates/device/src/clock/clock_model.rs +++ b/crates/device/src/clock/clock_model.rs @@ -26,6 +26,8 @@ pub struct Clock { pub midi_out: Arc>>, /// For emitting a metronome pub click_out: Arc>>, + // Cache of formatted strings + pub view_cache: Arc>, } impl std::fmt::Debug for Clock { @@ -59,6 +61,7 @@ impl Clock { midi_in: Arc::new(RwLock::new(Some(MidiInput::new(jack, &"M/clock", &[])?))), midi_out: Arc::new(RwLock::new(Some(MidiOutput::new(jack, &"clock/M", &[])?))), click_out: Arc::new(RwLock::new(Some(AudioOutput::new(jack, &"click", &[])?))), + ..Default::default() }; if let Some(bpm) = bpm { clock.timebase.bpm.set(bpm); diff --git a/crates/device/src/clock/clock_view.rs b/crates/device/src/clock/clock_view.rs index 92e778eb..09b6561d 100644 --- a/crates/device/src/clock/clock_view.rs +++ b/crates/device/src/clock/clock_view.rs @@ -1,4 +1,5 @@ use crate::*; +use std::fmt::Write; pub fn view_transport ( play: bool, @@ -49,3 +50,86 @@ pub(crate) fn button_play_pause (playing: bool) -> impl Content { ) ) } + +#[derive(Debug)] pub struct ViewCache { + pub sr: Memo, String>, + pub buf: Memo, String>, + pub lat: Memo, String>, + pub bpm: Memo, String>, + pub beat: Memo, String>, + pub time: Memo, String>, +} + +impl Default for ViewCache { + fn default () -> Self { + let mut beat = String::with_capacity(16); + write!(beat, "{}", Self::BEAT_EMPTY); + let mut time = String::with_capacity(16); + write!(time, "{}", Self::TIME_EMPTY); + let mut bpm = String::with_capacity(16); + write!(bpm, "{}", Self::BPM_EMPTY); + Self { + beat: Memo::new(None, beat), + time: Memo::new(None, time), + bpm: Memo::new(None, bpm), + sr: Memo::new(None, String::with_capacity(16)), + buf: Memo::new(None, String::with_capacity(16)), + lat: Memo::new(None, String::with_capacity(16)), + } + } +} + +impl ViewCache { + pub const BEAT_EMPTY: &'static str = "-.-.--"; + pub const TIME_EMPTY: &'static str = "-.---s"; + pub const BPM_EMPTY: &'static str = "---.---"; + + //pub fn track_counter (cache: &Arc>, track: usize, tracks: usize) + //-> Arc> + //{ + //let data = (track, tracks); + //cache.write().unwrap().trks.update(Some(data), rewrite!(buf, "{}/{}", data.0, data.1)); + //cache.read().unwrap().trks.view.clone() + //} + + //pub fn scene_add (cache: &Arc>, scene: usize, scenes: usize, is_editing: bool) + //-> impl Content + //{ + //let data = (scene, scenes); + //cache.write().unwrap().scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1)); + //button_3("S", "add scene", cache.read().unwrap().scns.view.clone(), is_editing) + //} + + pub fn update_clock (cache: &Arc>, clock: &Clock, compact: bool) { + let rate = clock.timebase.sr.get(); + let chunk = clock.chunk.load(Relaxed) as f64; + let lat = chunk / rate * 1000.; + let delta = |start: &Moment|clock.global.usec.get() - start.usec.get(); + let mut cache = cache.write().unwrap(); + cache.buf.update(Some(chunk), rewrite!(buf, "{chunk}")); + cache.lat.update(Some(lat), rewrite!(buf, "{lat:.1}ms")); + cache.sr.update(Some((compact, rate)), |buf,_,_|{ + buf.clear(); + if compact { + write!(buf, "{:.1}kHz", rate / 1000.) + } else { + write!(buf, "{:.0}Hz", rate) + } + }); + if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) { + let pulse = clock.timebase.usecs_to_pulse(now); + let time = now/1000000.; + let bpm = clock.timebase.bpm.get(); + cache.beat.update(Some(pulse), |buf, _, _|{ + buf.clear(); + clock.timebase.format_beats_1_to(buf, pulse) + }); + cache.time.update(Some(time), rewrite!(buf, "{:.3}s", time)); + cache.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm)); + } else { + cache.beat.update(None, rewrite!(buf, "{}", ViewCache::BEAT_EMPTY)); + cache.time.update(None, rewrite!(buf, "{}", ViewCache::TIME_EMPTY)); + cache.bpm.update(None, rewrite!(buf, "{}", ViewCache::BPM_EMPTY)); + } + } +} diff --git a/crates/device/src/port.rs b/crates/device/src/port.rs index d8735ae1..f75959ea 100644 --- a/crates/device/src/port.rs +++ b/crates/device/src/port.rs @@ -1,8 +1,216 @@ +use crate::*; + mod port_audio_out; pub use self::port_audio_out::*; mod port_audio_in; pub use self::port_audio_in::*; -mod port_connect; pub use self::port_connect::*; mod port_midi_out; pub use self::port_midi_out::*; mod port_midi_in; pub use self::port_midi_in::*; pub(crate) use ConnectName::*; pub(crate) use ConnectScope::*; pub(crate) use ConnectStatus::*; + +pub trait RegisterPorts: HasJack<'static> { + /// Register a MIDI input port. + fn midi_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; + /// Register a MIDI output port. + fn midi_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; + /// Register an audio input port. + fn audio_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; + /// Register an audio output port. + fn audio_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; +} + +impl> RegisterPorts for J { + fn midi_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually { + MidiInput::new(self.jack(), name, connect) + } + fn midi_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually { + MidiOutput::new(self.jack(), name, connect) + } + fn audio_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually { + AudioInput::new(self.jack(), name, connect) + } + fn audio_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually { + AudioOutput::new(self.jack(), name, connect) + } +} + +pub trait JackPort: HasJack<'static> { + type Port: PortSpec + Default; + type Pair: PortSpec + Default; + fn new (jack: &Jack<'static>, name: &impl AsRef, connect: &[Connect]) + -> Usually where Self: Sized; + fn register (jack: &Jack<'static>, name: &impl AsRef) -> Usually> { + jack.with_client(|c|c.register_port::(name.as_ref(), Default::default())) + .map_err(|e|e.into()) + } + fn port_name (&self) -> &Arc; + fn connections (&self) -> &[Connect]; + fn port (&self) -> &Port; + fn port_mut (&mut self) -> &mut Port; + fn into_port (self) -> Port where Self: Sized; + fn close (self) -> Usually<()> where Self: Sized { + let jack = self.jack().clone(); + Ok(jack.with_client(|c|c.unregister_port(self.into_port()))?) + } + fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec { + self.with_client(|c|c.ports(re_name, re_type, flags)) + } + fn port_by_id (&self, id: u32) -> Option> { + self.with_client(|c|c.port_by_id(id)) + } + fn port_by_name (&self, name: impl AsRef) -> Option> { + self.with_client(|c|c.port_by_name(name.as_ref())) + } + fn connect_to_matching <'k> (&'k self) -> Usually<()> { + for connect in self.connections().iter() { + //panic!("{connect:?}"); + let status = match &connect.name { + Exact(name) => self.connect_exact(name), + RegExp(re) => self.connect_regexp(re, connect.scope), + }?; + *connect.status.write().unwrap() = status; + } + Ok(()) + } + fn connect_exact <'k> (&'k self, name: &str) -> + Usually, Arc, ConnectStatus)>> + { + self.with_client(move|c|{ + let mut status = vec![]; + for port in c.ports(None, None, PortFlags::empty()).iter() { + if port.as_str() == &*name { + if let Some(port) = c.port_by_name(port.as_str()) { + let port_status = self.connect_to_unowned(&port)?; + let name = port.name()?.into(); + status.push((port, name, port_status)); + if port_status == Connected { + break + } + } + } + } + Ok(status) + }) + } + fn connect_regexp <'k> ( + &'k self, re: &str, scope: ConnectScope + ) -> Usually, Arc, ConnectStatus)>> { + self.with_client(move|c|{ + let mut status = vec![]; + let ports = c.ports(Some(&re), None, PortFlags::empty()); + for port in ports.iter() { + if let Some(port) = c.port_by_name(port.as_str()) { + let port_status = self.connect_to_unowned(&port)?; + let name = port.name()?.into(); + status.push((port, name, port_status)); + if port_status == Connected && scope == One { + break + } + } + } + Ok(status) + }) + } + /** Connect to a matching port by name. */ + fn connect_to_name (&self, name: impl AsRef) -> Usually { + self.with_client(|c|if let Some(ref port) = c.port_by_name(name.as_ref()) { + self.connect_to_unowned(port) + } else { + Ok(Missing) + }) + } + /** Connect to a matching port by reference. */ + fn connect_to_unowned (&self, port: &Port) -> Usually { + self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) { + Connected + } else if let Ok(_) = c.connect_ports(port, self.port()) { + Connected + } else { + Mismatch + })) + } + /** Connect to an owned matching port by reference. */ + fn connect_to_owned (&self, port: &Port) -> Usually { + self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) { + Connected + } else if let Ok(_) = c.connect_ports(port, self.port()) { + Connected + } else { + Mismatch + })) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ConnectName { + /** Exact match */ + Exact(Arc), + /** Match regular expression */ + RegExp(Arc), +} + +#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectScope { + One, + All +} + +#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectStatus { + Missing, + Disconnected, + Connected, + Mismatch, +} + +#[derive(Clone, Debug)] pub struct Connect { + pub name: ConnectName, + pub scope: ConnectScope, + pub status: Arc, Arc, ConnectStatus)>>>, + pub info: Arc, +} + +impl Connect { + pub fn collect (exact: &[impl AsRef], re: &[impl AsRef], re_all: &[impl AsRef]) + -> Vec + { + let mut connections = vec![]; + for port in exact.iter() { connections.push(Self::exact(port)) } + for port in re.iter() { connections.push(Self::regexp(port)) } + for port in re_all.iter() { connections.push(Self::regexp_all(port)) } + connections + } + /// Connect to this exact port + pub fn exact (name: impl AsRef) -> Self { + let info = format!("=:{}", name.as_ref()).into(); + let name = Exact(name.as_ref().into()); + Self { name, scope: One, status: Arc::new(RwLock::new(vec![])), info } + } + pub fn regexp (name: impl AsRef) -> Self { + let info = format!("~:{}", name.as_ref()).into(); + let name = RegExp(name.as_ref().into()); + Self { name, scope: One, status: Arc::new(RwLock::new(vec![])), info } + } + pub fn regexp_all (name: impl AsRef) -> Self { + let info = format!("+:{}", name.as_ref()).into(); + let name = RegExp(name.as_ref().into()); + Self { name, scope: All, status: Arc::new(RwLock::new(vec![])), info } + } + pub fn info (&self) -> Arc { + let status = { + let status = self.status.read().unwrap(); + let mut ok = 0; + for (_, _, state) in status.iter() { + if *state == Connected { + ok += 1 + } + } + format!("{ok}/{}", status.len()) + }; + let scope = match self.scope { + One => " ", All => "*", + }; + let name = match &self.name { + Exact(name) => format!("= {name}"), RegExp(name) => format!("~ {name}"), + }; + format!(" ({}) {} {}", status, scope, name).into() + } +} diff --git a/crates/device/src/port/port_audio_in.rs b/crates/device/src/port/port_audio_in.rs index dcec321c..7edd0755 100644 --- a/crates/device/src/port/port_audio_in.rs +++ b/crates/device/src/port/port_audio_in.rs @@ -10,11 +10,15 @@ use crate::*; /// List of ports to connect to. pub connections: Vec, } -has!(Jack<'static>: |self: AudioInput|self.jack); +impl HasJack<'static> for AudioInput { + fn jack (&self) -> &Jack<'static> { + &self.jack + } +} impl JackPort for AudioInput { type Port = AudioIn; type Pair = AudioOut; - fn name (&self) -> &Arc { + fn port_name (&self) -> &Arc { &self.name } fn port (&self) -> &Port { diff --git a/crates/device/src/port/port_audio_out.rs b/crates/device/src/port/port_audio_out.rs index 9984cf13..fda9d606 100644 --- a/crates/device/src/port/port_audio_out.rs +++ b/crates/device/src/port/port_audio_out.rs @@ -10,11 +10,15 @@ use crate::*; /// List of ports to connect to. pub connections: Vec, } -has!(Jack<'static>: |self: AudioOutput|self.jack); +impl HasJack<'static> for AudioOutput { + fn jack (&self) -> &Jack<'static> { + &self.jack + } +} impl JackPort for AudioOutput { type Port = AudioOut; type Pair = AudioIn; - fn name (&self) -> &Arc { + fn port_name (&self) -> &Arc { &self.name } fn port (&self) -> &Port { diff --git a/crates/device/src/port/port_connect.rs b/crates/device/src/port/port_connect.rs deleted file mode 100644 index cd0a9192..00000000 --- a/crates/device/src/port/port_connect.rs +++ /dev/null @@ -1,181 +0,0 @@ -use crate::*; - -pub trait JackPort: HasJack<'static> { - type Port: PortSpec + Default; - type Pair: PortSpec + Default; - fn new (jack: &Jack<'static>, name: &impl AsRef, connect: &[Connect]) - -> Usually where Self: Sized; - fn register (jack: &Jack<'static>, name: &impl AsRef) -> Usually> { - Ok(jack.register_port::(name.as_ref())?) - } - fn name (&self) -> &Arc; - fn connections (&self) -> &[Connect]; - fn port (&self) -> &Port; - fn port_mut (&mut self) -> &mut Port; - fn into_port (self) -> Port where Self: Sized; - fn close (self) -> Usually<()> where Self: Sized { - let jack = self.jack().clone(); - Ok(jack.with_client(|c|c.unregister_port(self.into_port()))?) - } - fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec { - self.with_client(|c|c.ports(re_name, re_type, flags)) - } - fn port_by_id (&self, id: u32) -> Option> { - self.with_client(|c|c.port_by_id(id)) - } - fn port_by_name (&self, name: impl AsRef) -> Option> { - self.with_client(|c|c.port_by_name(name.as_ref())) - } - fn connect_to_matching <'k> (&'k self) -> Usually<()> { - for connect in self.connections().iter() { - //panic!("{connect:?}"); - let status = match &connect.name { - Exact(name) => self.connect_exact(name), - RegExp(re) => self.connect_regexp(re, connect.scope), - }?; - *connect.status.write().unwrap() = status; - } - Ok(()) - } - fn connect_exact <'k> (&'k self, name: &str) -> - Usually, Arc, ConnectStatus)>> - { - self.with_client(move|c|{ - let mut status = vec![]; - for port in c.ports(None, None, PortFlags::empty()).iter() { - if port.as_str() == &*name { - if let Some(port) = c.port_by_name(port.as_str()) { - let port_status = self.connect_to_unowned(&port)?; - let name = port.name()?.into(); - status.push((port, name, port_status)); - if port_status == Connected { - break - } - } - } - } - Ok(status) - }) - } - fn connect_regexp <'k> ( - &'k self, re: &str, scope: ConnectScope - ) -> Usually, Arc, ConnectStatus)>> { - self.with_client(move|c|{ - let mut status = vec![]; - let ports = c.ports(Some(&re), None, PortFlags::empty()); - for port in ports.iter() { - if let Some(port) = c.port_by_name(port.as_str()) { - let port_status = self.connect_to_unowned(&port)?; - let name = port.name()?.into(); - status.push((port, name, port_status)); - if port_status == Connected && scope == One { - break - } - } - } - Ok(status) - }) - } - /** Connect to a matching port by name. */ - fn connect_to_name (&self, name: impl AsRef) -> Usually { - self.with_client(|c|if let Some(ref port) = c.port_by_name(name.as_ref()) { - self.connect_to_unowned(port) - } else { - Ok(Missing) - }) - } - /** Connect to a matching port by reference. */ - fn connect_to_unowned (&self, port: &Port) -> Usually { - self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) { - Connected - } else if let Ok(_) = c.connect_ports(port, self.port()) { - Connected - } else { - Mismatch - })) - } - /** Connect to an owned matching port by reference. */ - fn connect_to_owned (&self, port: &Port) -> Usually { - self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) { - Connected - } else if let Ok(_) = c.connect_ports(port, self.port()) { - Connected - } else { - Mismatch - })) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum ConnectName { - /** Exact match */ - Exact(Arc), - /** Match regular expression */ - RegExp(Arc), -} - -#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectScope { - One, - All -} - -#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectStatus { - Missing, - Disconnected, - Connected, - Mismatch, -} - -#[derive(Clone, Debug)] pub struct Connect { - pub name: ConnectName, - pub scope: ConnectScope, - pub status: Arc, Arc, ConnectStatus)>>>, - pub info: Arc, -} - -impl Connect { - pub fn collect (exact: &[impl AsRef], re: &[impl AsRef], re_all: &[impl AsRef]) - -> Vec - { - let mut connections = vec![]; - for port in exact.iter() { connections.push(Self::exact(port)) } - for port in re.iter() { connections.push(Self::regexp(port)) } - for port in re_all.iter() { connections.push(Self::regexp_all(port)) } - connections - } - /// Connect to this exact port - pub fn exact (name: impl AsRef) -> Self { - let info = format!("=:{}", name.as_ref()).into(); - let name = Exact(name.as_ref().into()); - Self { name, scope: One, status: Arc::new(RwLock::new(vec![])), info } - } - pub fn regexp (name: impl AsRef) -> Self { - let info = format!("~:{}", name.as_ref()).into(); - let name = RegExp(name.as_ref().into()); - Self { name, scope: One, status: Arc::new(RwLock::new(vec![])), info } - } - pub fn regexp_all (name: impl AsRef) -> Self { - let info = format!("+:{}", name.as_ref()).into(); - let name = RegExp(name.as_ref().into()); - Self { name, scope: All, status: Arc::new(RwLock::new(vec![])), info } - } - pub fn info (&self) -> Arc { - let status = { - let status = self.status.read().unwrap(); - let mut ok = 0; - for (_, _, state) in status.iter() { - if *state == Connected { - ok += 1 - } - } - format!("{ok}/{}", status.len()) - }; - let scope = match self.scope { - One => " ", All => "*", - }; - let name = match &self.name { - Exact(name) => format!("= {name}"), RegExp(name) => format!("~ {name}"), - }; - format!(" ({}) {} {}", status, scope, name).into() - } -} diff --git a/crates/device/src/port/port_midi_in.rs b/crates/device/src/port/port_midi_in.rs index d3a3d91b..23c535c5 100644 --- a/crates/device/src/port/port_midi_in.rs +++ b/crates/device/src/port/port_midi_in.rs @@ -14,11 +14,15 @@ use crate::*; /// List of ports to connect to. pub connections: Vec, } -has!(Jack<'static>: |self: MidiInput|self.jack); +impl HasJack<'static> for MidiInput { + fn jack (&self) -> &Jack<'static> { + &self.jack + } +} impl JackPort for MidiInput { type Port = MidiIn; type Pair = MidiOut; - fn name (&self) -> &Arc { + fn port_name (&self) -> &Arc { &self.name } fn port (&self) -> &Port { @@ -85,7 +89,7 @@ pub trait HasMidiIns { let mut y = 0; self.midi_ins().iter().enumerate().map(move|(i, input)|{ let height = 1 + input.connections().len(); - let data = (i, input.name(), input.connections(), y, y + height); + let data = (i, input.port_name(), input.connections(), y, y + height); y += height; data }) diff --git a/crates/device/src/port/port_midi_out.rs b/crates/device/src/port/port_midi_out.rs index ecfc2e0b..b27978bb 100644 --- a/crates/device/src/port/port_midi_out.rs +++ b/crates/device/src/port/port_midi_out.rs @@ -16,11 +16,15 @@ use crate::*; /// Buffer output_buffer: Vec>>, } -has!(Jack<'static>: |self: MidiOutput|self.jack); +impl HasJack<'static> for MidiOutput { + fn jack (&self) -> &Jack<'static> { + &self.jack + } +} impl JackPort for MidiOutput { type Port = MidiOut; type Pair = MidiIn; - fn name (&self) -> &Arc { + fn port_name (&self) -> &Arc { &self.name } fn port (&self) -> &Port { @@ -120,7 +124,7 @@ pub trait HasMidiOuts { let mut y = 0; self.midi_outs().iter().enumerate().map(move|(i, output)|{ let height = 1 + output.connections().len(); - let data = (i, output.name(), output.connections(), y, y + height); + let data = (i, output.port_name(), output.connections(), y, y + height); y += height; data }) diff --git a/crates/engine/src/jack.rs b/crates/engine/src/jack.rs index 1691e13d..7e5214aa 100644 --- a/crates/engine/src/jack.rs +++ b/crates/engine/src/jack.rs @@ -1,12 +1,130 @@ use crate::*; pub use ::jack::{*, contrib::{*, ClosureProcessHandler}}; -pub(crate) use std::sync::{Arc, RwLock}; +/// Wraps [JackState] and through it [jack::Client] when connected. +#[derive(Clone, Debug, Default)] +pub struct Jack<'j>(Arc>>); +/// Implement [Jack] constructor and methods +impl<'j> Jack<'j> { + /// Register new [Client] and wrap it for shared use. + pub fn new_run + Audio + Send + Sync + 'static> ( + name: &impl AsRef, + init: impl FnOnce(Jack<'j>)->Usually + ) -> Usually>> { + Jack::new(name)?.run(init) + } + pub fn new (name: &impl AsRef) -> Usually { + let client = Client::new(name.as_ref(), ClientOptions::NO_START_SERVER)?.0; + Ok(Jack(Arc::new(RwLock::new(JackState::Inactive(client))))) + } + pub fn run + Audio + Send + Sync + 'static> + (self, init: impl FnOnce(Self)->Usually) -> Usually>> + { + let client_state = self.0.clone(); + let app: Arc> = Arc::new(RwLock::new(init(self)?)); + let mut state = Activating; + std::mem::swap(&mut*client_state.write().unwrap(), &mut state); + if let Inactive(client) = state { + let client = client.activate_async( + // This is the misc notifications handler. It's a struct that wraps a [Box] + // which performs type erasure on a callback that takes [JackEvent], which is + // one of the available misc notifications. + Notifications(Box::new({ + let app = app.clone(); + move|event|(&mut*app.write().unwrap()).handle(event) + }) as BoxedJackEventHandler), + // This is the main processing handler. It's a struct that wraps a [Box] + // which performs type erasure on a callback that takes [Client] and [ProcessScope] + // and passes them down to the `app`'s `process` callback, which in turn + // implements audio and MIDI input and output on a realtime basis. + ClosureProcessHandler::new(Box::new({ + let app = app.clone(); + move|c: &_, s: &_|if let Ok(mut app) = app.write() { + app.process(c, s) + } else { + Control::Quit + } + }) as BoxedAudioHandler), + )?; + *client_state.write().unwrap() = Active(client); + } else { + unreachable!(); + } + Ok(app) + } + /// Run something with the client. + pub fn with_client (&self, op: impl FnOnce(&Client)->T) -> T { + match &*self.0.read().unwrap() { + Inert => panic!("jack client not activated"), + Inactive(ref client) => op(client), + Activating => panic!("jack client has not finished activation"), + Active(ref client) => op(client.as_client()), + } + } +} +impl<'j> HasJack<'j> for Jack<'j> { + fn jack (&self) -> &Jack<'j> { + self + } +} +impl<'j> HasJack<'j> for &Jack<'j> { + fn jack (&self) -> &Jack<'j> { + self + } +} +/// 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. +#[derive(Debug, Default)] +pub enum JackState<'j> { + /// Unused + #[default] Inert, + /// Before activation. + Inactive(Client), + /// During activation. + Activating, + /// After activation. Must not be dropped for JACK thread to persist. + Active(DynamicAsyncClient<'j>), +} +/// Things that can provide a [jack::Client] reference. +pub trait HasJack<'j>: Send + Sync { + /// Return the internal [jack::Client] handle + /// that lets you call the JACK API. + fn jack (&self) -> &Jack<'j>; + fn with_client (&self, op: impl FnOnce(&Client)->T) -> T { + self.jack().with_client(op) + } + fn port_by_name (&self, name: &str) -> Option> { + self.with_client(|client|client.port_by_name(name)) + } + fn port_by_id (&self, id: u32) -> Option> { + self.with_client(|c|c.port_by_id(id)) + } + fn register_port (&self, name: impl AsRef) -> Usually> { + self.with_client(|client|Ok(client.register_port(name.as_ref(), PS::default())?)) + } + fn sync_lead (&self, enable: bool, callback: impl Fn(TimebaseInfo)->Position) -> Usually<()> { + if enable { + self.with_client(|client|match client.register_timebase_callback(false, callback) { + Ok(_) => Ok(()), + Err(e) => Err(e) + })? + } + Ok(()) + } + fn sync_follow (&self, _enable: bool) -> Usually<()> { + // TODO: sync follow + Ok(()) + } +} /// Trait for thing that has a JACK process callback. pub trait Audio { + /// Handle a JACK event. fn handle (&mut self, _event: JackEvent) {} + /// Projecss a JACK chunk. fn process (&mut self, _: &Client, _: &ProcessScope) -> Control { Control::Continue } + /// The JACK process callback function passed to the server. fn callback ( state: &Arc>, client: &Client, scope: &ProcessScope ) -> Control where Self: Sized { @@ -17,7 +135,6 @@ pub trait Audio { } } } - /// Implement [Audio]: provide JACK callbacks. #[macro_export] macro_rules! audio { (| @@ -30,7 +147,6 @@ pub trait Audio { } } } - /// Event enum for JACK events. #[derive(Debug, Clone, PartialEq)] pub enum JackEvent { ThreadInit, @@ -44,7 +160,6 @@ pub trait Audio { GraphReorder, XRun, } - /// Generic notification handler that emits [JackEvent] pub struct Notifications(pub T); @@ -105,121 +220,3 @@ pub type BoxedJackEventHandler<'j> = Box; use self::JackState::*; -impl<'j, T: Has>> HasJack<'j> for T { - fn jack (&self) -> &Jack<'j> { self.get() } -} -impl<'j> HasJack<'j> for Jack<'j> { - fn jack (&self) -> &Jack<'j> { self } -} -impl<'j> HasJack<'j> for &Jack<'j> { - fn jack (&self) -> &Jack<'j> { self } -} - -/// Things that can provide a [jack::Client] reference. -pub trait HasJack<'j> { - /// Return the internal [jack::Client] handle - /// that lets you call the JACK API. - fn jack (&self) -> &Jack<'j>; - /// Run the JACK thread. - fn run ( - &self, callback: impl FnOnce(&Jack)->Usually - ) -> Usually>> { - let jack = self.jack(); - let app = Arc::new(RwLock::new(callback(jack)?)); - let mut state = Activating; - std::mem::swap(&mut*jack.state.write().unwrap(), &mut state); - if let Inactive(client) = state { - let client = client.activate_async( - // This is the misc notifications handler. It's a struct that wraps a [Box] - // which performs type erasure on a callback that takes [JackEvent], which is - // one of the available misc notifications. - Notifications(Box::new({ - let app = app.clone(); - move|event|app.write().unwrap().handle(event) - }) as BoxedJackEventHandler), - // This is the main processing handler. It's a struct that wraps a [Box] - // which performs type erasure on a callback that takes [Client] and [ProcessScope] - // and passes them down to the `app`'s `process` callback, which in turn - // implements audio and MIDI input and output on a realtime basis. - ClosureProcessHandler::new(Box::new({ - let app = app.clone(); - move|c: &_, s: &_|if let Ok(mut app) = app.write() { - app.process(c, s) - } else { - Control::Quit - } - }) as BoxedAudioHandler), - )?; - *jack.state.write().unwrap() = Active(client); - } else { - unreachable!(); - } - Ok(app) - } - /// Run something with the client. - fn with_client (&self, op: impl FnOnce(&Client)->T) -> T { - match &*self.jack().state.read().unwrap() { - Inert => panic!("jack client not activated"), - Inactive(ref client) => op(client), - Activating => panic!("jack client has not finished activation"), - Active(ref client) => op(client.as_client()), - } - } - fn port_by_name (&self, name: &str) -> Option> { - self.with_client(|client|client.port_by_name(name)) - } - fn port_by_id (&self, id: u32) -> Option> { - self.with_client(|c|c.port_by_id(id)) - } - fn register_port (&self, name: impl AsRef) -> Usually> { - self.with_client(|client|Ok(client.register_port(name.as_ref(), PS::default())?)) - } - fn sync_lead (&self, enable: bool, callback: impl Fn(TimebaseInfo)->Position) -> Usually<()> { - if enable { - self.with_client(|client|match client.register_timebase_callback(false, callback) { - Ok(_) => Ok(()), - Err(e) => Err(e) - })? - } - Ok(()) - } - fn sync_follow (&self, _enable: bool) -> Usually<()> { - // TODO: sync follow - Ok(()) - } -} - -/// Wraps [JackState] and through it [jack::Client]. -#[derive(Clone, Debug, Default)] -pub struct Jack<'j> { - pub state: Arc>> -} - -impl<'j> Jack<'j> { - pub fn new (name: &str) -> Usually { - Ok(Self { - state: JackState::new(Client::new(name, ClientOptions::NO_START_SERVER)?.0) - }) - } -} - -/// 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. -#[derive(Debug, Default)] -pub enum JackState<'j> { - /// Unused - #[default] Inert, - /// Before activation. - Inactive(Client), - /// During activation. - Activating, - /// After activation. Must not be dropped for JACK thread to persist. - Active(DynamicAsyncClient<'j>), -} - -impl<'j> JackState<'j> { - fn new (client: Client) -> Arc> { - Arc::new(RwLock::new(Self::Inactive(client))) - } -} diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 8ecfdb75..da077e32 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -89,7 +89,7 @@ mod note; pub use self::note::*; pub mod jack; pub use self::jack::*; pub mod midi; pub use self::midi::*; -pub(crate) use std::sync::{Arc, atomic::{AtomicUsize, AtomicBool, Ordering::Relaxed}}; +pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, AtomicBool, Ordering::Relaxed}}; pub(crate) use std::fmt::Debug; pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem}; diff --git a/deps/tengri b/deps/tengri index f714302f..2048dd22 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit f714302f21d64ef5f6dcbee09aa913e8e3b9fbc5 +Subproject commit 2048dd2263fc4389c8f510bfd12c851efe34026f