use crate::*; pub use ::jack as libjack; pub use ::jack::{ contrib::ClosureProcessHandler, NotificationHandler, Client, AsyncClient, ClientOptions, ClientStatus, ProcessScope, Control, CycleTimes, Frames, Port, PortId, PortSpec, Unowned, MidiIn, MidiOut, AudioIn, AudioOut, Transport, TransportState, MidiIter, MidiWriter, RawMidi, }; /// Implement [TryFrom<&Arc>>]: create app state from wrapped JACK handle. #[macro_export] macro_rules! from_jack { (|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc>> for $Struct $(<$($L),*$($T),*>)? { type Error = Box; fn try_from ($jack: &Arc>) -> Usually { Ok($cb) } } }; } /// 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 } } } /// This is a boxed realtime callback. pub type BoxedAudioHandler = Box Control + Send>; /// This is the notification handler wrapper for a boxed realtime callback. pub type DynamicAudioHandler = ClosureProcessHandler<(), BoxedAudioHandler>; /// This is a boxed [JackEvent] callback. pub type BoxedJackEventHandler = Box; /// This is the notification handler wrapper for a boxed [JackEvent] callback. pub type DynamicNotifications = 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 = AsyncClient; /// This is a connection which may be `Inactive`, `Activating`, or `Active`. /// In the `Active` and `Inactive` states, its `client` method returns a /// [Client] which you can use to talk to the JACK API. #[derive(Debug)] pub enum JackConnection { /// Before activation. Inactive(Client), /// During activation. Activating, /// After activation. Must not be dropped for JACK thread to persist. Active(DynamicAsyncClient), } from!(|jack: JackConnection|Client = match jack { JackConnection::Inactive(client) => client, JackConnection::Activating => panic!("jack client still activating"), JackConnection::Active(_) => panic!("jack client already activated"), }); impl JackConnection { pub fn new (name: &str) -> Usually { let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; Ok(Self::Inactive(client)) } /// Return the internal [Client] handle that lets you call the JACK API. pub fn client (&self) -> &Client { match self { Self::Inactive(ref client) => client, Self::Activating => panic!("jack client has not finished activation"), Self::Active(ref client) => client.as_client(), } } /// Activate a connection with an application. /// /// Consume a `JackConnection::Inactive`, /// binding a process callback and /// returning a `JackConnection::Active`. /// /// Needs work. Strange ownership situation between the callback /// and the host object. fn activate ( self, mut cb: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static, ) -> Usually>> where Self: Send + Sync + 'static { let client = Client::from(self); let state = Arc::new(RwLock::new(Self::Activating)); let event = Box::new(move|_|{/*TODO*/}) as Box; let events = Notifications(event); let frame = Box::new({let state = state.clone(); move|c: &_, s: &_|cb(&state, c, s)}); let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler); *state.write().unwrap() = Self::Active(client.activate_async(events, frames)?); Ok(state) } /// Activate a connection with an application. /// /// * Wrap a [JackConnection::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 ( self, init: impl FnOnce(&Arc>)->Usually ) -> Usually>> { // Wrap self for multiple ownership. let connection = Arc::new(RwLock::new(self)); // Run init callback. Return value is target. Target must retain clone of `connection`. let target = Arc::new(RwLock::new(init(&connection)?)); // Swap the `client` from the `JackConnection::Inactive` // for a `JackConnection::Activating`. let mut client = Self::Activating; std::mem::swap(&mut*connection.write().unwrap(), &mut client); // Replace the `JackConnection::Activating` with a // `JackConnection::Active` wrapping the [AsyncClient] // returned by the activation. *connection.write().unwrap() = Self::Active(Client::from(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 `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.client().port_by_name(name) } pub fn register_port (&self, name: &str, spec: PS) -> Usually> { Ok(self.client().register_port(name, spec)?) } } /// 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>; } 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) } 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 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_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) } } #[derive(Debug, Clone, PartialEq)] /// Event enum for JACK events. pub enum JackEvent { ThreadInit, Shutdown(ClientStatus, String), Freewheel(bool), SampleRate(Frames), ClientRegistration(String, bool), PortRegistration(PortId, bool), PortRename(PortId, String, String), 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 } } //////////////////////////////////////////////////////////////////////////////////// ///// 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)?) //} //} ///// 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) //})? //)? //)?} //}; //} ///// `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 {} ///////// /* /// Trait for things that may expose JACK ports. pub trait Ports { fn audio_ins(&self) -> Usually>> { Ok(vec![]) } fn audio_outs(&self) -> Usually>> { Ok(vec![]) } fn midi_ins(&self) -> Usually>> { Ok(vec![]) } 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 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 }) } */