From 447638ee7157d19c0d002824c9db181a10f69782 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 20 May 2025 22:05:09 +0300 Subject: [PATCH] wip: general overhaul of core and ports --- crates/app/src/api.rs | 7 - crates/app/src/model.rs | 22 ++- crates/app/src/view.rs | 38 +++-- crates/cli/tek.rs | 4 +- crates/device/src/arranger.rs | 4 +- crates/device/src/arranger/arranger_api.rs | 23 +-- crates/device/src/arranger/arranger_model.rs | 71 +++++--- crates/device/src/clock/clock_model.rs | 12 +- crates/device/src/device.rs | 8 +- crates/device/src/dialog/dialog_view.rs | 35 ++-- crates/device/src/pool/pool_api.rs | 3 - crates/device/src/pool/pool_model.rs | 6 +- crates/device/src/sampler/sampler_api.rs | 16 +- crates/device/src/sampler/sampler_audio.rs | 34 ++-- crates/device/src/sampler/sampler_model.rs | 18 +- crates/device/src/sampler/sampler_view.rs | 6 +- crates/device/src/sequencer/seq_model.rs | 12 +- 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 | 13 +- crates/engine/src/jack/jack_client.rs | 165 ++++++------------- crates/engine/src/jack/jack_device.rs | 160 +++++++++--------- crates/engine/src/jack/jack_event.rs | 56 ------- crates/engine/src/jack/jack_handler.rs | 105 ++++++++++++ crates/engine/src/jack/jack_port.rs | 134 ++++----------- crates/engine/src/lib.rs | 85 ++++++++++ crates/engine/src/midi/midi_in.rs | 116 +++++++++++-- crates/engine/src/midi/midi_out.rs | 52 +++--- deps/tengri | 2 +- 30 files changed, 824 insertions(+), 548 deletions(-) create mode 100644 crates/engine/src/audio.rs create mode 100644 crates/engine/src/audio/audio_in.rs create mode 100644 crates/engine/src/audio/audio_out.rs delete mode 100644 crates/engine/src/jack/jack_event.rs create mode 100644 crates/engine/src/jack/jack_handler.rs diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index 39902a89..d0ff1dd0 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -112,10 +112,3 @@ impl AppCommand { //Ok(None) //} } - -dsl!(ClockCommand: |self: App, iter|self.clock().take(iter)); -dsl!(MidiEditCommand: |self: App, iter|Ok(self.editor().map(|x|x.take(iter)).transpose()?.flatten())); -dsl!(PoolCommand: |self: App, iter|self.pool.take(iter)); -dsl!(SamplerCommand: |self: App, iter|Ok(self.project.sampler().map(|x|x.take(iter)).transpose()?.flatten())); -dsl!(ArrangementCommand: |self: App, iter|self.project.take(iter)); -dsl!(DialogCommand: |self: App, iter|Dsl::take(&self.dialog, iter)); diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index f90f5978..e81da8da 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -33,8 +33,8 @@ 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); @@ -54,6 +54,18 @@ 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)); //has_editor!(|self: App|{ //editor = self.editor; //editor_w = { @@ -332,7 +344,7 @@ pub struct Configuration { /// View definition pub view: TokenIter<'static>, // Input keymap - pub keys: InputMap>, + pub keys: InputMap<'static, App, AppCommand, TuiIn, TokenIter<'static>>, } impl Configuration { @@ -406,7 +418,7 @@ impl Configuration { } fn parse_keys (base: &impl AsRef, iter: Option>) - -> Usually>> + -> Usually>> { if iter.is_none() { return Err(format!("missing keys definition").into()) @@ -452,7 +464,7 @@ impl Configuration { let cond = cond.unwrap(); println!("ok"); map.add_layer_if( - Box::new(move |state: &App|Dsl::take_or_fail( + Box::new(move |state: &App|FromDsl::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 5e4f3d3d..a30ae1ef 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -4,22 +4,30 @@ pub(crate) use ::tengri::tui::ratatui::prelude::Position; impl App { pub fn view (&self) -> impl Content + '_ { - let view: Perhaps> = + let view: Perhaps>> = FromDsl::take_from(self, &mut self.config.view.clone()); - self.size.of(lay! { - When(matches!(&view, Ok(None)), Fill::y(Bsp::s( - Fixed::y(1, "no view"), - Fill::y(format!("{:?}", &self.config.view)), - ))), - When(matches!(&view, Err(_)), Fill::y(Bsp::s( - Fixed::y(1, "view error"), - Fill::y(Bsp::s( - view.as_ref().err().map(|e|format!("{e}")), - format!("{:?}", &self.config.view), - )) - ))), - When(matches!(&view, Ok(Some(_))), Fill::y(view)), - }) + Either(view.is_ok(), + ThunkRender::new(move|to|if let Some(view) = view.as_ref().unwrap().as_ref() { + Content::render(view, to) + }), + "error?!") + //Fill::xy(self.size.of(col! { + //Tui::bg(Rgb(72,72,0), When(matches!(&view, Err(_)), Bsp::s( + //Fixed::y(1, "view error"), + //Bsp::s( + //view.as_ref().err().map(|e|format!("{e}")), + //format!("{}", &self.config.view.0.0))))), + //When(matches!(&view, Ok(None)), Bsp::s( + //Tui::bg(Rgb(96,48,0), + //Fixed::y(1, Fill::x(Align::w("no view returned, as defined by:")))), + //Tui::bg(Rgb(72,32,0), + //Fill::x(Stack::south(|add: &mut dyn FnMut(&dyn Render)|{ + //for line in self.config.view.0.0.split('\n') { + //add(&Fill::x(Align::w(line))); + //} + //}))))), + //When(matches!(&view, Ok(Some(_))), &view.unwrap().unwrap()), + //})) } } diff --git a/crates/cli/tek.rs b/crates/cli/tek.rs index 32a10df7..fd580d00 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -91,11 +91,11 @@ impl Cli { )); Tui::new()?.run(&Jack::new(name)?.run(|jack|{ for (index, connect) in midi_froms.iter().enumerate() { - let port = JackMidiIn::new(jack, &format!("M/{index}"), &[connect.clone()])?; + let port = MidiInput::new(jack, &format!("M/{index}"), &[connect.clone()])?; midi_ins.push(port); } for (index, connect) in midi_tos.iter().enumerate() { - let port = JackMidiOut::new(jack, &format!("{index}/M"), &[connect.clone()])?; + let port = MidiOutput::new(jack, &format!("{index}/M"), &[connect.clone()])?; midi_outs.push(port); }; let config = Configuration::new(&match self.mode { diff --git a/crates/device/src/arranger.rs b/crates/device/src/arranger.rs index 94a7f295..a3e25c42 100644 --- a/crates/device/src/arranger.rs +++ b/crates/device/src/arranger.rs @@ -18,8 +18,8 @@ mod arranger_view; pub use self::arranger_view::*; def_sizes_iter!(ScenesSizes => Scene); def_sizes_iter!(TracksSizes => Track); -def_sizes_iter!(InputsSizes => JackMidiIn); -def_sizes_iter!(OutputsSizes => JackMidiOut); +def_sizes_iter!(InputsSizes => MidiInput); +def_sizes_iter!(OutputsSizes => MidiOutput); def_sizes_iter!(PortsSizes => Arc, [PortConnect]); pub(crate) fn wrap (bg: Color, fg: Color, content: impl Content) -> impl Content { diff --git a/crates/device/src/arranger/arranger_api.rs b/crates/device/src/arranger/arranger_api.rs index 2feaae37..10b79cd2 100644 --- a/crates/device/src/arranger/arranger_api.rs +++ b/crates/device/src/arranger/arranger_api.rs @@ -1,23 +1,4 @@ use crate::*; - -#[tengri_proc::expose] -impl Arrangement { - fn _todo_usize_stub_ (&self) -> usize { todo!() } - fn _todo_arc_str_stub_ (&self) -> Arc { todo!() } - fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } - fn _todo_opt_item_theme_stub (&self) -> Option { todo!() } - fn select_nothing (&self) -> Selection { - Selection::Nothing - } -} - -dsl!(TrackCommand: |self: Arrangement, iter|self.take(iter)); -dsl!(MidiInputCommand: |self: Arrangement, iter|self.take(iter)); -dsl!(MidiOutputCommand: |self: Arrangement, iter|self.take(iter)); -dsl!(DeviceCommand: |self: Arrangement, iter|self.take(iter)); -dsl!(SceneCommand: |self: Arrangement, iter|self.take(iter)); -dsl!(ClipCommand: |self: Arrangement, iter|self.take(iter)); - #[tengri_proc::command(Arrangement)] impl ArrangementCommand { fn home (arranger: &mut Arrangement) -> Perhaps { @@ -173,7 +154,7 @@ impl ArrangementCommand { Ok(None) } fn output_add (arranger: &mut Arrangement) -> Perhaps { - arranger.midi_outs.push(JackMidiOut::new( + arranger.midi_outs.push(MidiOutput::new( arranger.jack(), format!("/M{}", arranger.midi_outs.len() + 1), &[] @@ -181,7 +162,7 @@ impl ArrangementCommand { Ok(None) } fn input_add (arranger: &mut Arrangement) -> Perhaps { - arranger.midi_ins.push(JackMidiIn::new( + arranger.midi_ins.push(MidiInput::new( arranger.jack(), format!("M{}/", arranger.midi_ins.len() + 1), &[] diff --git a/crates/device/src/arranger/arranger_model.rs b/crates/device/src/arranger/arranger_model.rs index 87c8ce04..a11032fb 100644 --- a/crates/device/src/arranger/arranger_model.rs +++ b/crates/device/src/arranger/arranger_model.rs @@ -1,7 +1,47 @@ use crate::*; - -#[derive(Default, Debug)] -pub struct Arrangement { +has!(Jack: |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.scenes); +has!(Vec: |self: Arrangement|self.tracks); +has!(Measure: |self: Arrangement|self.size); +has!(Option: |self: Arrangement|self.editor); +maybe_has!(Track: |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() }); +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())); +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())); +#[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 _todo_usize_stub_ (&self) -> usize { todo!() } + fn _todo_arc_str_stub_ (&self) -> Arc { todo!() } + fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } + fn _todo_opt_item_theme_stub (&self) -> Option { todo!() } + fn select_nothing (&self) -> Selection { + Selection::Nothing + } +} +#[derive(Default, Debug)] pub struct Arrangement { /// Project name. pub name: Arc, /// Base color. @@ -13,13 +53,13 @@ pub struct Arrangement { /// Allows one MIDI clip to be edited pub editor: Option, /// List of global midi inputs - pub midi_ins: Vec, + pub midi_ins: Vec, /// List of global midi outputs - pub midi_outs: Vec, + pub midi_outs: Vec, /// List of global audio inputs - pub audio_ins: Vec, + pub audio_ins: Vec, /// List of global audio outputs - pub audio_outs: Vec, + pub audio_outs: Vec, /// Last track number (to avoid duplicate port names) pub track_last: usize, /// List of tracks @@ -40,23 +80,6 @@ pub struct Arrangement { /// Display size of clips area pub inner_size: Measure, } - -has!(Jack: |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.scenes); -has!(Vec: |self: Arrangement|self.tracks); -has!(Measure: |self: Arrangement|self.size); -has!(Option: |self: Arrangement|self.editor); -maybe_has!(Track: |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() }); -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() }); - impl Arrangement { /// Width of display pub fn w (&self) -> u16 { diff --git a/crates/device/src/clock/clock_model.rs b/crates/device/src/clock/clock_model.rs index c961233a..ef1bff86 100644 --- a/crates/device/src/clock/clock_model.rs +++ b/crates/device/src/clock/clock_model.rs @@ -21,11 +21,11 @@ pub struct Clock { /// Size of buffer in samples pub chunk: Arc, /// For syncing the clock to an external source - pub midi_in: Arc>>, + pub midi_in: Arc>>, /// For syncing other devices to this clock - pub midi_out: Arc>>, + pub midi_out: Arc>>, /// For emitting a metronome - pub click_out: Arc>>, + pub click_out: Arc>>, } impl std::fmt::Debug for Clock { @@ -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(JackMidiIn::new(jack, "M/clock", &[])?))), - midi_out: Arc::new(RwLock::new(Some(JackMidiOut::new(jack, "clock/M", &[])?))), - click_out: Arc::new(RwLock::new(Some(JackAudioOut::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/device.rs b/crates/device/src/device.rs index 894aa2d2..30ae4366 100644 --- a/crates/device/src/device.rs +++ b/crates/device/src/device.rs @@ -44,25 +44,25 @@ impl Device { _ => todo!(), } } - pub fn midi_ins (&self) -> &[JackMidiIn] { + pub fn midi_ins (&self) -> &[MidiInput] { match self { //Self::Sampler(Sampler { midi_in, .. }) => &[midi_in], _ => todo!() } } - pub fn midi_outs (&self) -> &[JackMidiOut] { + pub fn midi_outs (&self) -> &[MidiOutput] { match self { Self::Sampler(_) => &[], _ => todo!() } } - pub fn audio_ins (&self) -> &[JackAudioIn] { + pub fn audio_ins (&self) -> &[AudioInput] { match self { Self::Sampler(Sampler { audio_ins, .. }) => audio_ins.as_slice(), _ => todo!() } } - pub fn audio_outs (&self) -> &[JackAudioOut] { + pub fn audio_outs (&self) -> &[AudioOutput] { match self { Self::Sampler(Sampler { audio_outs, .. }) => audio_outs.as_slice(), _ => todo!() diff --git a/crates/device/src/dialog/dialog_view.rs b/crates/device/src/dialog/dialog_view.rs index dadeadbb..5f3a7ec0 100644 --- a/crates/device/src/dialog/dialog_view.rs +++ b/crates/device/src/dialog/dialog_view.rs @@ -1,19 +1,24 @@ use crate::*; -content!(TuiOut: |self: Dialog| match self { - Self::Menu(_) => - self.view_dialog_menu().boxed(), - Self::Help(offset) => - self.view_dialog_help(*offset).boxed(), - Self::Browser(target, browser) => - self.view_dialog_browser(target, browser).boxed(), - Self::Options => - self.view_dialog_options().boxed(), - Self::Device(index) => - self.view_dialog_device(*index).boxed(), - Self::Message(message) => - self.view_dialog_message(message).boxed(), -}); +impl Content for Dialog { + fn content (&self) -> impl Render + '_ { + Some(match self { + Self::Menu(_) => self.view_dialog_menu().boxed(), + _ => "kyp".boxed() + }) + //Self::Help(offset) => + //self.view_dialog_help(*offset).boxed(), + //Self::Browser(target, browser) => + //self.view_dialog_browser(target, browser).boxed(), + //Self::Options => + //self.view_dialog_options().boxed(), + //Self::Device(index) => + //self.view_dialog_device(*index).boxed(), + //Self::Message(message) => + //self.view_dialog_message(message).boxed(), + //}) + } +} content!(TuiOut: |self: Message| match self { Self::FailedToAddDevice => "Failed to add device." @@ -25,7 +30,7 @@ impl Dialog { let option = |a,i|Tui::fg(Rgb(255,255,255), format!("{}", a)); Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option))) } - pub fn view_dialog_help <'a> (&'a self, offset: usize) -> impl Content + use<'a> { + pub fn view_dialog_help <'a> (&'a self, offset: usize) -> impl Content + 'a { Bsp::s(Tui::bold(true, "Help"), "FIXME") //Bsp::s(Tui::bold(true, "Help"), Bsp::s("", Map::south(1, //move||self.config.keys.layers.iter() diff --git a/crates/device/src/pool/pool_api.rs b/crates/device/src/pool/pool_api.rs index 6f9d8b90..90edd252 100644 --- a/crates/device/src/pool/pool_api.rs +++ b/crates/device/src/pool/pool_api.rs @@ -81,9 +81,6 @@ impl PoolCommand { } -dsl!(BrowserCommand: |self: Pool, iter|Ok(self.browser - .as_ref().map(|p|p.take(iter)).transpose()?.flatten())); - #[tengri_proc::command(Pool)] impl PoolClipCommand { diff --git a/crates/device/src/pool/pool_model.rs b/crates/device/src/pool/pool_model.rs index f6034bf3..40ac6632 100644 --- a/crates/device/src/pool/pool_model.rs +++ b/crates/device/src/pool/pool_model.rs @@ -12,7 +12,11 @@ pub struct Pool { /// Embedded file browser pub browser: Option, } - +from_dsl!(BrowserCommand: |state: Pool, iter|Ok(state.browser + .as_ref() + .map(|p|FromDsl::take_from(p, iter)) + .transpose()? + .flatten())); impl Default for Pool { fn default () -> Self { use PoolMode::*; diff --git a/crates/device/src/sampler/sampler_api.rs b/crates/device/src/sampler/sampler_api.rs index d7b391f7..045398e1 100644 --- a/crates/device/src/sampler/sampler_api.rs +++ b/crates/device/src/sampler/sampler_api.rs @@ -63,21 +63,17 @@ impl SamplerCommand { fn record_begin (sampler: &mut Sampler, slot: usize) -> Perhaps { sampler.recording = Some(( slot, - Arc::new(RwLock::new(Sample::new( + Some(Arc::new(RwLock::new(Sample::new( "Sample", 0, 0, vec![vec![];sampler.audio_ins.len()] - ))) + )))) )); Ok(None) } fn record_finish (sampler: &mut Sampler) -> Perhaps { - let recording = sampler.recording.take(); - let _sample = if let Some((index, sample)) = recording { - let old = sampler.mapped[index].clone(); - sampler.mapped[index] = Some(sample); - old - } else { - None - }; + let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{ + std::mem::swap(sample, &mut sampler.mapped[*index]); + sample + }); // TODO: undo Ok(None) } fn record_cancel (sampler: &mut Sampler) -> Perhaps { diff --git a/crates/device/src/sampler/sampler_audio.rs b/crates/device/src/sampler/sampler_audio.rs index 87d0a78e..ec1c08e8 100644 --- a/crates/device/src/sampler/sampler_audio.rs +++ b/crates/device/src/sampler/sampler_audio.rs @@ -28,23 +28,25 @@ impl Sampler { /// Record from inputs to sample fn record_into (&mut self, scope: &ProcessScope) { - let mut sample = self.recording - .as_mut().expect("no recording sample").1 - .write().unwrap(); - if sample.channels.len() != self.audio_ins.len() { - panic!("channel count mismatch"); + if let Some(ref sample) = self.recording.as_ref().expect("no recording sample").1 { + let mut sample = sample.write().unwrap(); + if sample.channels.len() != self.audio_ins.len() { + panic!("channel count mismatch"); + } + let samples_with_meters = self.audio_ins.iter() + .zip(self.input_meters.iter_mut()) + .zip(sample.channels.iter_mut()); + let mut length = 0; + for ((input, meter), channel) in samples_with_meters { + let slice = input.port().as_slice(scope); + length = length.max(slice.len()); + *meter = to_rms(slice); + channel.extend_from_slice(slice); + } + sample.end += length; + } else { + panic!("tried to record into the void") } - let samples_with_meters = self.audio_ins.iter() - .zip(self.input_meters.iter_mut()) - .zip(sample.channels.iter_mut()); - let mut length = 0; - for ((input, meter), channel) in samples_with_meters { - let slice = input.port().as_slice(scope); - length = length.max(slice.len()); - *meter = to_rms(slice); - channel.extend_from_slice(slice); - } - sample.end += length; } /// Update input meters diff --git a/crates/device/src/sampler/sampler_model.rs b/crates/device/src/sampler/sampler_model.rs index fcd5d0ac..937d4208 100644 --- a/crates/device/src/sampler/sampler_model.rs +++ b/crates/device/src/sampler/sampler_model.rs @@ -8,11 +8,11 @@ pub struct Sampler { /// Device color. pub color: ItemTheme, /// Audio input ports. Samples get recorded here. - pub audio_ins: Vec, + pub audio_ins: Vec, /// Audio input meters. pub input_meters: Vec, /// Sample currently being recorded. - pub recording: Option<(usize, Arc>)>, + pub recording: Option<(usize, Option>>)>, /// Recording buffer. pub buffer: Vec>, /// Samples mapped to MIDI notes. @@ -22,11 +22,11 @@ pub struct Sampler { /// Sample currently being edited. pub editing: Option>>, /// MIDI input port. Triggers sample playback. - pub midi_in: JackMidiIn, + pub midi_in: MidiInput, /// Collection of currently playing instances of samples. pub voices: Arc>>, /// Audio output ports. Voices get played here. - pub audio_outs: Vec, + pub audio_outs: Vec, /// Audio output meters. pub output_meters: Vec, /// How to mix the voices. @@ -58,14 +58,14 @@ impl Sampler { let name = name.as_ref(); Ok(Self { name: name.into(), - midi_in: JackMidiIn::new(jack, format!("M/{name}"), midi_from)?, + midi_in: MidiInput::new(jack, format!("M/{name}"), midi_from)?, audio_ins: vec![ - JackAudioIn::new(jack, &format!("L/{name}"), audio_from[0])?, - JackAudioIn::new(jack, &format!("R/{name}"), audio_from[1])?, + AudioInput::new(jack, &format!("L/{name}"), audio_from[0])?, + AudioInput::new(jack, &format!("R/{name}"), audio_from[1])?, ], audio_outs: vec![ - JackAudioOut::new(jack, &format!("{name}/L"), audio_to[0])?, - JackAudioOut::new(jack, &format!("{name}/R"), audio_to[1])?, + AudioOutput::new(jack, &format!("{name}/L"), audio_to[0])?, + AudioOutput::new(jack, &format!("{name}/R"), audio_to[1])?, ], input_meters: vec![0.0;2], output_meters: vec![0.0;2], diff --git a/crates/device/src/sampler/sampler_view.rs b/crates/device/src/sampler/sampler_view.rs index 7b21233d..ef17810a 100644 --- a/crates/device/src/sampler/sampler_view.rs +++ b/crates/device/src/sampler/sampler_view.rs @@ -99,7 +99,7 @@ impl Sampler { pub fn view_sample (&self, note_pt: usize) -> impl Content + use<'_> { Outer(true, Style::default().fg(Tui::g(96))) - .enclose(Fill::xy(draw_viewer(if let Some((_, sample)) = &self.recording { + .enclose(Fill::xy(draw_viewer(if let Some((_, Some(sample))) = &self.recording { Some(sample) } else if let Some(sample) = &self.mapped[note_pt] { Some(sample) @@ -109,7 +109,7 @@ impl Sampler { } pub fn view_sample_info (&self, note_pt: usize) -> impl Content + use<'_> { - Fill::x(Fixed::y(1, draw_info(if let Some((_, sample)) = &self.recording { + Fill::x(Fixed::y(1, draw_info(if let Some((_, Some(sample))) = &self.recording { Some(sample) } else if let Some(sample) = &self.mapped[note_pt] { Some(sample) @@ -119,7 +119,7 @@ impl Sampler { } pub fn view_sample_status (&self, note_pt: usize) -> impl Content + use<'_> { - Fixed::x(20, draw_info_v(if let Some((_, sample)) = &self.recording { + Fixed::x(20, draw_info_v(if let Some((_, Some(sample))) = &self.recording { Some(sample) } else if let Some(sample) = &self.mapped[note_pt] { Some(sample) diff --git a/crates/device/src/sequencer/seq_model.rs b/crates/device/src/sequencer/seq_model.rs index 22bda520..3b411708 100644 --- a/crates/device/src/sequencer/seq_model.rs +++ b/crates/device/src/sequencer/seq_model.rs @@ -32,9 +32,9 @@ pub struct Sequencer { /// Send all notes off pub reset: bool, // TODO?: after Some(nframes) /// Record from MIDI ports to current sequence. - pub midi_ins: Vec, + pub midi_ins: Vec, /// Play from current sequence to MIDI ports - pub midi_outs: Vec, + pub midi_outs: Vec, /// Notes currently held at input pub notes_in: Arc>, /// Notes currently held at output @@ -79,8 +79,8 @@ impl Sequencer { let _name = name.as_ref(); let clock = clock.cloned().unwrap_or_default(); Ok(Self { - midi_ins: vec![JackMidiIn::new(jack, format!("M/{}", name.as_ref()), midi_from)?,], - midi_outs: vec![JackMidiOut::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, @@ -102,8 +102,8 @@ 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!(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 new file mode 100644 index 00000000..565da268 --- /dev/null +++ b/crates/engine/src/audio.rs @@ -0,0 +1,2 @@ +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 new file mode 100644 index 00000000..771e53d2 --- /dev/null +++ b/crates/engine/src/audio/audio_in.rs @@ -0,0 +1,86 @@ +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 new file mode 100644 index 00000000..35e3c6c8 --- /dev/null +++ b/crates/engine/src/audio/audio_out.rs @@ -0,0 +1,77 @@ +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 b13d667e..0f3e9dd8 100644 --- a/crates/engine/src/jack.rs +++ b/crates/engine/src/jack.rs @@ -7,11 +7,12 @@ pub use ::jack::{*, contrib::{*, ClosureProcessHandler}}; //Unowned, MidiIn, MidiOut, AudioIn, AudioOut, //}; -pub(crate) use PortConnectName::*; -pub(crate) use PortConnectScope::*; -pub(crate) use PortConnectStatus::*; +pub(crate) use ConnectName::*; +pub(crate) use ConnectScope::*; +pub(crate) use ConnectStatus::*; pub(crate) use std::sync::{Arc, RwLock}; -mod jack_client; pub use self::jack_client::*; -mod jack_event; pub use self::jack_event::*; -mod jack_port; pub use self::jack_port::*; +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::*; diff --git a/crates/engine/src/jack/jack_client.rs b/crates/engine/src/jack/jack_client.rs index d8d6be6e..abf67158 100644 --- a/crates/engine/src/jack/jack_client.rs +++ b/crates/engine/src/jack/jack_client.rs @@ -2,17 +2,57 @@ use crate::*; use super::*; use self::JackState::*; -impl> HasJack for T { - fn jack (&self) -> &Jack { - self.get() - } +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 { +pub trait HasJack<'j> { /// Return the internal [jack::Client] handle /// that lets you call the JACK API. - fn jack (&self) -> &Jack; + 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() { @@ -34,7 +74,7 @@ pub trait HasJack { 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(()), + Ok(_) => Ok(()), Err(e) => Err(e) })? } @@ -46,71 +86,25 @@ pub trait HasJack { } } -impl HasJack for Jack { - fn jack (&self) -> &Jack { - self - } -} - -impl HasJack for &Jack { - fn jack (&self) -> &Jack { - self - } -} - /// Wraps [JackState] and through it [jack::Client]. #[derive(Clone, Debug, Default)] -pub struct Jack { - pub state: Arc> +pub struct Jack<'j> { + pub state: Arc>> } -impl Jack { +impl<'j> Jack<'j> { pub fn new (name: &str) -> Usually { Ok(Self { state: JackState::new(Client::new(name, ClientOptions::NO_START_SERVER)?.0) }) } - pub fn run <'j: 'static, T: Audio + 'j> ( - &self, cb: impl FnOnce(&Jack)->Usually - ) -> Usually>> { - let app = Arc::new(RwLock::new(cb(self)?)); - let mut state = Activating; - std::mem::swap(&mut*self.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<'j>), - )?; - *self.state.write().unwrap() = Active(client); - } else { - unreachable!(); - } - Ok(app) - } } /// This is a connection which may be [Inactive], [Activating], or [Active]. /// In the [Active] and [Inactive] states, [JackState::client] returns a /// [jack::Client], which you can use to talk to the JACK API. #[derive(Debug, Default)] -pub enum JackState { +pub enum JackState<'j> { /// Unused #[default] Inert, /// Before activation. @@ -118,64 +112,11 @@ pub enum JackState { /// During activation. Activating, /// After activation. Must not be dropped for JACK thread to persist. - Active(DynamicAsyncClient<'static>), + Active(DynamicAsyncClient<'j>), } -impl JackState { +impl<'j> JackState<'j> { fn new (client: Client) -> Arc> { Arc::new(RwLock::new(Self::Inactive(client))) } } - -/// This is a boxed realtime callback. -pub type BoxedAudioHandler<'j> = - Box Control + Send + 'j>; - -/// This is the notification handler wrapper for a boxed realtime callback. -pub type DynamicAudioHandler<'j> = - ClosureProcessHandler<(), BoxedAudioHandler<'j>>; - -/// This is a boxed [JackEvent] callback. -pub type BoxedJackEventHandler<'j> = - Box; - -/// This is the notification handler wrapper for a boxed [JackEvent] callback. -pub type DynamicNotifications<'j> = - Notifications>; - -/// This is a running JACK [AsyncClient] with maximum type erasure. -/// It has one [Box] containing a function that handles [JackEvent]s, -/// and another [Box] containing a function that handles realtime IO, -/// and that's all it knows about them. -pub type DynamicAsyncClient<'j> - = AsyncClient, DynamicAudioHandler<'j>>; - -/// Implement [Audio]: provide JACK callbacks. -#[macro_export] macro_rules! audio { - (| - $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 })? - } - } -} - -/// Trait for thing that has a JACK process callback. -pub trait Audio: Send + Sync { - 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 - } - } -} diff --git a/crates/engine/src/jack/jack_device.rs b/crates/engine/src/jack/jack_device.rs index 7aa3c188..01ed254f 100644 --- a/crates/engine/src/jack/jack_device.rs +++ b/crates/engine/src/jack/jack_device.rs @@ -1,86 +1,86 @@ -use crate::* +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, -} +///// 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 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 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 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 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)?) - } -} +//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_event.rs b/crates/engine/src/jack/jack_event.rs deleted file mode 100644 index 9ba6ad0f..00000000 --- a/crates/engine/src/jack/jack_event.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::*; -use super::*; - -/// 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 - } -} diff --git a/crates/engine/src/jack/jack_handler.rs b/crates/engine/src/jack/jack_handler.rs new file mode 100644 index 00000000..557f97e4 --- /dev/null +++ b/crates/engine/src/jack/jack_handler.rs @@ -0,0 +1,105 @@ +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/jack/jack_port.rs b/crates/engine/src/jack/jack_port.rs index 8d0d852d..13e7aefd 100644 --- a/crates/engine/src/jack/jack_port.rs +++ b/crates/engine/src/jack/jack_port.rs @@ -1,18 +1,28 @@ use crate::*; use super::*; -pub trait JackPort: HasJack { +pub trait JackPort<'j>: HasJack<'j> { type Port: PortSpec; type Pair: PortSpec; fn port (&self) -> &Port; } -pub trait JackPortConnect: JackPort { - fn connect_to (&self, to: T) -> Usually; +pub trait ConnectTo<'j, T>: JackPort<'j> { + fn connect_to (&'j self, to: &T) -> Usually; } -pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port> { - fn conn (&self) -> &[PortConnect]; +#[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> { + fn connections (&self) -> &[Connect]; 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)) } @@ -23,7 +33,7 @@ pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port Usually<()> { - for connect in self.conn().iter() { + for connect in self.connections().iter() { //panic!("{connect:?}"); let status = match &connect.name { Exact(name) => self.connect_exact(name), @@ -33,9 +43,9 @@ pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port Usually, Arc, PortConnectStatus)>> { + fn connect_exact (&self, name: &str) -> + Usually, Arc, ConnectStatus)>> + { self.with_client(|c|{ let mut status = vec![]; for port in c.ports(None, None, PortFlags::empty()).iter() { @@ -54,8 +64,8 @@ pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port Usually, Arc, PortConnectStatus)>> { + &self, re: &str, scope: ConnectScope + ) -> Usually, Arc, ConnectStatus)>> { self.with_client(|c|{ let mut status = vec![]; let ports = c.ports(Some(&re), None, PortFlags::empty()); @@ -75,33 +85,33 @@ pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port), /** Match regular expression */ RegExp(Arc), } -#[derive(Clone, Copy, Debug, PartialEq)] pub enum PortConnectScope { +#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectScope { One, All } -#[derive(Clone, Copy, Debug, PartialEq)] pub enum PortConnectStatus { +#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectStatus { Missing, Disconnected, Connected, Mismatch, } -#[derive(Clone, Debug)] pub struct PortConnect { - pub name: PortConnectName, - pub scope: PortConnectScope, - pub status: Arc, Arc, PortConnectStatus)>>>, +#[derive(Clone, Debug)] pub struct Connect { + pub name: ConnectName, + pub scope: ConnectScope, + pub status: Arc, Arc, ConnectStatus)>>>, pub info: Arc, } -impl PortConnect { +impl Connect { pub fn collect (exact: &[impl AsRef], re: &[impl AsRef], re_all: &[impl AsRef]) -> Vec { @@ -147,89 +157,3 @@ impl PortConnect { format!(" ({}) {} {}", status, scope, name).into() } } - -macro_rules! impl_port { - ($Name:ident : $Spec:ident -> $Pair:ident |$jack:ident, $name:ident|$port:expr) => { - #[derive(Debug)] pub struct $Name { - /// Handle to JACK client, for receiving reconnect events. - jack: Jack, - /// Port name - name: Arc, - /// Port handle. - port: Port<$Spec>, - /// List of ports to connect to. - conn: Vec - } - impl AsRef> for $Name { fn as_ref (&self) -> &Port<$Spec> { &self.port } } - impl $Name { - pub fn new ($jack: &Jack, name: impl AsRef, connect: &[PortConnect]) - -> Usually - { - let $name = name.as_ref(); - let jack = $jack.clone(); - let port = $port?; - let name = $name.into(); - let conn = connect.to_vec(); - let port = Self { jack, port, name, conn }; - port.connect_to_matching()?; - Ok(port) - } - pub fn name (&self) -> &Arc { &self.name } - pub fn port (&self) -> &Port<$Spec> { &self.port } - pub fn port_mut (&mut self) -> &mut Port<$Spec> { &mut self.port } - pub fn into_port (self) -> Port<$Spec> { self.port } - pub fn close (self) -> Usually<()> { - let Self { jack, port, .. } = self; - Ok(jack.with_client(|client|client.unregister_port(port))?) - } - } - impl HasJack for $Name { fn jack (&self) -> &Jack { &self.jack } } - impl JackPort for $Name { - type Port = $Spec; - type Pair = $Pair; - fn port (&self) -> &Port<$Spec> { &self.port } - } - impl JackPortConnect<&str> for $Name { - 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 JackPortConnect<&Port> for $Name { - 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 JackPortConnect<&Port<$Pair>> for $Name { - fn connect_to (&self, port: &Port<$Pair>) -> 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 JackPortAutoconnect for $Name { - fn conn (&self) -> &[PortConnect] { - &self.conn - } - } - }; -} - -impl_port!(JackAudioIn: AudioIn -> AudioOut |j, n|j.register_port::(n)); - -impl_port!(JackAudioOut: AudioOut -> AudioIn |j, n|j.register_port::(n)); - -impl_port!(JackMidiIn: MidiIn -> MidiOut |j, n|j.register_port::(n)); diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 4791132d..24c4b15f 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -1,9 +1,94 @@ #![feature(type_alias_impl_trait)] +//macro_rules! impl_port { + //($Name:ident : $Spec:ident -> $Pair:ident |$jack:ident, $name:ident|$port:expr) => { + //#[derive(Debug)] pub struct $Name { + ///// Handle to JACK client, for receiving reconnect events. + //jack: Jack<'static>, + ///// Port name + //name: Arc, + ///// Port handle. + //port: Port<$Spec>, + ///// List of ports to connect to. + //conn: Vec + //} + //impl AsRef> for $Name { + //fn as_ref (&self) -> &Port<$Spec> { &self.port } + //} + //impl $Name { + //pub fn new ($jack: &Jack, name: impl AsRef, connect: &[PortConnect]) + //-> Usually + //{ + //let $name = name.as_ref(); + //let jack = $jack.clone(); + //let port = $port?; + //let name = $name.into(); + //let conn = connect.to_vec(); + //let port = Self { jack, port, name, conn }; + //port.connect_to_matching()?; + //Ok(port) + //} + //pub fn name (&self) -> &Arc { &self.name } + //pub fn port (&self) -> &Port<$Spec> { &self.port } + //pub fn port_mut (&mut self) -> &mut Port<$Spec> { &mut self.port } + //pub fn into_port (self) -> Port<$Spec> { self.port } + //pub fn close (self) -> Usually<()> { + //let Self { jack, port, .. } = self; + //Ok(jack.with_client(|client|client.unregister_port(port))?) + //} + //} + //impl HasJack<'static> for $Name { + //fn jack (&self) -> &'static Jack<'static> { &self.jack } + //} + //impl JackPort<'static> for $Name { + //type Port = $Spec; + //type Pair = $Pair; + //fn port (&self) -> &Port<$Spec> { &self.port } + //} + //impl ConnectTo<'static, &str> for $Name { + //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 $Name { + //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<$Pair>> for $Name { + //fn connect_to (&self, port: &Port<$Pair>) -> 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 $Name { + //fn connections (&self) -> &[PortConnect] { + //&self.conn + //} + //} + //}; +//} + 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/midi_in.rs b/crates/engine/src/midi/midi_in.rs index f3282937..dab3d026 100644 --- a/crates/engine/src/midi/midi_in.rs +++ b/crates/engine/src/midi/midi_in.rs @@ -1,29 +1,119 @@ use crate::*; -impl JackMidiIn { +//impl_port!(MidiInput: MidiOut -> MidiIn |j, n|j.register_port::(n)); + +#[derive(Debug)] pub struct MidiInput<'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 MidiInput<'j> { + fn as_ref (&self) -> &Port { &self.port } +} +impl<'j> MidiInput<'j> { + pub fn new (jack: &Jack, name: impl AsRef, connect: &[Connect]) + -> 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))?) + } pub fn parsed <'a> (&'a self, scope: &'a ProcessScope) -> impl Iterator, &'a [u8])> { parse_midi_input(self.port().iter(scope)) } } - -#[tengri_proc::command(JackMidiIn)] -impl MidiInputCommand { - fn _todo_ (_port: &mut JackMidiIn) -> Perhaps { Ok(None) } +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 + } } -impl>> HasMidiIns for T { - fn midi_ins (&self) -> &Vec { +#[tengri_proc::command(MidiInput)] +impl MidiInputCommand { + //fn _todo_ (_port: &mut MidiInput) -> Perhaps { Ok(None) } +} + +impl>> HasMidiIns for T { + fn midi_ins (&self) -> &Vec { self.get() } - fn midi_ins_mut (&mut self) -> &mut Vec { + fn midi_ins_mut (&mut self) -> &mut Vec { self.get_mut() } } /// Trait for thing that may receive MIDI. pub trait HasMidiIns { - fn midi_ins (&self) -> &Vec; - fn midi_ins_mut (&mut self) -> &mut Vec; + fn midi_ins (&self) -> &Vec; + fn midi_ins_mut (&mut self) -> &mut Vec; /// Collect MIDI input from app ports (TODO preallocate large buffers) fn midi_input_collect <'a> (&'a self, scope: &'a ProcessScope) -> CollectedMidiInput<'a> { self.midi_ins().iter() @@ -33,7 +123,7 @@ pub trait HasMidiIns { .collect::>() } fn midi_ins_with_sizes <'a> (&'a self) -> - impl Iterator, &[PortConnect], usize, usize)> + Send + Sync + 'a + impl Iterator, &[Connect], usize, usize)> + Send + Sync + 'a { let mut y = 0; self.midi_ins().iter().enumerate().map(move|(i, input)|{ @@ -47,10 +137,10 @@ pub trait HasMidiIns { pub type CollectedMidiInput<'a> = Vec, MidiError>)>>; -impl AddMidiIn for T { +impl<'j, T: HasMidiIns + HasJack<'j>> AddMidiIn for T { fn midi_in_add (&mut self) -> Usually<()> { let index = self.midi_ins().len(); - let port = JackMidiIn::new(self.jack(), &format!("M/{index}"), &[])?; + let port = MidiInput::new(self.jack(), &format!("M/{index}"), &[])?; self.midi_ins_mut().push(port); Ok(()) } diff --git a/crates/engine/src/midi/midi_out.rs b/crates/engine/src/midi/midi_out.rs index bb896c2a..5a0fba73 100644 --- a/crates/engine/src/midi/midi_out.rs +++ b/crates/engine/src/midi/midi_out.rs @@ -1,14 +1,14 @@ use crate::*; -#[derive(Debug)] pub struct JackMidiOut { +#[derive(Debug)] pub struct MidiOutput { /// Handle to JACK client, for receiving reconnect events. - jack: Jack, + jack: Jack<'static>, /// Port name name: Arc, /// Port handle. port: Port, /// List of ports to connect to. - conn: Vec, + conn: Vec, /// List of currently held notes. held: Arc>, /// Buffer @@ -17,10 +17,10 @@ use crate::*; output_buffer: Vec>>, } -has!(Jack: |self: JackMidiOut|self.jack); +has!(Jack<'static>: |self: MidiOutput|self.jack); -impl JackMidiOut { - pub fn new (jack: &Jack, name: impl AsRef, connect: &[PortConnect]) +impl MidiOutput { + pub fn new (jack: &Jack, name: impl AsRef, connect: &[Connect]) -> Usually { let jack = jack.clone(); @@ -94,20 +94,20 @@ impl JackMidiOut { } } -impl AsRef> for JackMidiOut { +impl AsRef> for MidiOutput { fn as_ref (&self) -> &Port { &self.port } } -impl JackPort for JackMidiOut { +impl JackPort<'static> for MidiOutput { type Port = MidiOut; type Pair = MidiIn; fn port (&self) -> &Port { &self.port } } -impl JackPortConnect<&str> for JackMidiOut { - fn connect_to (&self, to: &str) -> Usually { +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 { @@ -116,8 +116,8 @@ impl JackPortConnect<&str> for JackMidiOut { } } -impl JackPortConnect<&Port> for JackMidiOut { - fn connect_to (&self, port: &Port) -> Usually { +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) { @@ -128,8 +128,8 @@ impl JackPortConnect<&Port> for JackMidiOut { } } -impl JackPortConnect<&Port> for JackMidiOut { - fn connect_to (&self, port: &Port) -> Usually { +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) { @@ -140,22 +140,22 @@ impl JackPortConnect<&Port> for JackMidiOut { } } -impl JackPortAutoconnect for JackMidiOut { - fn conn (&self) -> &[PortConnect] { +impl ConnectAuto<'static> for MidiOutput { + fn connections (&self) -> &[Connect] { &self.conn } } -#[tengri_proc::command(JackMidiOut)] +#[tengri_proc::command(MidiOutput)] impl MidiOutputCommand { - fn _todo_ (_port: &mut JackMidiOut) -> Perhaps { Ok(None) } + fn _todo_ (_port: &mut MidiOutput) -> Perhaps { Ok(None) } } -impl>> HasMidiOuts for T { - fn midi_outs (&self) -> &Vec { +impl>> HasMidiOuts for T { + fn midi_outs (&self) -> &Vec { self.get() } - fn midi_outs_mut (&mut self) -> &mut Vec { + fn midi_outs_mut (&mut self) -> &mut Vec { self.get_mut() } } @@ -163,10 +163,10 @@ impl>> HasMidiOuts for T { /// Trait for thing that may output MIDI. pub trait HasMidiOuts { - fn midi_outs (&self) -> &Vec; - fn midi_outs_mut (&mut self) -> &mut Vec; + fn midi_outs (&self) -> &Vec; + fn midi_outs_mut (&mut self) -> &mut Vec; fn midi_outs_with_sizes <'a> (&'a self) -> - impl Iterator, &[PortConnect], usize, usize)> + Send + Sync + 'a + impl Iterator, &[Connect], usize, usize)> + Send + Sync + 'a { let mut y = 0; self.midi_outs().iter().enumerate().map(move|(i, output)|{ @@ -184,10 +184,10 @@ pub trait HasMidiOuts { } /// Trail for thing that may gain new MIDI ports. -impl AddMidiOut for T { +impl<'j, T: HasMidiOuts + HasJack<'j>> AddMidiOut for T { fn midi_out_add (&mut self) -> Usually<()> { let index = self.midi_outs().len(); - let port = JackMidiOut::new(self.jack(), &format!("{index}/M"), &[])?; + let port = MidiOutput::new(self.jack(), &format!("{index}/M"), &[])?; self.midi_outs_mut().push(port); Ok(()) } diff --git a/deps/tengri b/deps/tengri index f08593f0..455d6d00 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit f08593f0f8c3dc03a734d922d2442848a4205ad6 +Subproject commit 455d6d00d5f91e7f9f6b9d3711aa47e09900ad46