diff --git a/jack/src/jack_connection.rs b/jack/src/jack_connect.rs similarity index 100% rename from jack/src/jack_connection.rs rename to jack/src/jack_connect.rs diff --git a/jack/src/jack_device.rs b/jack/src/jack_device.rs new file mode 100644 index 00000000..c415f36e --- /dev/null +++ b/jack/src/jack_device.rs @@ -0,0 +1,86 @@ +use crate::* + +/// A [AudioComponent] bound to a JACK client and a set of ports. +pub struct JackDevice { + /// The active JACK client of this device. + pub client: DynamicAsyncClient, + /// The device state, encapsulated for sharing between threads. + pub state: Arc>>>, + /// Unowned copies of the device's JACK ports, for connecting to the device. + /// The "real" readable/writable `Port`s are owned by the `state`. + pub ports: UnownedJackPorts, +} + +impl std::fmt::Debug for JackDevice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("JackDevice") + .field("ports", &self.ports) + .finish() + } +} + +impl Render for JackDevice { + type Engine = E; + fn min_size(&self, to: E::Size) -> Perhaps { + self.state.read().unwrap().layout(to) + } + fn render(&self, to: &mut E::Output) -> Usually<()> { + self.state.read().unwrap().render(to) + } +} + +impl Handle for JackDevice { + fn handle(&mut self, from: &E::Input) -> Perhaps { + self.state.write().unwrap().handle(from) + } +} + +impl Ports for JackDevice { + fn audio_ins(&self) -> Usually>> { + Ok(self.ports.audio_ins.values().collect()) + } + fn audio_outs(&self) -> Usually>> { + Ok(self.ports.audio_outs.values().collect()) + } + fn midi_ins(&self) -> Usually>> { + Ok(self.ports.midi_ins.values().collect()) + } + fn midi_outs(&self) -> Usually>> { + Ok(self.ports.midi_outs.values().collect()) + } +} + +impl JackDevice { + /// Returns a locked mutex of the state's contents. + pub fn state(&self) -> LockResult>>> { + self.state.read() + } + /// Returns a locked mutex of the state's contents. + pub fn state_mut(&self) -> LockResult>>> { + self.state.write() + } + pub fn connect_midi_in(&self, index: usize, port: &Port) -> Usually<()> { + Ok(self + .client + .as_client() + .connect_ports(port, self.midi_ins()?[index])?) + } + pub fn connect_midi_out(&self, index: usize, port: &Port) -> Usually<()> { + Ok(self + .client + .as_client() + .connect_ports(self.midi_outs()?[index], port)?) + } + pub fn connect_audio_in(&self, index: usize, port: &Port) -> Usually<()> { + Ok(self + .client + .as_client() + .connect_ports(port, self.audio_ins()?[index])?) + } + pub fn connect_audio_out(&self, index: usize, port: &Port) -> Usually<()> { + Ok(self + .client + .as_client() + .connect_ports(self.audio_outs()?[index], port)?) + } +} diff --git a/jack/src/jack_port.rs b/jack/src/jack_port.rs index 31839b93..7561c0a7 100644 --- a/jack/src/jack_port.rs +++ b/jack/src/jack_port.rs @@ -1,84 +1,165 @@ use crate::*; pub struct JackPort { - pub port: Port, + /// Handle to JACK client, for receiving reconnect events. + pub jack: Arc>, + /// Port handle. + pub port: Port, + /// List of ports to connect to. pub connect: Vec } - -impl JackPort { - //pub fn new (jack: &impl RegisterPort +pub struct PortConnect { + pub name: PortConnectName, + pub order: PortConnectScope, + pub status: Vec<(Port, PortConnectStatus)>, } - -#[derive(Clone, PartialEq)] -pub enum PortConnect { +impl PortConnect { + /// Connect to this exact port + pub fn exact (name: impl AsRef) -> Self { + let name = PortConnectName::Exact(name.as_ref().into()); + Self { name, order: PortConnectScope::One, status: vec![] } + } + pub fn wildcard (name: impl AsRef) -> Self { + let name = PortConnectName::Wildcard(name.as_ref().into()); + Self { name, order: PortConnectScope::One, status: vec![] } + } + pub fn wildcard_all (name: impl AsRef) -> Self { + let name = PortConnectName::Wildcard(name.as_ref().into()); + Self { name, order: PortConnectScope::All, status: vec![] } + } + pub fn regexp (name: impl AsRef) -> Self { + let name = PortConnectName::RegExp(name.as_ref().into()); + Self { name, order: PortConnectScope::One, status: vec![] } + } + pub fn regexp_all (name: impl AsRef) -> Self { + let name = PortConnectName::RegExp(name.as_ref().into()); + Self { name, order: PortConnectScope::All, status: vec![] } + } +} +#[derive(Clone, Debug, PartialEq)] +pub enum PortConnectName { + /// Exact match Exact(Arc), + /// Match wildcard Wildcard(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, } + +impl AsRef> for JackPort { + fn as_ref (&self) -> &Port { + &self.port + } +} +impl JackPort { + pub fn midi_in ( + jack: Arc>, name: impl AsRef, connect: &[PortConnect] + ) -> Usually> { + let input = jack.midi_in(name)?; + for port in connect.iter() { + let port = port.as_ref(); + if let Some(output) = jack.port_by_name(port).as_ref() { + jack.connect_ports(output, &input)?; + } else { + panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); + } + } + Ok(JackPort { jack: jack.clone(), port: input, connect: connect.clone() }) + } + + pub fn midi_out ( + jack: Arc>, + name: impl AsRef, + connect: &[impl AsRef] + ) -> Usually> { + let output = jack.midi_out(name)?; + for port in connect.iter() { + let port = port.as_ref(); + if let Some(input) = jack.port_by_name(port).as_ref() { + jack.connect_ports(&output, input)?; + } else { + panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); + } + } + Ok(JackPort { jack: jack.clone(), port: output, connect: connect.into() }) + } + + pub fn audio_in ( + jack: Arc>, + name: impl AsRef, + connect: &[impl AsRef] + ) -> Usually> { + let input = jack.audio_in(name)?; + for port in connect.iter() { + let port = port.as_ref(); + if let Some(output) = jack.port_by_name(port).as_ref() { + jack.connect_ports(output, &input)?; + } else { + panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); + } + } + Ok(JackPort { jack: jack.clone(), port: input, connect: connect.into() }) + } + + pub fn audio_out ( + jack: Arc>, + name: impl AsRef, + connect: &[impl AsRef] + ) -> Usually> { + let output = jack.audio_out(name)?; + for port in connect.iter() { + let port = port.as_ref(); + if let Some(input) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(&output, input)?; + } else { + panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); + } + } + Ok(JackPort { jack: jack.clone(), port: output, connect: connect.into() }) + } +} + /// 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 [JackClient::activate_with]. pub trait RegisterPort { - fn midi_in (&self, name: impl AsRef, connect: &[impl AsRef]) -> Usually>; - fn midi_out (&self, name: impl AsRef, connect: &[impl AsRef]) -> Usually>; - fn audio_in (&self, name: impl AsRef, connect: &[impl AsRef]) -> Usually>; - fn audio_out (&self, name: impl AsRef, connect: &[impl AsRef]) -> Usually>; + 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 RegisterPort for Arc> { - fn midi_in (&self, name: impl AsRef, connect: &[impl AsRef]) -> Usually> { - let jack = self.read().unwrap(); - let input = jack.client().register_port(name.as_ref(), MidiIn::default())?; - for port in connect.iter() { - let port = port.as_ref(); - if let Some(output) = jack.port_by_name(port).as_ref() { - jack.client().connect_ports(output, &input)?; - } else { - panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); - } - } - Ok(input) +impl RegisterPort for JackConnection { + fn midi_in (&self, name: impl AsRef) -> Usually> { + Ok(self.client().register_port(name.as_ref(), MidiIn::default())?) } - fn midi_out (&self, name: impl AsRef, connect: &[impl AsRef]) -> Usually> { - let jack = self.read().unwrap(); - let output = jack.client().register_port(name.as_ref(), MidiOut::default())?; - for port in connect.iter() { - let port = port.as_ref(); - if let Some(input) = jack.port_by_name(port).as_ref() { - jack.client().connect_ports(&output, input)?; - } else { - panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); - } - } - Ok(output) + fn midi_out (&self, name: impl AsRef) -> Usually> { + Ok(self.client().register_port(name.as_ref(), MidiOut::default())?) } - fn audio_in (&self, name: impl AsRef, connect: &[impl AsRef]) -> Usually> { - let jack = self.read().unwrap(); - let input = jack.client().register_port(name.as_ref(), AudioIn::default())?; - for port in connect.iter() { - let port = port.as_ref(); - if let Some(output) = jack.port_by_name(port).as_ref() { - jack.client().connect_ports(output, &input)?; - } else { - panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); - } - } - Ok(input) + fn audio_in (&self, name: impl AsRef) -> Usually> { + Ok(self.client().register_port(name.as_ref(), AudioIn::default())?) } - fn audio_out (&self, name: impl AsRef, connect: &[impl AsRef]) -> Usually> { - let jack = self.read().unwrap(); - let output = jack.client().register_port(name.as_ref(), AudioOut::default())?; - for port in connect.iter() { - let port = port.as_ref(); - if let Some(input) = jack.port_by_name(port).as_ref() { - jack.client().connect_ports(&output, input)?; - } else { - panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); - } - } - Ok(output) + fn audio_out (&self, name: impl AsRef) -> Usually> { + Ok(self.client().register_port(name.as_ref(), AudioOut::default())?) + } +} +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) } } @@ -131,15 +212,24 @@ pub trait ConnectPort { Ok(()) } } - -impl ConnectPort for Arc> { +impl ConnectPort for JackConnection { fn port_by_name (&self, name: impl AsRef) -> Option> { - self.read().unwrap().client().port_by_name(name.as_ref()) + self.client().port_by_name(name.as_ref()) } fn connect_ports (&self, source: &Port, target: &Port) -> Usually<()> { - Ok(self.read().unwrap().client().connect_ports(source, target)?) + Ok(self.client().connect_ports(source, target)?) + } +} +impl ConnectPort for Arc> { + 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)?) } } diff --git a/jack/src/lib.rs b/jack/src/lib.rs index 5689c22f..e27a5e36 100644 --- a/jack/src/lib.rs +++ b/jack/src/lib.rs @@ -9,101 +9,16 @@ pub(crate) use ::jack::{ Port, PortId, PortSpec, Unowned, MidiIn, MidiOut, AudioIn, AudioOut, }; -mod from_jack; pub use self::from_jack::*; -mod jack_audio; pub use self::jack_audio::*; -mod jack_connection; pub use self::jack_connection::*; -mod jack_event; pub use self::jack_event::*; -mod jack_port; pub use self::jack_port::*; +mod from_jack; pub use self::from_jack::*; +mod jack_audio; pub use self::jack_audio::*; +mod jack_connect; pub use self::jack_connect::*; +mod jack_event; pub use self::jack_event::*; +mod jack_port; pub use self::jack_port::*; pub(crate) type Usually = Result>; //////////////////////////////////////////////////////////////////////////////////// -///// A [AudioComponent] bound to a JACK client and a set of ports. -//pub struct JackDevice { - ///// The active JACK client of this device. - //pub client: DynamicAsyncClient, - ///// The device state, encapsulated for sharing between threads. - //pub state: Arc>>>, - ///// Unowned copies of the device's JACK ports, for connecting to the device. - ///// The "real" readable/writable `Port`s are owned by the `state`. - //pub ports: UnownedJackPorts, -//} - -//impl std::fmt::Debug for JackDevice { - //fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - //f.debug_struct("JackDevice") - //.field("ports", &self.ports) - //.finish() - //} -//} - -//impl Render for JackDevice { - //type Engine = E; - //fn min_size(&self, to: E::Size) -> Perhaps { - //self.state.read().unwrap().layout(to) - //} - //fn render(&self, to: &mut E::Output) -> Usually<()> { - //self.state.read().unwrap().render(to) - //} -//} - -//impl Handle for JackDevice { - //fn handle(&mut self, from: &E::Input) -> Perhaps { - //self.state.write().unwrap().handle(from) - //} -//} - -//impl Ports for JackDevice { - //fn audio_ins(&self) -> Usually>> { - //Ok(self.ports.audio_ins.values().collect()) - //} - //fn audio_outs(&self) -> Usually>> { - //Ok(self.ports.audio_outs.values().collect()) - //} - //fn midi_ins(&self) -> Usually>> { - //Ok(self.ports.midi_ins.values().collect()) - //} - //fn midi_outs(&self) -> Usually>> { - //Ok(self.ports.midi_outs.values().collect()) - //} -//} - -//impl JackDevice { - ///// Returns a locked mutex of the state's contents. - //pub fn state(&self) -> LockResult>>> { - //self.state.read() - //} - ///// Returns a locked mutex of the state's contents. - //pub fn state_mut(&self) -> LockResult>>> { - //self.state.write() - //} - //pub fn connect_midi_in(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(port, self.midi_ins()?[index])?) - //} - //pub fn connect_midi_out(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(self.midi_outs()?[index], port)?) - //} - //pub fn connect_audio_in(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(port, self.audio_ins()?[index])?) - //} - //pub fn connect_audio_out(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(self.audio_outs()?[index], port)?) - //} -//} - ///// `JackDevice` factory. Creates JACK `Client`s, performs port registration ///// and activation, and encapsulates a `AudioComponent` into a `JackDevice`. //pub struct Jack {