use crate::*; use jack::*; /// Trait for things that have a JACK process callback. pub trait Audio { fn process(&mut self, _: &Client, _: &ProcessScope) -> Control { Control::Continue } } /// 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![]) } } /// 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 {} /// Wraps [Client] or [DynamicAsyncClient] in place. pub enum JackClient { Inactive(Client), Active(DynamicAsyncClient), } impl JackClient { pub fn client(&self) -> &Client { match self { Self::Inactive(ref client) => client, Self::Active(ref client) => client.as_client(), } } pub fn transport(&self) -> Transport { self.client().transport() } 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)?) } pub fn activate( self, state: &Arc>, mut process: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static, ) -> Usually { Ok(match self { Self::Active(_) => self, Self::Inactive(client) => Self::Active(client.activate_async( Notifications( Box::new(move |_| { /*TODO*/ }) as Box ), contrib::ClosureProcessHandler::new(Box::new({ let state = state.clone(); move |c: &Client, s: &ProcessScope| process(&state, c, s) }) as BoxedAudioHandler), )?), }) } } pub type DynamicAsyncClient = AsyncClient; type DynamicAudioHandler = contrib::ClosureProcessHandler<(), BoxedAudioHandler>; pub type BoxedAudioHandler = Box Control + Send>; /// Just run thing with JACK. Returns the activated client. pub fn jack_run(name: &str, app: &Arc>) -> Usually where T: Handle + Audio + Send + Sync + 'static, { let options = ClientOptions::NO_START_SERVER; let (client, _status) = Client::new(name, options)?; Ok(client.activate_async( Notifications(Box::new({ let _app = app.clone(); move |_event| { // FIXME: this deadlocks //app.lock().unwrap().handle(&event).unwrap(); } }) as Box), contrib::ClosureProcessHandler::new(Box::new({ let app = app.clone(); move |c: &Client, s: &ProcessScope| { app.write().unwrap().process(c, s) //Control::Continue } }) as BoxedAudioHandler), )?) } /// `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), contrib::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 } } 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 }) } /// Notification handler used by the [Jack] factory /// when constructing [JackDevice]s. pub type DynamicNotifications = Notifications>; #[derive(Debug)] /// 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 Widget for JackDevice { type Engine = E; fn layout(&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) })? )? )?} }; }