use jack::*; use crate::*; /// 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), 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 = ClosureProcessHandler; 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), 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)?, audio_outs: register_ports(&self.client, self.audio_outs, AudioOut)?, midi_ins: register_ports(&self.client, self.midi_ins, MidiIn)?, midi_outs: register_ports(&self.client, self.midi_outs, MidiOut)?, }; 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 } } 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); } 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::Area) -> Perhaps { self.state.read().unwrap().layout(to) } fn render (&self, to: &mut E) -> Perhaps { self.state.read().unwrap().render(to) } } impl Handle for JackDevice { fn handle (&mut self, from: &E::HandleInput) -> 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) })? )? )?} }; }