wip: general overhaul of core and ports
Some checks are pending
/ build (push) Waiting to run

This commit is contained in:
🪞👃🪞 2025-05-20 22:05:09 +03:00
parent 573534a9a6
commit 447638ee71
30 changed files with 824 additions and 548 deletions

View file

@ -0,0 +1,2 @@
mod audio_in; pub use self::audio_in::*;
mod audio_out; pub use self::audio_out::*;

View file

@ -0,0 +1,86 @@
use crate::*;
//impl_port!(AudioInput: AudioOut -> AudioIn |j, n|j.register_port::<AudioOut>(n));
#[derive(Debug)] pub struct AudioInput<'j> {
/// Handle to JACK client, for receiving reconnect events.
jack: Jack<'j>,
/// Port name
name: Arc<str>,
/// Port handle.
port: Port<AudioOut>,
/// List of ports to connect to.
connections: Vec<PortConnect>
}
impl<'j> AsRef<Port<AudioOut>> for AudioInput<'j> {
fn as_ref (&self) -> &Port<AudioOut> { &self.port }
}
impl<'j> AudioInput<'j> {
pub fn new (jack: &Jack, name: impl AsRef<str>, connect: &[PortConnect])
-> Usually<Self>
{
let port = Self {
port: jack.register_port::<AudioIn>(name.as_ref())?,
jack,
name: name.into(),
connections: connect.to_vec()
};
port.connect_to_matching()?;
Ok(port)
}
pub fn name (&self) -> &Arc<str> { &self.name }
pub fn port (&self) -> &Port<AudioOut> { &self.port }
pub fn port_mut (&mut self) -> &mut Port<AudioOut> { &mut self.port }
pub fn into_port (self) -> Port<AudioOut> { self.port }
pub fn close (self) -> Usually<()> {
let Self { jack, port, .. } = self;
Ok(jack.with_client(|client|client.unregister_port(port))?)
}
}
impl<'j> HasJack<'j> for AudioInput<'j> {
fn jack (&self) -> &'j Jack<'j> { &self.jack }
}
impl<'j> JackPort<'j> for AudioInput<'j> {
type Port = AudioIn;
type Pair = AudioOut;
fn port (&self) -> &Port<AudioOut> { &self.port }
}
//impl<'j, T: AsRef<str>> ConnectTo<'j, T> for AudioInput<'j> {
//fn connect_to (&self, to: &T) -> Usually<PortConnectStatus> {
//self.with_client(|c|if let Some(ref port) = c.port_by_name(to.as_ref()) {
//self.connect_to(port)
//} else {
//Ok(Missing)
//})
//}
//}
connect_to!(<'j>|self: AudioInput<'j>, port: &str|{
self.with_client(|c|if let Some(ref port) = c.port_by_name(port.as_ref()) {
self.connect_to(port)
} else {
Ok(Missing)
})
});
connect_to!(<'j>|self: AudioInput<'j>, port: Port<AudioOut>|{
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
}))
});
connect_to!(<'j>|self: AudioInput<'j>, port: Port<Unowned>|{
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<'j> ConnectAuto<'j> for AudioInput<'j> {
fn connections (&self) -> &[PortConnect] {
&self.connections
}
}

View file

@ -0,0 +1,77 @@
use crate::*;
//impl_port!(AudioOutput: AudioOut -> AudioIn |j, n|j.register_port::<AudioOut>(n));
#[derive(Debug)] pub struct AudioOutput<'j> {
/// Handle to JACK client, for receiving reconnect events.
jack: Jack<'j>,
/// Port name
name: Arc<str>,
/// Port handle.
port: Port<AudioOut>,
/// List of ports to connect to.
connections: Vec<PortConnect>
}
impl<'j> AsRef<Port<AudioOut>> for AudioOutput<'j> {
fn as_ref (&self) -> &Port<AudioOut> { &self.port }
}
impl<'j> AudioOutput<'j> {
pub fn new (jack: &Jack, name: impl AsRef<str>, connect: &[PortConnect])
-> Usually<Self>
{
let port = Self {
port: jack.register_port::<AudioOut>(name.as_ref())?,
jack,
name: name.into(),
connections: connect.to_vec()
};
port.connect_to_matching()?;
Ok(port)
}
pub fn name (&self) -> &Arc<str> { &self.name }
pub fn port (&self) -> &Port<AudioOut> { &self.port }
pub fn port_mut (&mut self) -> &mut Port<AudioOut> { &mut self.port }
pub fn into_port (self) -> Port<AudioOut> { self.port }
pub fn close (self) -> Usually<()> {
let Self { jack, port, .. } = self;
Ok(jack.with_client(|client|client.unregister_port(port))?)
}
}
impl<'j> HasJack<'j> for AudioOutput<'j> {
fn jack (&self) -> &'j Jack<'j> { &self.jack }
}
impl<'j> JackPort<'j> for AudioOutput<'j> {
type Port = AudioOut;
type Pair = AudioIn;
fn port (&self) -> &Port<AudioOut> { &self.port }
}
connect_to!(<'j>|self: AudioOutput<'j>, port: &str|{
self.with_client(|c|if let Some(ref port) = c.port_by_name(port.as_ref()) {
self.connect_to(port)
} else {
Ok(Missing)
})
});
connect_to!(<'j>|self: AudioOutput<'j>, port: Port<AudioIn>|{
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
}))
});
connect_to!(<'j>|self: AudioOutput<'j>, port: Port<Unowned>|{
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<'j> ConnectAuto<'j> for AudioOutput<'j> {
fn connections (&self) -> &[PortConnect] {
&self.connections
}
}

View file

@ -7,11 +7,12 @@ pub use ::jack::{*, contrib::{*, ClosureProcessHandler}};
//Unowned, MidiIn, MidiOut, AudioIn, AudioOut,
//};
pub(crate) use PortConnectName::*;
pub(crate) use PortConnectScope::*;
pub(crate) use PortConnectStatus::*;
pub(crate) use ConnectName::*;
pub(crate) use ConnectScope::*;
pub(crate) use ConnectStatus::*;
pub(crate) use std::sync::{Arc, RwLock};
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_client; pub use self::jack_client::*;
mod jack_device; pub use self::jack_device::*;
mod jack_handler; pub use self::jack_handler::*;
mod jack_port; pub use self::jack_port::*;

View file

@ -2,17 +2,57 @@ use crate::*;
use super::*;
use self::JackState::*;
impl<T: Has<Jack>> HasJack for T {
fn jack (&self) -> &Jack {
self.get()
}
impl<'j, T: Has<Jack<'j>>> HasJack<'j> for T {
fn jack (&'j self) -> &'j Jack<'j> { self.get() }
}
impl<'j> HasJack<'j> for Jack<'j> {
fn jack (&'j self) -> &'j Jack<'j> { self }
}
impl<'j> HasJack<'j> for &Jack<'j> {
fn jack (&'j self) -> &'j Jack<'j> { self }
}
/// Things that can provide a [jack::Client] reference.
pub trait HasJack {
pub trait HasJack<'j> {
/// Return the internal [jack::Client] handle
/// that lets you call the JACK API.
fn jack (&self) -> &Jack;
fn jack (&'j self) -> &'j Jack<'j>;
/// Run the JACK thread.
fn run <T: Audio + Send + Sync + 'j> (
&self, cb: impl FnOnce(&Jack)->Usually<T>
) -> Usually<Arc<RwLock<T>>> {
let jack = self.jack();
let app = Arc::new(RwLock::new(cb(jack)?));
let mut state = Activating;
std::mem::swap(&mut*jack.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({
let app = app.clone();
move|event|app.write().unwrap().handle(event)
}) as BoxedJackEventHandler<'j>),
// 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),
)?;
*jack.state.write().unwrap() = Active(client);
} else {
unreachable!();
}
Ok(app)
}
/// Run something with the client.
fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
match &*self.jack().state.read().unwrap() {
@ -34,7 +74,7 @@ pub trait HasJack {
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(()),
Ok(_) => Ok(()),
Err(e) => Err(e)
})?
}
@ -46,71 +86,25 @@ pub trait HasJack {
}
}
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 {
pub state: Arc<RwLock<JackState>>
pub struct Jack<'j> {
pub state: Arc<RwLock<JackState<'j>>>
}
impl Jack {
impl<'j> Jack<'j> {
pub fn new (name: &str) -> Usually<Self> {
Ok(Self {
state: JackState::new(Client::new(name, ClientOptions::NO_START_SERVER)?.0)
})
}
pub fn run <'j: 'static, T: Audio + 'j> (
&self, cb: impl FnOnce(&Jack)->Usually<T>
) -> Usually<Arc<RwLock<T>>> {
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({
let app = app.clone();
move|event|app.write().unwrap().handle(event)
}) 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, [JackState::client] returns a
/// [jack::Client], which you can use to talk to the JACK API.
#[derive(Debug, Default)]
pub enum JackState {
pub enum JackState<'j> {
/// Unused
#[default] Inert,
/// Before activation.
@ -118,64 +112,11 @@ pub enum JackState {
/// During activation.
Activating,
/// After activation. Must not be dropped for JACK thread to persist.
Active(DynamicAsyncClient<'static>),
Active(DynamicAsyncClient<'j>),
}
impl JackState {
impl<'j> JackState<'j> {
fn new (client: Client) -> Arc<RwLock<Self>> {
Arc::new(RwLock::new(Self::Inactive(client)))
}
}
/// This is a boxed realtime callback.
pub type BoxedAudioHandler<'j> =
Box<dyn FnMut(&Client, &ProcessScope) -> Control + Send + 'j>;
/// This is the notification handler wrapper for a boxed realtime callback.
pub type DynamicAudioHandler<'j> =
ClosureProcessHandler<(), BoxedAudioHandler<'j>>;
/// This is a boxed [JackEvent] callback.
pub type BoxedJackEventHandler<'j> =
Box<dyn Fn(JackEvent) + Send + Sync + 'j>;
/// This is the notification handler wrapper for a boxed [JackEvent] callback.
pub type DynamicNotifications<'j> =
Notifications<BoxedJackEventHandler<'j>>;
/// 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<'j>
= AsyncClient<DynamicNotifications<'j>, DynamicAudioHandler<'j>>;
/// Implement [Audio]: provide JACK callbacks.
#[macro_export] macro_rules! audio {
(|
$self1:ident:
$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident
|$cb:expr$(;|$self2:ident,$e:ident|$cb2:expr)?) => {
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
#[inline] fn process (&mut $self1, $c: &Client, $s: &ProcessScope) -> Control { $cb }
$(#[inline] fn handle (&mut $self2, $e: JackEvent) { $cb2 })?
}
}
}
/// Trait for thing that has a JACK process callback.
pub trait Audio: Send + Sync {
fn handle (&mut self, _event: JackEvent) {}
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
fn callback (
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
) -> Control where Self: Sized {
if let Ok(mut state) = state.write() {
state.process(client, scope)
} else {
Control::Quit
}
}
}

View file

@ -1,86 +1,86 @@
use crate::*
use crate::*;
/// A [AudioComponent] bound to a JACK client and a set of ports.
pub struct JackDevice<E: Engine> {
/// The active JACK client of this device.
pub client: DynamicAsyncClient,
/// The device state, encapsulated for sharing between threads.
pub state: Arc<RwLock<Box<dyn AudioComponent<E>>>>,
/// 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,
}
///// A [AudioComponent] bound to a JACK client and a set of ports.
//pub struct JackDevice<E: Engine> {
///// The active JACK client of this device.
//pub client: DynamicAsyncClient,
///// The device state, encapsulated for sharing between threads.
//pub state: Arc<RwLock<Box<dyn AudioComponent<E>>>>,
///// 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<E: Engine> std::fmt::Debug for JackDevice<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("JackDevice")
.field("ports", &self.ports)
.finish()
}
}
//impl<E: Engine> std::fmt::Debug for JackDevice<E> {
//fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
//f.debug_struct("JackDevice")
//.field("ports", &self.ports)
//.finish()
//}
//}
impl<E: Engine> Render for JackDevice<E> {
type Engine = E;
fn min_size(&self, to: E::Size) -> Perhaps<E::Size> {
self.state.read().unwrap().layout(to)
}
fn render(&self, to: &mut E::Output) -> Usually<()> {
self.state.read().unwrap().render(to)
}
}
//impl<E: Engine> Render for JackDevice<E> {
//type Engine = E;
//fn min_size(&self, to: E::Size) -> Perhaps<E::Size> {
//self.state.read().unwrap().layout(to)
//}
//fn render(&self, to: &mut E::Output) -> Usually<()> {
//self.state.read().unwrap().render(to)
//}
//}
impl<E: Engine> Handle<E> for JackDevice<E> {
fn handle(&mut self, from: &E::Input) -> Perhaps<E::Handled> {
self.state.write().unwrap().handle(from)
}
}
//impl<E: Engine> Handle<E> for JackDevice<E> {
//fn handle(&mut self, from: &E::Input) -> Perhaps<E::Handled> {
//self.state.write().unwrap().handle(from)
//}
//}
impl<E: Engine> Ports for JackDevice<E> {
fn audio_ins (&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(self.ports.audio_ins.values().collect())
}
fn audio_outs (&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(self.ports.audio_outs.values().collect())
}
fn midi_ins (&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(self.ports.midi_ins.values().collect())
}
fn midi_outs (&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(self.ports.midi_outs.values().collect())
}
}
//impl<E: Engine> Ports for JackDevice<E> {
//fn audio_ins (&self) -> Usually<Vec<&Port<Unowned>>> {
//Ok(self.ports.audio_ins.values().collect())
//}
//fn audio_outs (&self) -> Usually<Vec<&Port<Unowned>>> {
//Ok(self.ports.audio_outs.values().collect())
//}
//fn midi_ins (&self) -> Usually<Vec<&Port<Unowned>>> {
//Ok(self.ports.midi_ins.values().collect())
//}
//fn midi_outs (&self) -> Usually<Vec<&Port<Unowned>>> {
//Ok(self.ports.midi_outs.values().collect())
//}
//}
impl<E: Engine> JackDevice<E> {
/// Returns a locked mutex of the state's contents.
pub fn state(&self) -> LockResult<RwLockReadGuard<Box<dyn AudioComponent<E>>>> {
self.state.read()
}
/// Returns a locked mutex of the state's contents.
pub fn state_mut(&self) -> LockResult<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
self.state.write()
}
pub fn connect_midi_in(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
Ok(self
.client
.as_client()
.connect_ports(port, self.midi_ins()?[index])?)
}
pub fn connect_midi_out(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
Ok(self
.client
.as_client()
.connect_ports(self.midi_outs()?[index], port)?)
}
pub fn connect_audio_in(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
Ok(self
.client
.as_client()
.connect_ports(port, self.audio_ins()?[index])?)
}
pub fn connect_audio_out(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
Ok(self
.client
.as_client()
.connect_ports(self.audio_outs()?[index], port)?)
}
}
//impl<E: Engine> JackDevice<E> {
///// Returns a locked mutex of the state's contents.
//pub fn state(&self) -> LockResult<RwLockReadGuard<Box<dyn AudioComponent<E>>>> {
//self.state.read()
//}
///// Returns a locked mutex of the state's contents.
//pub fn state_mut(&self) -> LockResult<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
//self.state.write()
//}
//pub fn connect_midi_in(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
//Ok(self
//.client
//.as_client()
//.connect_ports(port, self.midi_ins()?[index])?)
//}
//pub fn connect_midi_out(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
//Ok(self
//.client
//.as_client()
//.connect_ports(self.midi_outs()?[index], port)?)
//}
//pub fn connect_audio_in(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
//Ok(self
//.client
//.as_client()
//.connect_ports(port, self.audio_ins()?[index])?)
//}
//pub fn connect_audio_out(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
//Ok(self
//.client
//.as_client()
//.connect_ports(self.audio_outs()?[index], port)?)
//}
//}

View file

@ -1,56 +0,0 @@
use crate::*;
use super::*;
/// Event enum for JACK events.
#[derive(Debug, Clone, PartialEq)] pub enum JackEvent {
ThreadInit,
Shutdown(ClientStatus, Arc<str>),
Freewheel(bool),
SampleRate(Frames),
ClientRegistration(Arc<str>, bool),
PortRegistration(PortId, bool),
PortRename(PortId, Arc<str>, Arc<str>),
PortsConnected(PortId, PortId, bool),
GraphReorder,
XRun,
}
/// Generic notification handler that emits [JackEvent]
pub struct Notifications<T: Fn(JackEvent) + Send>(pub T);
impl<T: Fn(JackEvent) + Send> NotificationHandler for Notifications<T> {
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
}
}

View file

@ -0,0 +1,105 @@
use crate::*;
use super::*;
/// Trait for thing that has a JACK process callback.
pub trait Audio {
fn handle (&mut self, _event: JackEvent) {}
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
fn callback (
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
) -> Control where Self: Sized {
if let Ok(mut state) = state.write() {
state.process(client, scope)
} else {
Control::Quit
}
}
}
/// Implement [Audio]: provide JACK callbacks.
#[macro_export] macro_rules! audio {
(|
$self1:ident:
$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident
|$cb:expr$(;|$self2:ident,$e:ident|$cb2:expr)?) => {
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
#[inline] fn process (&mut $self1, $c: &Client, $s: &ProcessScope) -> Control { $cb }
$(#[inline] fn handle (&mut $self2, $e: JackEvent) { $cb2 })?
}
}
}
/// Event enum for JACK events.
#[derive(Debug, Clone, PartialEq)] pub enum JackEvent {
ThreadInit,
Shutdown(ClientStatus, Arc<str>),
Freewheel(bool),
SampleRate(Frames),
ClientRegistration(Arc<str>, bool),
PortRegistration(PortId, bool),
PortRename(PortId, Arc<str>, Arc<str>),
PortsConnected(PortId, PortId, bool),
GraphReorder,
XRun,
}
/// Generic notification handler that emits [JackEvent]
pub struct Notifications<T: Fn(JackEvent) + Send>(pub T);
impl<T: Fn(JackEvent) + Send> NotificationHandler for Notifications<T> {
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
}
}
/// 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<'j>
= AsyncClient<DynamicNotifications<'j>, DynamicAudioHandler<'j>>;
/// This is the notification handler wrapper for a boxed realtime callback.
pub type DynamicAudioHandler<'j> =
ClosureProcessHandler<(), BoxedAudioHandler<'j>>;
/// This is a boxed realtime callback.
pub type BoxedAudioHandler<'j> =
Box<dyn FnMut(&Client, &ProcessScope) -> Control + Send + Sync + 'j>;
/// This is the notification handler wrapper for a boxed [JackEvent] callback.
pub type DynamicNotifications<'j> =
Notifications<BoxedJackEventHandler<'j>>;
/// This is a boxed [JackEvent] callback.
pub type BoxedJackEventHandler<'j> =
Box<dyn Fn(JackEvent) + Send + Sync + 'j>;

View file

@ -1,18 +1,28 @@
use crate::*;
use super::*;
pub trait JackPort: HasJack {
pub trait JackPort<'j>: HasJack<'j> {
type Port: PortSpec;
type Pair: PortSpec;
fn port (&self) -> &Port<Self::Port>;
}
pub trait JackPortConnect<T>: JackPort {
fn connect_to (&self, to: T) -> Usually<PortConnectStatus>;
pub trait ConnectTo<'j, T>: JackPort<'j> {
fn connect_to (&'j self, to: &T) -> Usually<ConnectStatus>;
}
pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port<Unowned>> {
fn conn (&self) -> &[PortConnect];
#[macro_export] macro_rules! connect_to {
(<$lt:lifetime>|$self:ident:$Self:ty, $port:ident:$Port:ty|$expr:expr) => {
impl<$lt> ConnectTo<$lt, &$Port> for $Self {
fn connect_to (&$self, $port: &$Port) -> Usually<ConnectStatus> {
$expr
}
}
};
}
pub trait ConnectAuto<'j>: JackPort<'j> + ConnectTo<'j, &'j Port<Unowned>> {
fn connections (&self) -> &[Connect];
fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec<String> {
self.with_client(|c|c.ports(re_name, re_type, flags))
}
@ -23,7 +33,7 @@ pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port<Unowne
self.with_client(|c|c.port_by_name(name.as_ref()))
}
fn connect_to_matching (&self) -> Usually<()> {
for connect in self.conn().iter() {
for connect in self.connections().iter() {
//panic!("{connect:?}");
let status = match &connect.name {
Exact(name) => self.connect_exact(name),
@ -33,9 +43,9 @@ pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port<Unowne
}
Ok(())
}
fn connect_exact (
&self, name: &str
) -> Usually<Vec<(Port<Unowned>, Arc<str>, PortConnectStatus)>> {
fn connect_exact (&self, name: &str) ->
Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>
{
self.with_client(|c|{
let mut status = vec![];
for port in c.ports(None, None, PortFlags::empty()).iter() {
@ -54,8 +64,8 @@ pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port<Unowne
})
}
fn connect_regexp (
&self, re: &str, scope: PortConnectScope
) -> Usually<Vec<(Port<Unowned>, Arc<str>, PortConnectStatus)>> {
&self, re: &str, scope: ConnectScope
) -> Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>> {
self.with_client(|c|{
let mut status = vec![];
let ports = c.ports(Some(&re), None, PortFlags::empty());
@ -75,33 +85,33 @@ pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port<Unowne
}
#[derive(Clone, Debug, PartialEq)]
pub enum PortConnectName {
pub enum ConnectName {
/** Exact match */
Exact(Arc<str>),
/** Match regular expression */
RegExp(Arc<str>),
}
#[derive(Clone, Copy, Debug, PartialEq)] pub enum PortConnectScope {
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectScope {
One,
All
}
#[derive(Clone, Copy, Debug, PartialEq)] pub enum PortConnectStatus {
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectStatus {
Missing,
Disconnected,
Connected,
Mismatch,
}
#[derive(Clone, Debug)] pub struct PortConnect {
pub name: PortConnectName,
pub scope: PortConnectScope,
pub status: Arc<RwLock<Vec<(Port<Unowned>, Arc<str>, PortConnectStatus)>>>,
#[derive(Clone, Debug)] pub struct Connect {
pub name: ConnectName,
pub scope: ConnectScope,
pub status: Arc<RwLock<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>>,
pub info: Arc<String>,
}
impl PortConnect {
impl Connect {
pub fn collect (exact: &[impl AsRef<str>], re: &[impl AsRef<str>], re_all: &[impl AsRef<str>])
-> Vec<Self>
{
@ -147,89 +157,3 @@ impl PortConnect {
format!(" ({}) {} {}", status, scope, name).into()
}
}
macro_rules! impl_port {
($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.
jack: Jack,
/// Port name
name: Arc<str>,
/// Port handle.
port: Port<$Spec>,
/// List of ports to connect to.
conn: Vec<PortConnect>
}
impl AsRef<Port<$Spec>> for $Name { fn as_ref (&self) -> &Port<$Spec> { &self.port } }
impl $Name {
pub fn new ($jack: &Jack, name: impl AsRef<str>, connect: &[PortConnect])
-> Usually<Self>
{
let $name = name.as_ref();
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)
}
pub fn name (&self) -> &Arc<str> { &self.name }
pub fn port (&self) -> &Port<$Spec> { &self.port }
pub fn port_mut (&mut self) -> &mut Port<$Spec> { &mut self.port }
pub fn into_port (self) -> Port<$Spec> { self.port }
pub fn close (self) -> Usually<()> {
let Self { jack, port, .. } = self;
Ok(jack.with_client(|client|client.unregister_port(port))?)
}
}
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 }
}
impl JackPortConnect<&str> for $Name {
fn connect_to (&self, to: &str) -> Usually<PortConnectStatus> {
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<Unowned>> for $Name {
fn connect_to (&self, port: &Port<Unowned>) -> Usually<PortConnectStatus> {
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<PortConnectStatus> {
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 -> AudioOut |j, n|j.register_port::<AudioIn>(n));
impl_port!(JackAudioOut: AudioOut -> AudioIn |j, n|j.register_port::<AudioOut>(n));
impl_port!(JackMidiIn: MidiIn -> MidiOut |j, n|j.register_port::<MidiIn>(n));

View file

@ -1,9 +1,94 @@
#![feature(type_alias_impl_trait)]
//macro_rules! impl_port {
//($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.
//jack: Jack<'static>,
///// Port name
//name: Arc<str>,
///// Port handle.
//port: Port<$Spec>,
///// List of ports to connect to.
//conn: Vec<PortConnect>
//}
//impl AsRef<Port<$Spec>> for $Name {
//fn as_ref (&self) -> &Port<$Spec> { &self.port }
//}
//impl $Name {
//pub fn new ($jack: &Jack, name: impl AsRef<str>, connect: &[PortConnect])
//-> Usually<Self>
//{
//let $name = name.as_ref();
//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)
//}
//pub fn name (&self) -> &Arc<str> { &self.name }
//pub fn port (&self) -> &Port<$Spec> { &self.port }
//pub fn port_mut (&mut self) -> &mut Port<$Spec> { &mut self.port }
//pub fn into_port (self) -> Port<$Spec> { self.port }
//pub fn close (self) -> Usually<()> {
//let Self { jack, port, .. } = self;
//Ok(jack.with_client(|client|client.unregister_port(port))?)
//}
//}
//impl HasJack<'static> for $Name {
//fn jack (&self) -> &'static Jack<'static> { &self.jack }
//}
//impl JackPort<'static> for $Name {
//type Port = $Spec;
//type Pair = $Pair;
//fn port (&self) -> &Port<$Spec> { &self.port }
//}
//impl ConnectTo<'static, &str> for $Name {
//fn connect_to (&self, to: &str) -> Usually<PortConnectStatus> {
//self.with_client(|c|if let Some(ref port) = c.port_by_name(to.as_ref()) {
//self.connect_to(port)
//} else {
//Ok(Missing)
//})
//}
//}
//impl ConnectTo<'static, &Port<Unowned>> for $Name {
//fn connect_to (&self, port: &Port<Unowned>) -> Usually<PortConnectStatus> {
//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 ConnectTo<'static, &Port<$Pair>> for $Name {
//fn connect_to (&self, port: &Port<$Pair>) -> Usually<PortConnectStatus> {
//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 ConnectAuto<'static> for $Name {
//fn connections (&self) -> &[PortConnect] {
//&self.conn
//}
//}
//};
//}
mod time; pub use self::time::*;
mod note; pub use self::note::*;
pub mod jack; pub use self::jack::*;
pub mod midi; pub use self::midi::*;
pub mod audio; pub use self::audio::*;
pub(crate) use std::sync::{Arc, atomic::{AtomicUsize, AtomicBool, Ordering::Relaxed}};
pub(crate) use std::fmt::Debug;

View file

@ -1,29 +1,119 @@
use crate::*;
impl JackMidiIn {
//impl_port!(MidiInput: MidiOut -> MidiIn |j, n|j.register_port::<MidiOut>(n));
#[derive(Debug)] pub struct MidiInput<'j> {
/// Handle to JACK client, for receiving reconnect events.
jack: Jack<'j>,
/// Port name
name: Arc<str>,
/// Port handle.
port: Port<MidiOut>,
/// List of ports to connect to.
connections: Vec<Connect>
}
impl<'j> AsRef<Port<MidiOut>> for MidiInput<'j> {
fn as_ref (&self) -> &Port<MidiOut> { &self.port }
}
impl<'j> MidiInput<'j> {
pub fn new (jack: &Jack, name: impl AsRef<str>, connect: &[Connect])
-> Usually<Self>
{
let port = Self {
port: jack.register_port::<MidiIn>(name.as_ref())?,
jack,
name: name.into(),
connections: connect.to_vec()
};
port.connect_to_matching()?;
Ok(port)
}
pub fn name (&self) -> &Arc<str> {
&self.name
}
pub fn port (&self) -> &Port<MidiOut> {
&self.port
}
pub fn port_mut (&mut self) -> &mut Port<MidiOut> {
&mut self.port
}
pub fn into_port (self) -> Port<MidiOut> {
self.port
}
pub fn close (self) -> Usually<()> {
let Self { jack, port, .. } = self;
Ok(jack.with_client(|client|client.unregister_port(port))?)
}
pub fn parsed <'a> (&'a self, scope: &'a ProcessScope) -> impl Iterator<Item=(usize, LiveEvent<'a>, &'a [u8])> {
parse_midi_input(self.port().iter(scope))
}
}
#[tengri_proc::command(JackMidiIn)]
impl MidiInputCommand {
fn _todo_ (_port: &mut JackMidiIn) -> Perhaps<Self> { Ok(None) }
impl<'j> HasJack<'j> for MidiInput<'j> {
fn jack (&self) -> &'j Jack<'j> { &self.jack }
}
impl<'j> JackPort<'j> for MidiInput<'j> {
type Port = MidiIn;
type Pair = MidiOut;
fn port (&self) -> &Port<MidiOut> { &self.port }
}
//impl<'j, T: AsRef<str>> ConnectTo<'j, T> for MidiInput<'j> {
//fn connect_to (&self, to: &T) -> Usually<ConnectStatus> {
//self.with_client(|c|if let Some(ref port) = c.port_by_name(to.as_ref()) {
//self.connect_to(port)
//} else {
//Ok(Missing)
//})
//}
//}
connect_to!(<'j>|self: MidiInput<'j>, port: &str|{
self.with_client(|c|if let Some(ref port) = c.port_by_name(port.as_ref()) {
self.connect_to(port)
} else {
Ok(Missing)
})
});
connect_to!(<'j>|self: MidiInput<'j>, port: Port<MidiOut>|{
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
}))
});
connect_to!(<'j>|self: MidiInput<'j>, port: Port<Unowned>|{
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<'j> ConnectAuto<'j> for MidiInput<'j> {
fn connections (&self) -> &[Connect] {
&self.connections
}
}
impl<T: Has<Vec<JackMidiIn>>> HasMidiIns for T {
fn midi_ins (&self) -> &Vec<JackMidiIn> {
#[tengri_proc::command(MidiInput)]
impl MidiInputCommand {
//fn _todo_ (_port: &mut MidiInput) -> Perhaps<Self> { Ok(None) }
}
impl<T: Has<Vec<MidiInput>>> HasMidiIns for T {
fn midi_ins (&self) -> &Vec<MidiInput> {
self.get()
}
fn midi_ins_mut (&mut self) -> &mut Vec<JackMidiIn> {
fn midi_ins_mut (&mut self) -> &mut Vec<MidiInput> {
self.get_mut()
}
}
/// Trait for thing that may receive MIDI.
pub trait HasMidiIns {
fn midi_ins (&self) -> &Vec<JackMidiIn>;
fn midi_ins_mut (&mut self) -> &mut Vec<JackMidiIn>;
fn midi_ins (&self) -> &Vec<MidiInput>;
fn midi_ins_mut (&mut self) -> &mut Vec<MidiInput>;
/// Collect MIDI input from app ports (TODO preallocate large buffers)
fn midi_input_collect <'a> (&'a self, scope: &'a ProcessScope) -> CollectedMidiInput<'a> {
self.midi_ins().iter()
@ -33,7 +123,7 @@ pub trait HasMidiIns {
.collect::<Vec<_>>()
}
fn midi_ins_with_sizes <'a> (&'a self) ->
impl Iterator<Item=(usize, &Arc<str>, &[PortConnect], usize, usize)> + Send + Sync + 'a
impl Iterator<Item=(usize, &Arc<str>, &[Connect], usize, usize)> + Send + Sync + 'a
{
let mut y = 0;
self.midi_ins().iter().enumerate().map(move|(i, input)|{
@ -47,10 +137,10 @@ pub trait HasMidiIns {
pub type CollectedMidiInput<'a> = Vec<Vec<(u32, Result<LiveEvent<'a>, MidiError>)>>;
impl<T: HasMidiIns + HasJack> AddMidiIn for T {
impl<'j, T: HasMidiIns + HasJack<'j>> AddMidiIn for T {
fn midi_in_add (&mut self) -> Usually<()> {
let index = self.midi_ins().len();
let port = JackMidiIn::new(self.jack(), &format!("M/{index}"), &[])?;
let port = MidiInput::new(self.jack(), &format!("M/{index}"), &[])?;
self.midi_ins_mut().push(port);
Ok(())
}

View file

@ -1,14 +1,14 @@
use crate::*;
#[derive(Debug)] pub struct JackMidiOut {
#[derive(Debug)] pub struct MidiOutput {
/// Handle to JACK client, for receiving reconnect events.
jack: Jack,
jack: Jack<'static>,
/// Port name
name: Arc<str>,
/// Port handle.
port: Port<MidiOut>,
/// List of ports to connect to.
conn: Vec<PortConnect>,
conn: Vec<Connect>,
/// List of currently held notes.
held: Arc<RwLock<[bool;128]>>,
/// Buffer
@ -17,10 +17,10 @@ use crate::*;
output_buffer: Vec<Vec<Vec<u8>>>,
}
has!(Jack: |self: JackMidiOut|self.jack);
has!(Jack<'static>: |self: MidiOutput|self.jack);
impl JackMidiOut {
pub fn new (jack: &Jack, name: impl AsRef<str>, connect: &[PortConnect])
impl MidiOutput {
pub fn new (jack: &Jack, name: impl AsRef<str>, connect: &[Connect])
-> Usually<Self>
{
let jack = jack.clone();
@ -94,20 +94,20 @@ impl JackMidiOut {
}
}
impl AsRef<Port<MidiOut>> for JackMidiOut {
impl AsRef<Port<MidiOut>> for MidiOutput {
fn as_ref (&self) -> &Port<MidiOut> {
&self.port
}
}
impl JackPort for JackMidiOut {
impl JackPort<'static> for MidiOutput {
type Port = MidiOut;
type Pair = MidiIn;
fn port (&self) -> &Port<MidiOut> { &self.port }
}
impl JackPortConnect<&str> for JackMidiOut {
fn connect_to (&self, to: &str) -> Usually<PortConnectStatus> {
impl ConnectTo<'static, &str> for MidiOutput {
fn connect_to (&self, to: &str) -> Usually<ConnectStatus> {
self.with_client(|c|if let Some(ref port) = c.port_by_name(to.as_ref()) {
self.connect_to(port)
} else {
@ -116,8 +116,8 @@ impl JackPortConnect<&str> for JackMidiOut {
}
}
impl JackPortConnect<&Port<Unowned>> for JackMidiOut {
fn connect_to (&self, port: &Port<Unowned>) -> Usually<PortConnectStatus> {
impl ConnectTo<'static, &Port<Unowned>> for MidiOutput {
fn connect_to (&self, port: &Port<Unowned>) -> Usually<ConnectStatus> {
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) {
@ -128,8 +128,8 @@ impl JackPortConnect<&Port<Unowned>> for JackMidiOut {
}
}
impl JackPortConnect<&Port<MidiIn>> for JackMidiOut {
fn connect_to (&self, port: &Port<MidiIn>) -> Usually<PortConnectStatus> {
impl ConnectTo<'static, &Port<MidiIn>> for MidiOutput {
fn connect_to (&self, port: &Port<MidiIn>) -> Usually<ConnectStatus> {
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) {
@ -140,22 +140,22 @@ impl JackPortConnect<&Port<MidiIn>> for JackMidiOut {
}
}
impl JackPortAutoconnect for JackMidiOut {
fn conn (&self) -> &[PortConnect] {
impl ConnectAuto<'static> for MidiOutput {
fn connections (&self) -> &[Connect] {
&self.conn
}
}
#[tengri_proc::command(JackMidiOut)]
#[tengri_proc::command(MidiOutput)]
impl MidiOutputCommand {
fn _todo_ (_port: &mut JackMidiOut) -> Perhaps<Self> { Ok(None) }
fn _todo_ (_port: &mut MidiOutput) -> Perhaps<Self> { Ok(None) }
}
impl<T: Has<Vec<JackMidiOut>>> HasMidiOuts for T {
fn midi_outs (&self) -> &Vec<JackMidiOut> {
impl<T: Has<Vec<MidiOutput>>> HasMidiOuts for T {
fn midi_outs (&self) -> &Vec<MidiOutput> {
self.get()
}
fn midi_outs_mut (&mut self) -> &mut Vec<JackMidiOut> {
fn midi_outs_mut (&mut self) -> &mut Vec<MidiOutput> {
self.get_mut()
}
}
@ -163,10 +163,10 @@ impl<T: Has<Vec<JackMidiOut>>> HasMidiOuts for T {
/// Trait for thing that may output MIDI.
pub trait HasMidiOuts {
fn midi_outs (&self) -> &Vec<JackMidiOut>;
fn midi_outs_mut (&mut self) -> &mut Vec<JackMidiOut>;
fn midi_outs (&self) -> &Vec<MidiOutput>;
fn midi_outs_mut (&mut self) -> &mut Vec<MidiOutput>;
fn midi_outs_with_sizes <'a> (&'a self) ->
impl Iterator<Item=(usize, &Arc<str>, &[PortConnect], usize, usize)> + Send + Sync + 'a
impl Iterator<Item=(usize, &Arc<str>, &[Connect], usize, usize)> + Send + Sync + 'a
{
let mut y = 0;
self.midi_outs().iter().enumerate().map(move|(i, output)|{
@ -184,10 +184,10 @@ pub trait HasMidiOuts {
}
/// Trail for thing that may gain new MIDI ports.
impl<T: HasMidiOuts + HasJack> AddMidiOut for T {
impl<'j, T: HasMidiOuts + HasJack<'j>> AddMidiOut for T {
fn midi_out_add (&mut self) -> Usually<()> {
let index = self.midi_outs().len();
let port = JackMidiOut::new(self.jack(), &format!("{index}/M"), &[])?;
let port = MidiOutput::new(self.jack(), &format!("{index}/M"), &[])?;
self.midi_outs_mut().push(port);
Ok(())
}