diff --git a/jack/src/has_jack.rs b/jack/src/has_jack.rs deleted file mode 100644 index fb8e16f5..00000000 --- a/jack/src/has_jack.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::*; -/// Things that can provide a [JackClient] reference. -pub trait HasJack { fn jack (&self) -> &JackClient; } -/// Implement [HasJack]. -#[macro_export] macro_rules! has_jack { - (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasJack for $Struct $(<$($L),*$($T),*>)? { - fn jack (&$self) -> &JackClient { $cb } - } - }; -} diff --git a/jack/src/jack_audio.rs b/jack/src/jack_audio.rs deleted file mode 100644 index 626d8a5d..00000000 --- a/jack/src/jack_audio.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::*; - -/// Implement [Audio]: provide JACK callbacks. -#[macro_export] macro_rules! audio { - (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? { - #[inline] fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control { $cb } - } - } -} - -/// Trait for thing that has a JACK process callback. -pub trait Audio: Send + Sync { - 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/jack/src/jack_client.rs b/jack/src/jack_client.rs index 233f02a9..465b12fa 100644 --- a/jack/src/jack_client.rs +++ b/jack/src/jack_client.rs @@ -1,88 +1,89 @@ use crate::*; -use self::JackClientState::*; - -/// Wraps [JackClientState] and through it [jack::Client]. -#[derive(Clone, Debug, Default)] -pub struct JackClient { - state: Arc> +use ::jack::contrib::*; +use self::JackState::*; +/// Things that can provide a [jack::Client] reference. +pub trait HasJack { + /// Return the internal [jack::Client] handle + /// that lets you call the JACK API. + fn jack (&self) -> &Jack; + /// 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 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(()) + } } -impl JackClient { +impl HasJack for Jack { fn jack (&self) -> &Jack { self } } +impl HasJack for &Jack { fn jack (&self) -> &Jack { self } } +/// Wraps [JackState] and through it [jack::Client]. +#[derive(Clone, Debug, Default)] +pub struct Jack { + state: Arc> +} +impl Jack { pub fn new (name: &str) -> Usually { - let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; - Ok(Self { state: Arc::new(RwLock::new(Inactive(client))) }) + Ok(Self { + state: JackState::new(Client::new(name, ClientOptions::NO_START_SERVER)?.0) + }) } - /// Return the internal [Client] handle that lets you call the JACK API. - pub fn inner (&self) -> Client { - self.state.read().unwrap().inner() - } - /// Activate a connection with an application. - /// - /// Consume a `JackClient::Inactive`, binding a process callback and returning a `JackClient::Active`. - /// - /// * [ ] TODO: Needs work. Strange ownership situation between the callback and the host object. - fn activate <'a: 'static> ( - &'a self, mut cb: impl FnMut(JackClient, &Client, &ProcessScope) -> Control + Send + 'a - ) -> Usually where Self: Send + Sync + 'a { - let client = self.inner(); - let state = Arc::new(RwLock::new(Activating)); - let event = Box::new(move|_|{/*TODO*/}) as Box; - let events = Notifications(event); - let frame = Box::new(move|c: &_, s: &_|cb(self.clone(), c, s)); - let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler<'a>); - *state.write().unwrap() = Active(client.activate_async(events, frames)?); - Ok(Self { state }) - } - /// Activate a connection with an application. - /// - /// * Wrap a [JackClient::Inactive] into [Arc>]. - /// * Pass it to the `init` callback - /// * This allows user code to connect to JACK - /// * While user code retains clone of the - /// [Arc>] that is - /// passed to `init`, the audio engine is running. - pub fn activate_with <'a: 'static, T> ( - &self, init: impl FnOnce(&JackClient)->Usually - ) -> Usually>> where T: Audio + 'a { - // Run init callback. Return value is target. Target must retain clone of `connection`. - let target = Arc::new(RwLock::new(init(&self)?)); - // Swap the `client` from the `JackClient::Inactive` - // for a `JackClient::Activating`. - let mut client = Activating; - std::mem::swap(&mut*self.state.write().unwrap(), &mut client); - // Replace the `JackClient::Activating` with a - // `JackClient::Active` wrapping the [AsyncClient] - // returned by the activation. - *self.state.write().unwrap() = Active(client.inner().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(move|_|{/*TODO*/}) 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 `target`'s `process` callback, which in turn - // implements audio and MIDI input and output on a realtime basis. - ClosureProcessHandler::new(Box::new({ - let target = target.clone(); - move|c: &_, s: &_|if let Ok(mut target) = target.write() { - target.process(c, s) - } else { - Control::Quit - } - }) as BoxedAudioHandler), - )?); - Ok(target) - } - pub fn port_by_name (&self, name: &str) -> Option> { - self.inner().port_by_name(name) - } - pub fn register_port (&self, name: &str, spec: PS) -> Usually> { - Ok(self.inner().register_port(name, spec)?) + 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(move|_|{/*TODO*/}) 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, [JackClientState::client] returns a +/// 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)] enum JackClientState { +#[derive(Debug, Default)] enum JackState { /// Unused #[default] Inert, /// Before activation. @@ -92,16 +93,15 @@ impl JackClient { /// After activation. Must not be dropped for JACK thread to persist. Active(DynamicAsyncClient<'static>), } -impl JackClientState { - pub fn inner (&self) -> Client { - match self { - Inert => panic!("jack client not activated"), - Inactive(ref client) => unsafe { Client::from_raw(client.raw()) }, - Activating => panic!("jack client has not finished activation"), - Active(ref client) => unsafe { Client::from_raw(client.as_client().raw()) }, - } - } +impl JackState { + fn new (client: Client) -> Arc> { Arc::new(RwLock::new(Self::Inactive(client))) } } +//has_jack_client!(|self: JackState|match self { + //Inert => panic!("jack client not activated"), + //Inactive(ref client) => client, + //Activating => panic!("jack client has not finished activation"), + //Active(ref client) => client.as_client(), +//}); /// This is a boxed realtime callback. pub type BoxedAudioHandler<'j> = Box Control + Send + 'j>; @@ -120,31 +120,26 @@ pub type DynamicNotifications<'j> = /// and that's all it knows about them. pub type DynamicAsyncClient<'j> = AsyncClient, DynamicAudioHandler<'j>>; - -impl RegisterPort for JackClient { - fn midi_in (&self, name: impl AsRef) -> Usually> { - Ok(self.inner().register_port(name.as_ref(), MidiIn::default())?) - } - fn midi_out (&self, name: impl AsRef) -> Usually> { - Ok(self.inner().register_port(name.as_ref(), MidiOut::default())?) - } - fn audio_in (&self, name: impl AsRef) -> Usually> { - Ok(self.inner().register_port(name.as_ref(), AudioIn::default())?) - } - fn audio_out (&self, name: impl AsRef) -> Usually> { - Ok(self.inner().register_port(name.as_ref(), AudioOut::default())?) +/// Implement [Audio]: provide JACK callbacks. +#[macro_export] macro_rules! audio { + (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? { + #[inline] fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control { $cb } + } } } -impl ConnectPort for JackClient { - fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec { - self.inner().ports(re_name, re_type, flags) +/// Trait for thing that has a JACK process callback. +pub trait Audio: Send + Sync { + fn process (&mut self, _: &Client, _: &ProcessScope) -> Control { + Control::Continue } - fn port_by_name (&self, name: impl AsRef) -> Option> { - self.inner().port_by_name(name.as_ref()) - } - fn connect_ports (&self, source: &Port, target: &Port) - -> Usually<()> - { - Ok(self.inner().connect_ports(source, target)?) + 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/jack/src/jack_connect.rs b/jack/src/jack_connect.rs deleted file mode 100644 index dcc4919a..00000000 --- a/jack/src/jack_connect.rs +++ /dev/null @@ -1,165 +0,0 @@ -use crate::*; -pub trait JackPortConnect { - fn jack (&self) -> &JackClient; - fn port (&self) -> &Port; - fn conn (&self) -> &[PortConnection]; - fn connect_to_matching (&mut self) -> Usually<()> { - use PortConnectionName::*; - use PortConnectionScope::*; - use PortConnectionStatus::*; - let jack = self.jack(); - for connect in self.conn().iter() { - let mut status = vec![]; - match &connect.name { - Exact(name) => for port in jack.ports(None, None, PortFlags::empty()).iter() { - if port.as_str() == &**name { - if let Some(port) = jack.port_by_name(port.as_str()) { - let port_status = Self::try_both_ways(jack, &port, &self.port()); - let name = port.name()?.into(); - status.push((port, name, port_status)); - if port_status == Connected { - break - } - } - } - }, - RegExp(re) => for port in jack.ports(Some(&re), None, PortFlags::empty()).iter() { - if let Some(port) = jack.port_by_name(port.as_str()) { - let port_status = Self::try_both_ways(jack, &port, &self.port()); - let name = port.name()?.into(); - status.push((port, name, port_status)); - if port_status == Connected && connect.scope == One { - break - } - } - } - } - *connect.status.write().unwrap() = status - } - Ok(()) - } - fn try_both_ways ( - jack: &impl ConnectPort, port_a: &Port, port_b: &Port - ) -> PortConnectionStatus where A: PortSpec, B: PortSpec { - if let Ok(_) = jack.connect_ports(port_a, port_b) { - PortConnectionStatus::Connected - } else if let Ok(_) = jack.connect_ports(port_b, port_a) { - PortConnectionStatus::Connected - } else { - PortConnectionStatus::Mismatch - } - } -} -#[derive(Clone, Debug)] -pub struct PortConnection { - pub name: PortConnectionName, - pub scope: PortConnectionScope, - pub status: Arc, Arc, PortConnectionStatus)>>>, -} -pub trait ConnectPort { - fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec; - fn port_by_name (&self, name: impl AsRef) -> Option>; - fn connect_ports (&self, source: &Port, target: &Port) - -> Usually<()>; - fn connect_midi_from (&self, input: &Port, ports: &[impl AsRef]) -> Usually<()> { - for port in ports.iter() { - let port = port.as_ref(); - if let Some(port) = self.port_by_name(port).as_ref() { - self.connect_ports(port, input)?; - } else { - panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); - } - } - Ok(()) - } - fn connect_midi_to (&self, output: &Port, ports: &[impl AsRef]) -> Usually<()> { - for port in ports.iter() { - let port = port.as_ref(); - if let Some(port) = self.port_by_name(port).as_ref() { - self.connect_ports(output, port)?; - } else { - panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); - } - } - Ok(()) - } - fn connect_audio_from (&self, input: &Port, ports: &[impl AsRef]) -> Usually<()> { - for port in ports.iter() { - let port = port.as_ref(); - if let Some(port) = self.port_by_name(port).as_ref() { - self.connect_ports(port, input)?; - } else { - panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); - } - } - Ok(()) - } - fn connect_audio_to (&self, output: &Port, ports: &[impl AsRef]) -> Usually<()> { - for port in ports.iter() { - let port = port.as_ref(); - if let Some(port) = self.port_by_name(port).as_ref() { - self.connect_ports(output, port)?; - } else { - panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); - } - } - Ok(()) - } -} -impl ConnectPort for Arc> { - fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec { - self.read().unwrap().ports(re_name, re_type, flags) - } - fn port_by_name (&self, name: impl AsRef) -> Option> { - self.read().unwrap().port_by_name(name.as_ref()) - } - fn connect_ports (&self, source: &Port, target: &Port) - -> Usually<()> - { - Ok(self.read().unwrap().connect_ports(source, target)?) - } -} -impl PortConnection { - 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 name = PortConnectionName::Exact(name.as_ref().into()); - Self { name, scope: PortConnectionScope::One, status: Arc::new(RwLock::new(vec![])) } - } - pub fn regexp (name: impl AsRef) -> Self { - let name = PortConnectionName::RegExp(name.as_ref().into()); - Self { name, scope: PortConnectionScope::One, status: Arc::new(RwLock::new(vec![])) } - } - pub fn regexp_all (name: impl AsRef) -> Self { - let name = PortConnectionName::RegExp(name.as_ref().into()); - Self { name, scope: PortConnectionScope::All, status: Arc::new(RwLock::new(vec![])) } - } - pub fn info (&self) -> Arc { - format!("{} {} {}", match self.scope { - PortConnectionScope::One => " ", - PortConnectionScope::All => "*", - }, match &self.name { - PortConnectionName::Exact(name) => format!("= {name}"), - PortConnectionName::RegExp(name) => format!("~ {name}"), - }, self.status.read().unwrap().len()).into() - } -} -#[derive(Clone, Debug, PartialEq)] -pub enum PortConnectionName { - /** Exact match */ - Exact(Arc), - /** Match regular expression */ - RegExp(Arc), -} -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum PortConnectionScope { One, All } -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum PortConnectionStatus { Missing, Disconnected, Connected, Mismatch, } diff --git a/jack/src/jack_device.rs b/jack/src/jack_device.rs index c415f36e..3ef67085 100644 --- a/jack/src/jack_device.rs +++ b/jack/src/jack_device.rs @@ -1,5 +1,4 @@ use crate::* - /// A [AudioComponent] bound to a JACK client and a set of ports. pub struct JackDevice { /// The active JACK client of this device. @@ -10,7 +9,6 @@ pub struct JackDevice { /// The "real" readable/writable `Port`s are owned by the `state`. pub ports: UnownedJackPorts, } - impl std::fmt::Debug for JackDevice { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("JackDevice") @@ -18,7 +16,6 @@ impl std::fmt::Debug for JackDevice { .finish() } } - impl Render for JackDevice { type Engine = E; fn min_size(&self, to: E::Size) -> Perhaps { @@ -28,13 +25,11 @@ impl Render for JackDevice { self.state.read().unwrap().render(to) } } - impl Handle for JackDevice { fn handle(&mut self, from: &E::Input) -> Perhaps { self.state.write().unwrap().handle(from) } } - impl Ports for JackDevice { fn audio_ins(&self) -> Usually>> { Ok(self.ports.audio_ins.values().collect()) @@ -49,7 +44,6 @@ impl Ports for JackDevice { Ok(self.ports.midi_outs.values().collect()) } } - impl JackDevice { /// Returns a locked mutex of the state's contents. pub fn state(&self) -> LockResult>>> { @@ -84,3 +78,120 @@ impl JackDevice { .connect_ports(self.audio_outs()?[index], port)?) } } +//////////////////////////////////////////////////////////////////////////////////// + +///// `JackDevice` factory. Creates JACK `Client`s, performs port registration +///// and activation, and encapsulates a `AudioComponent` into a `JackDevice`. +//pub struct Jack { + //pub client: Client, + //pub midi_ins: Vec, + //pub audio_ins: Vec, + //pub midi_outs: Vec, + //pub audio_outs: Vec, +//} + +//impl Jack { + //pub fn new(name: &str) -> Usually { + //Ok(Self { + //midi_ins: vec![], + //audio_ins: vec![], + //midi_outs: vec![], + //audio_outs: vec![], + //client: Client::new(name, ClientOptions::NO_START_SERVER)?.0, + //}) + //} + //pub fn run<'a: 'static, D, E>( + //self, + //state: impl FnOnce(JackPorts) -> Box, + //) -> Usually> + //where + //D: AudioComponent + Sized + 'static, + //E: Engine + 'static, + //{ + //let owned_ports = JackPorts { + //audio_ins: register_ports(&self.client, self.audio_ins, AudioIn::default())?, + //audio_outs: register_ports(&self.client, self.audio_outs, AudioOut::default())?, + //midi_ins: register_ports(&self.client, self.midi_ins, MidiIn::default())?, + //midi_outs: register_ports(&self.client, self.midi_outs, MidiOut::default())?, + //}; + //let midi_outs = owned_ports + //.midi_outs + //.values() + //.map(|p| Ok(p.name()?)) + //.collect::>>()?; + //let midi_ins = owned_ports + //.midi_ins + //.values() + //.map(|p| Ok(p.name()?)) + //.collect::>>()?; + //let audio_outs = owned_ports + //.audio_outs + //.values() + //.map(|p| Ok(p.name()?)) + //.collect::>>()?; + //let audio_ins = owned_ports + //.audio_ins + //.values() + //.map(|p| Ok(p.name()?)) + //.collect::>>()?; + //let state = Arc::new(RwLock::new(state(owned_ports) as Box>)); + //let client = self.client.activate_async( + //Notifications(Box::new({ + //let _state = state.clone(); + //move |_event| { + //// FIXME: this deadlocks + ////state.lock().unwrap().handle(&event).unwrap(); + //} + //}) as Box), + //ClosureProcessHandler::new(Box::new({ + //let state = state.clone(); + //move |c: &Client, s: &ProcessScope| state.write().unwrap().process(c, s) + //}) as BoxedAudioHandler), + //)?; + //Ok(JackDevice { + //ports: UnownedJackPorts { + //audio_ins: query_ports(&client.as_client(), audio_ins), + //audio_outs: query_ports(&client.as_client(), audio_outs), + //midi_ins: query_ports(&client.as_client(), midi_ins), + //midi_outs: query_ports(&client.as_client(), midi_outs), + //}, + //client, + //state, + //}) + //} + //pub fn audio_in(mut self, name: &str) -> Self { + //self.audio_ins.push(name.to_string()); + //self + //} + //pub fn audio_out(mut self, name: &str) -> Self { + //self.audio_outs.push(name.to_string()); + //self + //} + //pub fn midi_in(mut self, name: &str) -> Self { + //self.midi_ins.push(name.to_string()); + //self + //} + //pub fn midi_out(mut self, name: &str) -> Self { + //self.midi_outs.push(name.to_string()); + //self + //} +//} + +///// A UI component that may be associated with a JACK client by the `Jack` factory. +//pub trait AudioComponent: Component + Audio { + ///// Perform type erasure for collecting heterogeneous devices. + //fn boxed(self) -> Box> + //where + //Self: Sized + 'static, + //{ + //Box::new(self) + //} +//} + +///// All things that implement the required traits can be treated as `AudioComponent`. +//impl + Audio> AudioComponent for W {} + +///////// + +/* +*/ diff --git a/jack/src/jack_event.rs b/jack/src/jack_event.rs index e8dcaa45..422f59b6 100644 --- a/jack/src/jack_event.rs +++ b/jack/src/jack_event.rs @@ -1,5 +1,4 @@ use crate::*; - #[derive(Debug, Clone, PartialEq)] /// Event enum for JACK events. pub enum JackEvent { @@ -14,10 +13,8 @@ pub enum JackEvent { GraphReorder, XRun, } - /// Generic notification handler that emits [JackEvent] pub struct Notifications(pub T); - impl NotificationHandler for Notifications { fn thread_init(&self, _: &Client) { self.0(JackEvent::ThreadInit); diff --git a/jack/src/jack_port.rs b/jack/src/jack_port.rs index 9a178aa0..2488dd08 100644 --- a/jack/src/jack_port.rs +++ b/jack/src/jack_port.rs @@ -1,185 +1,196 @@ use crate::*; - macro_rules! impl_port { - ($Name:ident $Spec:ident |$jack:ident, $name:ident|$port:expr) => { + ($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. - pub jack: JackClient, + jack: Jack, /// Port name - pub name: Arc, + name: Arc, /// Port handle. - pub port: Port<$Spec>, + port: Port<$Spec>, /// List of ports to connect to. - pub conn: Vec + conn: Vec } impl AsRef> for $Name { fn as_ref (&self) -> &Port<$Spec> { &self.port } } impl $Name { - pub fn new ($jack: &JackClient, name: impl AsRef, connect: &[PortConnection]) + pub fn name (&self) -> &str { self.name.as_ref() } + pub fn port (&self) -> &Port<$Spec> { &self.port } + pub fn port_mut (&mut self) -> &mut Port<$Spec> { &mut self.port } + pub fn new ($jack: &Jack, name: impl AsRef, connect: &[PortConnect]) -> Usually { let $name = name.as_ref(); - let mut port = Self { - jack: $jack.clone(), - port: $port?, - name: $name.into(), - conn: connect.to_vec() - }; + 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) } } - impl JackPortConnect<$Spec> for $Name { - fn jack (&self) -> &JackClient { &self.jack } + 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 } - fn conn (&self) -> &[PortConnection] { self.conn.as_slice() } + } + 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 |jack, name|jack.audio_in(name) } -impl_port! { JackAudioOut AudioOut |jack, name|jack.audio_out(name) } -impl_port! { JackMidiIn MidiIn |jack, name|jack.midi_in(name) } -impl_port! { JackMidiOut MidiOut |jack, name|jack.midi_out(name) } - - -/// This is a utility trait for things that may register or connect [Port]s. -/// It contains shorthand methods to this purpose. It's implemented for -/// `Arc>` for terse port registration in the -/// `init` callback of [jack::Client::activate_with]. -pub trait RegisterPort { - fn midi_in (&self, name: impl AsRef) -> Usually>; - fn midi_out (&self, name: impl AsRef) -> Usually>; - fn audio_in (&self, name: impl AsRef) -> Usually>; - fn audio_out (&self, name: impl AsRef) -> Usually>; +impl_port!(JackAudioIn: AudioIn -> AudioOut |j, n|j.register_port::(n)); +impl_port!(JackAudioOut: AudioOut -> AudioIn |j, n|j.register_port::(n)); +impl_port!(JackMidiIn: MidiIn -> MidiOut |j, n|j.register_port::(n)); +impl_port!(JackMidiOut: MidiOut -> MidiIn |j, n|j.register_port::(n)); +pub trait JackPort: HasJack { + type Port: PortSpec; + type Pair: PortSpec; + fn port (&self) -> &Port; } -impl RegisterPort for Arc> { - fn midi_in (&self, name: impl AsRef) -> Usually> { - self.read().unwrap().midi_in(name) - } - fn midi_out (&self, name: impl AsRef) -> Usually> { - self.read().unwrap().midi_out(name) - } - fn audio_in (&self, name: impl AsRef) -> Usually> { - self.read().unwrap().audio_in(name) - } - fn audio_out (&self, name: impl AsRef) -> Usually> { - self.read().unwrap().audio_out(name) - } +pub trait JackPortConnect: JackPort { + fn connect_to (&self, to: T) -> Usually; } - -///// Collection of JACK ports as [AudioIn]/[AudioOut]/[MidiIn]/[MidiOut]. -//#[derive(Default, Debug)] -//pub struct JackPorts { - //pub audio_ins: BTreeMap>, - //pub midi_ins: BTreeMap>, - //pub audio_outs: BTreeMap>, - //pub midi_outs: BTreeMap>, -//} - -///// Collection of JACK ports as [Unowned]. -//#[derive(Default, Debug)] -//pub struct UnownedJackPorts { - //pub audio_ins: BTreeMap>, - //pub midi_ins: BTreeMap>, - //pub audio_outs: BTreeMap>, - //pub midi_outs: BTreeMap>, -//} - -//impl JackPorts { - //pub fn clone_unowned(&self) -> UnownedJackPorts { - //let mut unowned = UnownedJackPorts::default(); - //for (name, port) in self.midi_ins.iter() { - //unowned.midi_ins.insert(name.clone(), port.clone_unowned()); - //} - //for (name, port) in self.midi_outs.iter() { - //unowned.midi_outs.insert(name.clone(), port.clone_unowned()); - //} - //for (name, port) in self.audio_ins.iter() { - //unowned.audio_ins.insert(name.clone(), port.clone_unowned()); - //} - //for (name, port) in self.audio_outs.iter() { - //unowned - //.audio_outs - //.insert(name.clone(), port.clone_unowned()); - //} - //unowned - //} -//} - -///// Implement the `Ports` trait. -//#[macro_export] -//macro_rules! ports { - //($T:ty $({ $(audio: { - //$(ins: |$ai_arg:ident|$ai_impl:expr,)? - //$(outs: |$ao_arg:ident|$ao_impl:expr,)? - //})? $(midi: { - //$(ins: |$mi_arg:ident|$mi_impl:expr,)? - //$(outs: |$mo_arg:ident|$mo_impl:expr,)? - //})?})?) => { - //impl Ports for $T {$( - //$( - //$(fn audio_ins <'a> (&'a self) -> Usually>> { - //let cb = |$ai_arg:&'a Self|$ai_impl; - //cb(self) - //})? - //)? - //$( - //$(fn audio_outs <'a> (&'a self) -> Usually>> { - //let cb = (|$ao_arg:&'a Self|$ao_impl); - //cb(self) - //})? - //)? - //)? $( - //$( - //$(fn midi_ins <'a> (&'a self) -> Usually>> { - //let cb = (|$mi_arg:&'a Self|$mi_impl); - //cb(self) - //})? - //)? - //$( - //$(fn midi_outs <'a> (&'a self) -> Usually>> { - //let cb = (|$mo_arg:&'a Self|$mo_impl); - //cb(self) - //})? - //)? - //)?} - //}; -//} - -/// Trait for things that may expose JACK ports. -pub trait Ports { - fn audio_ins(&self) -> Usually>> { - Ok(vec![]) +pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port> { + fn conn (&self) -> &[PortConnect]; + fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec { + self.with_client(|c|c.ports(re_name, re_type, flags)) } - fn audio_outs(&self) -> Usually>> { - Ok(vec![]) + fn port_by_name (&self, name: impl AsRef) -> Option> { + self.with_client(|c|c.port_by_name(name.as_ref())) } - fn midi_ins(&self) -> Usually>> { - Ok(vec![]) + fn connect_to_matching (&self) -> Usually<()> { + for connect in self.conn().iter() { + let status = match &connect.name { + Exact(name) => self.connect_exact(name), + RegExp(re) => self.connect_regexp(re), + }?; + *connect.status.write().unwrap() = status; + } + Ok(()) } - fn midi_outs(&self) -> Usually>> { - Ok(vec![]) - } -} - -fn register_ports( - client: &Client, - names: Vec, - spec: T, -) -> Usually>> { - names - .into_iter() - .try_fold(BTreeMap::new(), |mut ports, name| { - let port = client.register_port(&name, spec)?; - ports.insert(name, port); - Ok(ports) + fn connect_exact (&self, name: &str) -> Usually, Arc, PortConnectStatus)>> { + self.with_client(|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 name = port.name()?.into(); + status.push((port, name, port_status)); + if port_status == Connected { + break + } + } + } + } + Ok(status) }) + } + fn connect_regexp (&self, re: &str) -> Usually, Arc, PortConnectStatus)>> { + self.with_client(|c|{ + let mut status = vec![]; + for port in c.ports(Some(&re), None, PortFlags::empty()).iter() { + if let Some(port) = c.port_by_name(port.as_str()) { + let port_status = self.connect_to(&port)?; + let name = port.name()?.into(); + status.push((port, name, port_status)); + // TODO + //if port_status == Connected && connect.scope == One { + //break + //} + } + } + Ok(status) + }) + } } - -fn query_ports(client: &Client, names: Vec) -> BTreeMap> { - names.into_iter().fold(BTreeMap::new(), |mut ports, name| { - let port = client.port_by_name(&name).unwrap(); - ports.insert(name, port); - ports - }) +#[derive(Clone, Debug, PartialEq)] +pub enum PortConnectName { + /** Exact match */ + Exact(Arc), + /** Match regular expression */ + RegExp(Arc), +} +#[derive(Clone, Copy, Debug, PartialEq)] pub enum PortConnectScope { + One, + All +} +#[derive(Clone, Copy, Debug, PartialEq)] pub enum PortConnectStatus { + Missing, + Disconnected, + Connected, + Mismatch, +} +#[derive(Clone, Debug)] pub struct PortConnect { + pub name: PortConnectName, + pub scope: PortConnectScope, + pub status: Arc, Arc, PortConnectStatus)>>>, +} +impl PortConnect { + 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 name = Exact(name.as_ref().into()); + Self { name, scope: One, status: Arc::new(RwLock::new(vec![])) } + } + pub fn regexp (name: impl AsRef) -> Self { + let name = RegExp(name.as_ref().into()); + Self { name, scope: One, status: Arc::new(RwLock::new(vec![])) } + } + pub fn regexp_all (name: impl AsRef) -> Self { + let name = RegExp(name.as_ref().into()); + Self { name, scope: All, status: Arc::new(RwLock::new(vec![])) } + } + pub fn info (&self) -> Arc { + format!("{} {} {}", match self.scope { + One => " ", + All => "*", + }, match &self.name { + Exact(name) => format!("= {name}"), + RegExp(name) => format!("~ {name}"), + }, self.status.read().unwrap().len()).into() + } } - diff --git a/jack/src/jack_sync.rs b/jack/src/jack_sync.rs deleted file mode 100644 index 0943dc55..00000000 --- a/jack/src/jack_sync.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::*; -use jack::contrib::*; - -pub trait SyncToTransport { - fn client (&self) -> Client; - fn sync_lead (&self, enable: bool, cb: impl Fn(TimebaseInfo)->Position) -> Usually<()> { - if enable { - self.client().register_timebase_callback(false, cb)?; - } - Ok(()) - } - fn sync_follow (&self, enable: bool) -> Usually<()> { - // TODO: sync follow - Ok(()) - } -} - -impl SyncToTransport for JackClient { - fn client (&self) -> Client { self.inner() } -} diff --git a/jack/src/lib.rs b/jack/src/lib.rs index 4c8d1766..fd57fa8d 100644 --- a/jack/src/lib.rs +++ b/jack/src/lib.rs @@ -1,15 +1,11 @@ #![feature(type_alias_impl_trait)] - -mod has_jack; pub use self::has_jack::*; -mod jack_audio; pub use self::jack_audio::*; -mod jack_connect; pub use self::jack_connect::*; -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_sync; pub use self::jack_sync::*; - +mod jack_client; pub use self::jack_client::*; +mod jack_event; pub use self::jack_event::*; +mod jack_port; pub use self::jack_port::*; +pub(crate) use PortConnectName::*; +pub(crate) use PortConnectScope::*; +pub(crate) use PortConnectStatus::*; pub(crate) use std::sync::{Arc, RwLock}; -pub(crate) use std::collections::BTreeMap; pub use ::jack; pub(crate) use ::jack::{ contrib::ClosureProcessHandler, NotificationHandler, Client, AsyncClient, ClientOptions, ClientStatus, @@ -17,123 +13,4 @@ pub use ::jack; pub(crate) use ::jack::{ Port, PortId, PortSpec, PortFlags, Unowned, MidiIn, MidiOut, AudioIn, AudioOut, }; - pub(crate) type Usually = Result>; - -//////////////////////////////////////////////////////////////////////////////////// - -///// `JackDevice` factory. Creates JACK `Client`s, performs port registration -///// and activation, and encapsulates a `AudioComponent` into a `JackDevice`. -//pub struct Jack { - //pub client: Client, - //pub midi_ins: Vec, - //pub audio_ins: Vec, - //pub midi_outs: Vec, - //pub audio_outs: Vec, -//} - -//impl Jack { - //pub fn new(name: &str) -> Usually { - //Ok(Self { - //midi_ins: vec![], - //audio_ins: vec![], - //midi_outs: vec![], - //audio_outs: vec![], - //client: Client::new(name, ClientOptions::NO_START_SERVER)?.0, - //}) - //} - //pub fn run<'a: 'static, D, E>( - //self, - //state: impl FnOnce(JackPorts) -> Box, - //) -> Usually> - //where - //D: AudioComponent + Sized + 'static, - //E: Engine + 'static, - //{ - //let owned_ports = JackPorts { - //audio_ins: register_ports(&self.client, self.audio_ins, AudioIn::default())?, - //audio_outs: register_ports(&self.client, self.audio_outs, AudioOut::default())?, - //midi_ins: register_ports(&self.client, self.midi_ins, MidiIn::default())?, - //midi_outs: register_ports(&self.client, self.midi_outs, MidiOut::default())?, - //}; - //let midi_outs = owned_ports - //.midi_outs - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let midi_ins = owned_ports - //.midi_ins - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let audio_outs = owned_ports - //.audio_outs - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let audio_ins = owned_ports - //.audio_ins - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let state = Arc::new(RwLock::new(state(owned_ports) as Box>)); - //let client = self.client.activate_async( - //Notifications(Box::new({ - //let _state = state.clone(); - //move |_event| { - //// FIXME: this deadlocks - ////state.lock().unwrap().handle(&event).unwrap(); - //} - //}) as Box), - //ClosureProcessHandler::new(Box::new({ - //let state = state.clone(); - //move |c: &Client, s: &ProcessScope| state.write().unwrap().process(c, s) - //}) as BoxedAudioHandler), - //)?; - //Ok(JackDevice { - //ports: UnownedJackPorts { - //audio_ins: query_ports(&client.as_client(), audio_ins), - //audio_outs: query_ports(&client.as_client(), audio_outs), - //midi_ins: query_ports(&client.as_client(), midi_ins), - //midi_outs: query_ports(&client.as_client(), midi_outs), - //}, - //client, - //state, - //}) - //} - //pub fn audio_in(mut self, name: &str) -> Self { - //self.audio_ins.push(name.to_string()); - //self - //} - //pub fn audio_out(mut self, name: &str) -> Self { - //self.audio_outs.push(name.to_string()); - //self - //} - //pub fn midi_in(mut self, name: &str) -> Self { - //self.midi_ins.push(name.to_string()); - //self - //} - //pub fn midi_out(mut self, name: &str) -> Self { - //self.midi_outs.push(name.to_string()); - //self - //} -//} - -///// A UI component that may be associated with a JACK client by the `Jack` factory. -//pub trait AudioComponent: Component + Audio { - ///// Perform type erasure for collecting heterogeneous devices. - //fn boxed(self) -> Box> - //where - //Self: Sized + 'static, - //{ - //Box::new(self) - //} -//} - -///// All things that implement the required traits can be treated as `AudioComponent`. -//impl + Audio> AudioComponent for W {} - -///////// - -/* -*/ diff --git a/midi/src/midi_in.rs b/midi/src/midi_in.rs index ff078d0c..986ee903 100644 --- a/midi/src/midi_in.rs +++ b/midi/src/midi_in.rs @@ -27,7 +27,7 @@ pub trait MidiRecordApi: HasClock + HasPlayClip + HasMidiIns { let notes_in = self.notes_in().clone(); let monitoring = self.monitoring(); for input in self.midi_ins_mut().iter() { - for (sample, event, bytes) in parse_midi_input(input.port.iter(scope)) { + for (sample, event, bytes) in parse_midi_input(input.port().iter(scope)) { if let LiveEvent::Midi { message, .. } = event { if monitoring { midi_buf[sample].push(bytes.to_vec()); @@ -67,7 +67,7 @@ pub trait MidiRecordApi: HasClock + HasPlayClip + HasMidiIns { let mut clip = clip.write().unwrap(); let length = clip.length; for input in self.midi_ins_mut().iter() { - for (sample, event, bytes) in parse_midi_input(input.port.iter(scope)) { + for (sample, event, bytes) in parse_midi_input(input.port().iter(scope)) { if let LiveEvent::Midi { message, .. } = event { clip.record_event({ let sample = (sample0 + sample - start) as f64; diff --git a/midi/src/midi_out.rs b/midi/src/midi_out.rs index b6e6fa65..913832de 100644 --- a/midi/src/midi_out.rs +++ b/midi/src/midi_out.rs @@ -143,7 +143,7 @@ pub trait MidiPlaybackApi: HasPlayClip + HasClock + HasMidiOuts { fn write (&mut self, scope: &ProcessScope, out: &[Vec>]) { let samples = scope.n_frames() as usize; for port in self.midi_outs_mut().iter_mut() { - Self::write_port(&mut port.port.writer(scope), samples, out) + Self::write_port(&mut port.port_mut().writer(scope), samples, out) } } diff --git a/midi/src/midi_player.rs b/midi/src/midi_player.rs index e91d4962..9bd8d19c 100644 --- a/midi/src/midi_player.rs +++ b/midi/src/midi_player.rs @@ -62,11 +62,11 @@ impl Default for MidiPlayer { } impl MidiPlayer { pub fn new ( - jack: &JackClient, + jack: &Jack, name: impl AsRef, clip: Option<&Arc>>, - midi_from: &[PortConnection], - midi_to: &[PortConnection], + midi_from: &[PortConnect], + midi_to: &[PortConnect], ) -> Usually { let name = name.as_ref(); let clock = Clock::from(jack); diff --git a/plugin/src/plugin.rs b/plugin/src/plugin.rs index d077ecac..0417b2f2 100644 --- a/plugin/src/plugin.rs +++ b/plugin/src/plugin.rs @@ -4,7 +4,7 @@ use crate::*; #[derive(Debug)] pub struct Plugin { /// JACK client handle (needs to not be dropped for standalone mode to work). - pub jack: JackClient, + pub jack: Jack, pub name: Arc, pub path: Option>, pub plugin: Option, @@ -37,7 +37,7 @@ impl Debug for PluginKind { } impl Plugin { pub fn new_lv2 ( - jack: &JackClient, + jack: &Jack, name: &str, path: &str, ) -> Usually { @@ -128,7 +128,7 @@ audio!(|self: PluginAudio, client, scope|{ impl Plugin { /// Create a plugin host device. pub fn new ( - jack: &JackClient, + jack: &Jack, name: &str, ) -> Usually { Ok(Self { @@ -257,7 +257,7 @@ fn draw_header (state: &Plugin, to: &mut TuiOut, x: u16, y: u16, w: u16) { //} //}); -//from_atom!("plugin/lv2" => |jack: &JackClient, args| -> Plugin { +//from_atom!("plugin/lv2" => |jack: &Jack, args| -> Plugin { //let mut name = String::new(); //let mut path = String::new(); //atom!(atom in args { diff --git a/sampler/src/sampler.rs b/sampler/src/sampler.rs index ee48c9c5..c0933191 100644 --- a/sampler/src/sampler.rs +++ b/sampler/src/sampler.rs @@ -30,7 +30,7 @@ pub trait HasSampler { } /// The sampler device plays sounds in response to MIDI notes. #[derive(Debug)] pub struct Sampler { - pub jack: JackClient, + pub jack: Jack, pub name: String, pub mapped: [Option>>;128], pub recording: Option<(usize, Arc>)>, @@ -89,11 +89,11 @@ impl Default for Sampler { } impl Sampler { pub fn new ( - jack: &JackClient, + jack: &Jack, name: impl AsRef, - midi_from: &[PortConnection], - audio_from: &[&[PortConnection];2], - audio_to: &[&[PortConnection];2], + midi_from: &[PortConnect], + audio_from: &[&[PortConnect];2], + audio_to: &[&[PortConnect];2], ) -> Usually { let name = name.as_ref(); Ok(Self { @@ -271,7 +271,7 @@ impl Sampler { let iterator = audio_ins.iter().zip(input_meter).zip(sample.channels.iter_mut()); let mut length = 0; for ((input, meter), channel) in iterator { - let slice = input.port.as_slice(scope); + let slice = input.port().as_slice(scope); length = length.max(slice.len()); let total: f32 = slice.iter().map(|x|x.abs()).sum(); let count = slice.len() as f32; @@ -281,7 +281,7 @@ impl Sampler { sample.end += length; } else { for (input, meter) in audio_ins.iter().zip(input_meter) { - let slice = input.port.as_slice(scope); + let slice = input.port().as_slice(scope); let total: f32 = slice.iter().map(|x|x.abs()).sum(); let count = slice.len() as f32; *meter = 10. * (total / count).log10(); @@ -292,7 +292,7 @@ impl Sampler { pub fn process_midi_in (&mut self, scope: &ProcessScope) { let Sampler { midi_in, mapped, voices, .. } = self; if let Some(ref midi_in) = midi_in { - for RawMidi { time, bytes } in midi_in.port.iter(scope) { + for RawMidi { time, bytes } in midi_in.port().iter(scope) { if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { match message { MidiMessage::NoteOn { ref key, ref vel } => { @@ -341,7 +341,7 @@ impl Sampler { let Sampler { ref mut audio_outs, buffer, .. } = self; for (i, port) in audio_outs.iter_mut().enumerate() { let buffer = &buffer[i]; - for (i, value) in port.port.as_mut_slice(scope).iter_mut().enumerate() { + for (i, value) in port.port_mut().as_mut_slice(scope).iter_mut().enumerate() { *value = *buffer.get(i).unwrap_or(&0.0); } } @@ -349,7 +349,7 @@ impl Sampler { } /////////////////////////////////////////////////////////////////////////////////////////////////// type MidiSample = (Option, Arc>); -//from_atom!("sampler" => |jack: &JackClient, args| -> crate::Sampler { +//from_atom!("sampler" => |jack: &Jack, args| -> crate::Sampler { //let mut name = String::new(); //let mut dir = String::new(); //let mut samples = BTreeMap::new(); @@ -377,7 +377,7 @@ type MidiSample = (Option, Arc>); //}); //Self::new(jack, &name) //}); -//from_atom!("sample" => |(_jack, dir): (&JackClient, &str), args| -> MidiSample { +//from_atom!("sample" => |(_jack, dir): (&Jack, &str), args| -> MidiSample { //let mut name = String::new(); //let mut file = String::new(); //let mut midi = None; diff --git a/tek/src/audio.rs b/tek/src/audio.rs index 22bb402c..51c8e031 100644 --- a/tek/src/audio.rs +++ b/tek/src/audio.rs @@ -1,5 +1,5 @@ use crate::*; -has_jack!(|self: Tek|&self.jack); +impl HasJack for Tek { fn jack (&self) -> &Jack { &self.jack } } audio!(|self: Tek, client, scope|{ // Start profiling cycle let t0 = self.perf.get_t0(); @@ -7,7 +7,7 @@ audio!(|self: Tek, client, scope|{ self.clock().update_from_scope(scope).unwrap(); // Collect MIDI input (TODO preallocate) let midi_in = self.midi_ins.iter() - .map(|port|port.port.iter(scope) + .map(|port|port.port().iter(scope) .map(|RawMidi { time, bytes }|(time, LiveEvent::parse(bytes))) .collect::>()) .collect::>(); diff --git a/tek/src/cli.rs b/tek/src/cli.rs index 402107a9..a10ea658 100644 --- a/tek/src/cli.rs +++ b/tek/src/cli.rs @@ -61,18 +61,18 @@ impl TekCli { pub fn run (&self) -> Usually<()> { let name = self.name.as_ref().map_or("tek", |x|x.as_str()); //let color = ItemPalette::random(); - let jack = JackClient::new(name)?; + let jack = Jack::new(name)?; let engine = Tui::new()?; let empty = &[] as &[&str]; - let midi_froms = PortConnection::collect(&self.midi_from, &self.midi_from_re, empty); - let midi_tos = PortConnection::collect(&self.midi_to, &self.midi_to_re, empty); - let left_froms = PortConnection::collect(&self.left_from, empty, empty); - let left_tos = PortConnection::collect(&self.left_to, empty, empty); - let right_froms = PortConnection::collect(&self.right_from, empty, empty); - let right_tos = PortConnection::collect(&self.right_to, empty, empty); + let midi_froms = PortConnect::collect(&self.midi_from, &self.midi_from_re, empty); + let midi_tos = PortConnect::collect(&self.midi_to, &self.midi_to_re, empty); + 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 audio_froms = &[left_froms.as_slice(), right_froms.as_slice()]; let audio_tos = &[left_tos.as_slice(), right_tos.as_slice() ]; - engine.run(&jack.activate_with(|jack|match self.mode { + let jack = jack.run(|jack|match self.mode { TekMode::Clock => Tek::new_clock( jack, self.bpm, self.sync_lead, self.sync_follow, &midi_froms, &midi_tos), @@ -89,14 +89,15 @@ impl TekCli { &audio_froms, &audio_tos, scenes, tracks, track_width), _ => todo!() - })?) + })?; + engine.run(&jack) } } impl Tek { pub fn new_clock ( - jack: &JackClient, + jack: &Jack, bpm: Option, sync_lead: bool, sync_follow: bool, - midi_froms: &[PortConnection], midi_tos: &[PortConnection], + midi_froms: &[PortConnect], midi_tos: &[PortConnect], ) -> Usually { let tek = Self { view: SourceIter(include_str!("./view_transport.edn")), @@ -122,9 +123,9 @@ impl Tek { Ok(tek) } pub fn new_sequencer ( - jack: &JackClient, + jack: &Jack, bpm: Option, sync_lead: bool, sync_follow: bool, - midi_froms: &[PortConnection], midi_tos: &[PortConnection], + midi_froms: &[PortConnect], midi_tos: &[PortConnect], ) -> Usually { let clip = MidiClip::new("Clip", true, 384usize, None, Some(ItemColor::random().into())); let clip = Arc::new(RwLock::new(clip)); @@ -139,10 +140,10 @@ impl Tek { }) } pub fn new_groovebox ( - jack: &JackClient, + jack: &Jack, bpm: Option, sync_lead: bool, sync_follow: bool, - midi_froms: &[PortConnection], midi_tos: &[PortConnection], - audio_froms: &[&[PortConnection];2], audio_tos: &[&[PortConnection];2], + midi_froms: &[PortConnect], midi_tos: &[PortConnect], + audio_froms: &[&[PortConnect];2], audio_tos: &[&[PortConnect];2], ) -> Usually { let app = Self { view: SourceIter(include_str!("./view_groovebox.edn")), @@ -150,15 +151,15 @@ impl Tek { ..Self::new_sequencer(jack, bpm, sync_lead, sync_follow, midi_froms, midi_tos)? }; if let Some(sampler) = app.sampler.as_ref().unwrap().midi_in.as_ref() { - jack.connect_ports(&app.player.as_ref().unwrap().midi_outs[0].port, &sampler.port)?; + app.player.as_ref().unwrap().midi_outs[0].connect_to(sampler.port())?; } Ok(app) } pub fn new_arranger ( - jack: &JackClient, + jack: &Jack, bpm: Option, sync_lead: bool, sync_follow: bool, - midi_froms: &[PortConnection], midi_tos: &[PortConnection], - audio_froms: &[&[PortConnection];2], audio_tos: &[&[PortConnection];2], + midi_froms: &[PortConnect], midi_tos: &[PortConnect], + audio_froms: &[&[PortConnect];2], audio_tos: &[&[PortConnect];2], scenes: usize, tracks: usize, track_width: usize, ) -> Usually { let mut arranger = Self { diff --git a/tek/src/model.rs b/tek/src/model.rs index 750869fe..d83167af 100644 --- a/tek/src/model.rs +++ b/tek/src/model.rs @@ -1,7 +1,7 @@ use crate::*; #[derive(Default, Debug)] pub struct Tek { /// Must not be dropped for the duration of the process - pub jack: JackClient, + pub jack: Jack, /// Source of time pub clock: Clock, /// Theme @@ -94,7 +94,7 @@ impl Tek { } pub fn tracks_add ( &mut self, count: usize, width: usize, - midi_from: &[PortConnection], midi_to: &[PortConnection], + midi_from: &[PortConnect], midi_to: &[PortConnect], ) -> Usually<()> { let jack = self.jack().clone(); let track_color_1 = ItemColor::random(); @@ -108,7 +108,7 @@ impl Tek { } pub fn track_add ( &mut self, name: Option<&str>, color: Option, - midi_from: &[PortConnection], midi_to: &[PortConnection], + midi_from: &[PortConnect], midi_to: &[PortConnect], ) -> Usually<(usize, &mut Track)> { let name = name.map_or_else(||self.track_next_name(), |x|x.to_string().into()); let mut track = Track { diff --git a/tek/src/view.rs b/tek/src/view.rs index ac58428d..e1d0b646 100644 --- a/tek/src/view.rs +++ b/tek/src/view.rs @@ -208,7 +208,7 @@ impl Tek { self.midi_ins.len(), self.midi_ins().get(0).map( move|input: &JackMidiIn|Bsp::s( - Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(input.name.clone())))), + Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(input.name().clone())))), input.conn().get(0).map(|connect|Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, connect.info())))))))); let rec = false; @@ -230,7 +230,7 @@ impl Tek { let h = 1 + self.midi_outs.len() as u16; let header: ThunkBox<_> = io_header!(self, " O ", " midi outs", self.midi_outs.len(), self.midi_outs().get(0).map( move|output: &JackMidiOut|Bsp::s( - Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(output.name.clone())))), + Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(output.name().clone())))), output.conn().get(0).map(|connect|Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, connect.info())))))))); let mute = false; diff --git a/time/src/clock.rs b/time/src/clock.rs index a669590e..d3dfa077 100644 --- a/time/src/clock.rs +++ b/time/src/clock.rs @@ -81,11 +81,10 @@ pub struct Clock { /// Size of buffer in samples pub chunk: Arc, } -impl From<&JackClient> for Clock { - fn from (jack: &JackClient) -> Self { - let chunk = jack.inner().buffer_size(); - let transport = jack.inner().transport(); - let timebase = Arc::new(Timebase::default()); +impl From<&Jack> for Clock { + fn from (jack: &Jack) -> Self { + let (chunk, transport) = jack.with_client(|c|(c.buffer_size(), c.transport())); + let timebase = Arc::new(Timebase::default()); Self { quant: Arc::new(24.into()), sync: Arc::new(384.into()), @@ -113,7 +112,7 @@ impl std::fmt::Debug for Clock { } } impl Clock { - pub fn new (jack: &JackClient, bpm: Option) -> Self { + pub fn new (jack: &Jack, bpm: Option) -> Self { let clock = Self::from(jack); if let Some(bpm) = bpm { clock.timebase.bpm.set(bpm);