mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
Merge pull request 'Update JACK to 0.13' (#1) from wmedrano/tek:main into main
Reviewed-on: https://codeberg.org/unspeaker/tek/pulls/1
This commit is contained in:
commit
0646e0fe0b
4 changed files with 253 additions and 213 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
|
@ -1094,11 +1094,11 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jack"
|
name = "jack"
|
||||||
version = "0.10.0"
|
version = "0.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce722655a29b13bb98ec7e8ba9dc65d670b9b37c7b1c09775c7f7516811c5a36"
|
checksum = "78a4ae24f4ee29676aef8330fed1104e72f314cab16643dbeb61bfd99b4a8273"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 2.6.0",
|
||||||
"jack-sys",
|
"jack-sys",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -1107,14 +1107,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jack-sys"
|
name = "jack-sys"
|
||||||
version = "0.4.0"
|
version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e9d70559ff166d148ccb750ddd77702af760718f3a752c731add168c22c16a9f"
|
checksum = "6013b7619b95a22b576dfb43296faa4ecbe40abbdb97dfd22ead520775fc86ab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"libloading 0.7.4",
|
"libloading 0.7.4",
|
||||||
|
"log",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,92 +1,92 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use tek_core::Direction;
|
use tek_core::Direction;
|
||||||
use tek_sequencer::{TransportToolbar, Arranger};
|
|
||||||
use tek_mixer::Mixer;
|
use tek_mixer::Mixer;
|
||||||
|
use tek_sequencer::{Arranger, TransportToolbar};
|
||||||
|
|
||||||
/// Root of application state.
|
/// Root of application state.
|
||||||
pub struct App {
|
pub struct App {
|
||||||
/// Whether the currently focused section has input priority
|
/// Whether the currently focused section has input priority
|
||||||
pub entered: bool,
|
pub entered: bool,
|
||||||
/// Currently focused section
|
/// Currently focused section
|
||||||
pub section: AppFocus,
|
pub section: AppFocus,
|
||||||
/// Transport model and view.
|
/// Transport model and view.
|
||||||
pub transport: TransportToolbar,
|
pub transport: TransportToolbar,
|
||||||
/// Arranger (contains sequencers)
|
/// Arranger (contains sequencers)
|
||||||
pub arranger: Arranger,
|
pub arranger: Arranger,
|
||||||
/// Mixer (contains tracks)
|
/// Mixer (contains tracks)
|
||||||
pub mixer: Mixer,
|
pub mixer: Mixer,
|
||||||
/// Main JACK client.
|
/// Main JACK client.
|
||||||
pub jack: Option<JackClient>,
|
pub jack: Option<JackClientt>,
|
||||||
/// Map of external MIDI outs in the jack graph
|
/// Map of external MIDI outs in the jack graph
|
||||||
/// to internal MIDI ins of this app.
|
/// to internal MIDI ins of this app.
|
||||||
pub midi_in: Option<Arc<Port<MidiIn>>>,
|
pub midi_in: Option<Arc<Port<MidiIn>>>,
|
||||||
/// Names of ports to connect to main MIDI IN.
|
/// Names of ports to connect to main MIDI IN.
|
||||||
pub midi_ins: Vec<String>,
|
pub midi_ins: Vec<String>,
|
||||||
/// Display mode of chain section
|
/// Display mode of chain section
|
||||||
pub chain_mode: bool,
|
pub chain_mode: bool,
|
||||||
/// Main audio outputs.
|
/// Main audio outputs.
|
||||||
pub audio_outs: Vec<Arc<Port<Unowned>>>,
|
pub audio_outs: Vec<Arc<Port<Unowned>>>,
|
||||||
/// Number of frames requested by process callback
|
/// Number of frames requested by process callback
|
||||||
chunk_size: usize,
|
chunk_size: usize,
|
||||||
/// Paths to user directories
|
/// Paths to user directories
|
||||||
_xdg: Option<Arc<XdgApp>>,
|
_xdg: Option<Arc<XdgApp>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new () -> Usually<Self> {
|
pub fn new() -> Usually<Self> {
|
||||||
let xdg = Arc::new(XdgApp::new("tek")?);
|
let xdg = Arc::new(XdgApp::new("tek")?);
|
||||||
let first_run = AppPaths::new(&xdg)?.should_create();
|
let first_run = AppPaths::new(&xdg)?.should_create();
|
||||||
let jack = JackClient::Inactive(
|
let jack = JackClient::Inactive(Client::new("tek", ClientOptions::NO_START_SERVER)?.0);
|
||||||
Client::new("tek", ClientOptions::NO_START_SERVER)?.0
|
*MODAL.lock().unwrap() =
|
||||||
);
|
first_run.then(|| ExitableComponent::boxed(SetupModal(Some(xdg.clone()), false)));
|
||||||
*MODAL.lock().unwrap() = first_run.then(||{
|
|
||||||
ExitableComponent::boxed(SetupModal(Some(xdg.clone()), false))
|
|
||||||
});
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
entered: true,
|
entered: true,
|
||||||
section: AppFocus::default(),
|
section: AppFocus::default(),
|
||||||
transport: TransportToolbar::new(Some(jack.transport())),
|
transport: TransportToolbar::new(Some(jack.transport())),
|
||||||
arranger: Arranger::new(""),
|
arranger: Arranger::new(""),
|
||||||
mixer: Mixer::new("")?,
|
mixer: Mixer::new("")?,
|
||||||
jack: Some(jack),
|
jack: Some(jack),
|
||||||
audio_outs: vec![],
|
audio_outs: vec![],
|
||||||
chain_mode: false,
|
chain_mode: false,
|
||||||
chunk_size: 0,
|
chunk_size: 0,
|
||||||
midi_in: None,
|
midi_in: None,
|
||||||
midi_ins: vec![],
|
midi_ins: vec![],
|
||||||
_xdg: Some(xdg),
|
_xdg: Some(xdg),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn client (&self) -> &Client {
|
pub fn client(&self) -> &Client {
|
||||||
self.jack.as_ref().unwrap().client()
|
self.jack.as_ref().unwrap().client()
|
||||||
}
|
}
|
||||||
pub fn audio_out (&self, index: usize) -> Option<Arc<Port<Unowned>>> {
|
pub fn audio_out(&self, index: usize) -> Option<Arc<Port<Unowned>>> {
|
||||||
self.audio_outs.get(index).map(|x|x.clone())
|
self.audio_outs.get(index).map(|x| x.clone())
|
||||||
}
|
}
|
||||||
pub fn with_midi_ins (mut self, names: &[&str]) -> Usually<Self> {
|
pub fn with_midi_ins(mut self, names: &[&str]) -> Usually<Self> {
|
||||||
self.midi_ins = names.iter().map(|x|x.to_string()).collect();
|
self.midi_ins = names.iter().map(|x| x.to_string()).collect();
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
pub fn with_audio_outs (mut self, names: &[&str]) -> Usually<Self> {
|
pub fn with_audio_outs(mut self, names: &[&str]) -> Usually<Self> {
|
||||||
let client = self.client();
|
let client = self.client();
|
||||||
self.audio_outs = names
|
self.audio_outs = names
|
||||||
.iter()
|
.iter()
|
||||||
.map(|name|client
|
.map(|name| {
|
||||||
.ports(Some(name), None, PortFlags::empty())
|
client
|
||||||
.get(0)
|
.ports(Some(name), None, PortFlags::empty())
|
||||||
.map(|name|client.port_by_name(name)))
|
.get(0)
|
||||||
|
.map(|name| client.port_by_name(name))
|
||||||
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
.filter_map(|x|x)
|
.filter_map(|x| x)
|
||||||
.map(Arc::new)
|
.map(Arc::new)
|
||||||
.collect();
|
.collect();
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
pub fn activate (
|
pub fn activate(
|
||||||
mut self, init: Option<impl FnOnce(&Arc<RwLock<Self>>)->Usually<()>>
|
mut self,
|
||||||
|
init: Option<impl FnOnce(&Arc<RwLock<Self>>) -> Usually<()>>,
|
||||||
) -> Usually<Arc<RwLock<Self>>> {
|
) -> Usually<Arc<RwLock<Self>>> {
|
||||||
let jack = self.jack.take().expect("no jack client");
|
let jack = self.jack.take().expect("no jack client");
|
||||||
let app = Arc::new(RwLock::new(self));
|
let app = Arc::new(RwLock::new(self));
|
||||||
app.write().unwrap().jack = Some(jack.activate(&app.clone(), |state, client, scope|{
|
app.write().unwrap().jack = Some(jack.activate(&app.clone(), |state, client, scope| {
|
||||||
state.write().unwrap().process(client, scope)
|
state.write().unwrap().process(client, scope)
|
||||||
})?);
|
})?);
|
||||||
if let Some(init) = init {
|
if let Some(init) = init {
|
||||||
|
|
@ -96,44 +96,56 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render!(App |self, buf, area| {
|
render!(
|
||||||
Split::down()
|
App | self,
|
||||||
.add_ref(&self.transport)
|
buf,
|
||||||
.add_ref(&self.arranger)
|
area | {
|
||||||
.add(If(self.arranger.selected.is_clip(), &Split::right()
|
Split::down()
|
||||||
.add(tek_mixer::TrackView {
|
.add_ref(&self.transport)
|
||||||
direction: Direction::Down,
|
.add_ref(&self.arranger)
|
||||||
entered: self.entered,
|
.add(If(
|
||||||
focused: self.section == AppFocus::Chain,
|
self.arranger.selected.is_clip(),
|
||||||
chain: self.mixer.track()
|
&Split::right()
|
||||||
})
|
.add(tek_mixer::TrackView {
|
||||||
.add_ref(&self.arranger.sequencer())
|
direction: Direction::Down,
|
||||||
))
|
entered: self.entered,
|
||||||
.render(buf, area)?;
|
focused: self.section == AppFocus::Chain,
|
||||||
if let Some(ref modal) = *MODAL.lock().unwrap() {
|
chain: self.mixer.track(),
|
||||||
modal.render(buf, area)?;
|
})
|
||||||
|
.add_ref(&self.arranger.sequencer()),
|
||||||
|
))
|
||||||
|
.render(buf, area)?;
|
||||||
|
if let Some(ref modal) = *MODAL.lock().unwrap() {
|
||||||
|
modal.render(buf, area)?;
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
}
|
}
|
||||||
Ok(area)
|
);
|
||||||
});
|
|
||||||
|
|
||||||
process!(App |self, _client, scope| {
|
process!(
|
||||||
let (
|
App | self,
|
||||||
reset, current_frames, chunk_size, current_usecs, next_usecs, period_usecs
|
_client,
|
||||||
) = self.transport.update(&scope);
|
scope | {
|
||||||
self.chunk_size = chunk_size;
|
let (reset, current_frames, chunk_size, current_usecs, next_usecs, period_usecs) =
|
||||||
for track in self.arranger.tracks.iter_mut() {
|
self.transport.update(&scope);
|
||||||
track.process(
|
self.chunk_size = chunk_size;
|
||||||
self.midi_in.as_ref().map(|p|p.iter(&scope)),
|
for track in self.arranger.tracks.iter_mut() {
|
||||||
&self.transport.timebase,
|
track.process(
|
||||||
self.transport.playing,
|
self.midi_in.as_ref().map(|p| p.iter(&scope)),
|
||||||
self.transport.started,
|
&self.transport.timebase,
|
||||||
self.transport.quant as usize,
|
self.transport.playing,
|
||||||
reset,
|
self.transport.started,
|
||||||
&scope,
|
self.transport.quant as usize,
|
||||||
(current_frames as usize, self.chunk_size),
|
reset,
|
||||||
(current_usecs as usize, next_usecs.saturating_sub(current_usecs) as usize),
|
&scope,
|
||||||
period_usecs as f64
|
(current_frames as usize, self.chunk_size),
|
||||||
);
|
(
|
||||||
|
current_usecs as usize,
|
||||||
|
next_usecs.saturating_sub(current_usecs) as usize,
|
||||||
|
),
|
||||||
|
period_usecs as f64,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Control::Continue
|
||||||
}
|
}
|
||||||
Control::Continue
|
);
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ better-panic = "0.3.0"
|
||||||
clap = { version = "4.5.4", features = [ "derive" ] }
|
clap = { version = "4.5.4", features = [ "derive" ] }
|
||||||
clojure-reader = "0.1.0"
|
clojure-reader = "0.1.0"
|
||||||
crossterm = "0.27"
|
crossterm = "0.27"
|
||||||
jack = "0.10"
|
jack = "0.13"
|
||||||
midly = "0.5"
|
midly = "0.5"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
|
ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
use jack::*;
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
use jack::*;
|
||||||
|
|
||||||
/// Trait for things that have a JACK process callback.
|
/// Trait for things that have a JACK process callback.
|
||||||
pub trait Audio {
|
pub trait Audio {
|
||||||
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
|
fn process(&mut self, _: &Client, _: &ProcessScope) -> Control {
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for things that may expose JACK ports.
|
/// Trait for things that may expose JACK ports.
|
||||||
pub trait Ports {
|
pub trait Ports {
|
||||||
fn audio_ins (&self) -> Usually<Vec<&Port<Unowned>>> {
|
fn audio_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
fn audio_outs (&self) -> Usually<Vec<&Port<Unowned>>> {
|
fn audio_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
fn midi_ins (&self) -> Usually<Vec<&Port<Unowned>>> {
|
fn midi_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
fn midi_outs (&self) -> Usually<Vec<&Port<Unowned>>> {
|
fn midi_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -27,7 +27,10 @@ pub trait Ports {
|
||||||
/// A UI component that may be associated with a JACK client by the `Jack` factory.
|
/// A UI component that may be associated with a JACK client by the `Jack` factory.
|
||||||
pub trait AudioComponent<E: Engine>: Component<E> + Audio {
|
pub trait AudioComponent<E: Engine>: Component<E> + Audio {
|
||||||
/// Perform type erasure for collecting heterogeneous devices.
|
/// Perform type erasure for collecting heterogeneous devices.
|
||||||
fn boxed (self) -> Box<dyn AudioComponent<E>> where Self: Sized + 'static {
|
fn boxed(self) -> Box<dyn AudioComponent<E>>
|
||||||
|
where
|
||||||
|
Self: Sized + 'static,
|
||||||
|
{
|
||||||
Box::new(self)
|
Box::new(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -42,177 +45,185 @@ pub enum JackClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JackClient {
|
impl JackClient {
|
||||||
pub fn client (&self) -> &Client {
|
pub fn client(&self) -> &Client {
|
||||||
match self {
|
match self {
|
||||||
Self::Inactive(ref client) =>
|
Self::Inactive(ref client) => client,
|
||||||
client,
|
Self::Active(ref client) => client.as_client(),
|
||||||
Self::Active(ref client) =>
|
|
||||||
client.as_client(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn transport (&self) -> Transport {
|
pub fn transport(&self) -> Transport {
|
||||||
self.client().transport()
|
self.client().transport()
|
||||||
}
|
}
|
||||||
pub fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
|
pub fn port_by_name(&self, name: &str) -> Option<Port<Unowned>> {
|
||||||
self.client().port_by_name(name)
|
self.client().port_by_name(name)
|
||||||
}
|
}
|
||||||
pub fn register_port <PS: PortSpec> (&self, name: &str, spec: PS) -> Usually<Port<PS>> {
|
pub fn register_port<PS: PortSpec>(&self, name: &str, spec: PS) -> Usually<Port<PS>> {
|
||||||
Ok(self.client().register_port(name, spec)?)
|
Ok(self.client().register_port(name, spec)?)
|
||||||
}
|
}
|
||||||
pub fn activate <T: Send + Sync + 'static> (
|
pub fn activate<T: Send + Sync + 'static>(
|
||||||
self,
|
self,
|
||||||
state: &Arc<RwLock<T>>,
|
state: &Arc<RwLock<T>>,
|
||||||
mut process: impl FnMut(&Arc<RwLock<T>>, &Client, &ProcessScope)->Control + Send + 'static
|
mut process: impl FnMut(&Arc<RwLock<T>>, &Client, &ProcessScope) -> Control + Send + 'static,
|
||||||
) -> Usually<Self> {
|
) -> Usually<Self> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Self::Active(_) => self,
|
Self::Active(_) => self,
|
||||||
Self::Inactive(client) => Self::Active(client.activate_async(
|
Self::Inactive(client) => Self::Active(client.activate_async(
|
||||||
Notifications(Box::new(move|_|{/*TODO*/})
|
Notifications(
|
||||||
as Box<dyn Fn(JackEvent) + Send + Sync>),
|
Box::new(move |_| { /*TODO*/ }) as Box<dyn Fn(JackEvent) + Send + Sync>
|
||||||
ClosureProcessHandler::new(Box::new({
|
),
|
||||||
|
contrib::ClosureProcessHandler::new(Box::new({
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
move|c: &Client, s: &ProcessScope|process(&state, c, s)
|
move |c: &Client, s: &ProcessScope| process(&state, c, s)
|
||||||
}) as BoxedAudioHandler)
|
}) as BoxedAudioHandler),
|
||||||
)?)
|
)?),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type DynamicAsyncClient =
|
pub type DynamicAsyncClient = AsyncClient<DynamicNotifications, DynamicAudioHandler>;
|
||||||
AsyncClient<DynamicNotifications, DynamicAudioHandler>;
|
|
||||||
|
|
||||||
type DynamicAudioHandler =
|
type DynamicAudioHandler = contrib::ClosureProcessHandler<(), BoxedAudioHandler>;
|
||||||
ClosureProcessHandler<BoxedAudioHandler>;
|
|
||||||
|
|
||||||
pub type BoxedAudioHandler =
|
pub type BoxedAudioHandler = Box<dyn FnMut(&Client, &ProcessScope) -> Control + Send>;
|
||||||
Box<dyn FnMut(&Client, &ProcessScope)-> Control + Send>;
|
|
||||||
|
|
||||||
/// Just run thing with JACK. Returns the activated client.
|
/// Just run thing with JACK. Returns the activated client.
|
||||||
pub fn jack_run <T, E: Engine> (name: &str, app: &Arc<RwLock<T>>) -> Usually<DynamicAsyncClient>
|
pub fn jack_run<T, E: Engine>(name: &str, app: &Arc<RwLock<T>>) -> Usually<DynamicAsyncClient>
|
||||||
where T: Handle<E> + Audio + Send + Sync + 'static
|
where
|
||||||
|
T: Handle<E> + Audio + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let options = ClientOptions::NO_START_SERVER;
|
let options = ClientOptions::NO_START_SERVER;
|
||||||
let (client, _status) = Client::new(name, options)?;
|
let (client, _status) = Client::new(name, options)?;
|
||||||
Ok(client.activate_async(
|
Ok(client.activate_async(
|
||||||
Notifications(Box::new({
|
Notifications(Box::new({
|
||||||
let _app = app.clone();
|
let _app = app.clone();
|
||||||
move|_event|{
|
move |_event| {
|
||||||
// FIXME: this deadlocks
|
// FIXME: this deadlocks
|
||||||
//app.lock().unwrap().handle(&event).unwrap();
|
//app.lock().unwrap().handle(&event).unwrap();
|
||||||
}
|
}
|
||||||
}) as Box<dyn Fn(JackEvent) + Send + Sync>),
|
}) as Box<dyn Fn(JackEvent) + Send + Sync>),
|
||||||
ClosureProcessHandler::new(Box::new({
|
contrib::ClosureProcessHandler::new(Box::new({
|
||||||
let app = app.clone();
|
let app = app.clone();
|
||||||
move|c: &Client, s: &ProcessScope|{
|
move |c: &Client, s: &ProcessScope| {
|
||||||
app.write().unwrap().process(c, s)
|
app.write().unwrap().process(c, s)
|
||||||
//Control::Continue
|
//Control::Continue
|
||||||
}
|
}
|
||||||
}) as BoxedAudioHandler)
|
}) as BoxedAudioHandler),
|
||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `JackDevice` factory. Creates JACK `Client`s, performs port registration
|
/// `JackDevice` factory. Creates JACK `Client`s, performs port registration
|
||||||
/// and activation, and encapsulates a `AudioComponent` into a `JackDevice`.
|
/// and activation, and encapsulates a `AudioComponent` into a `JackDevice`.
|
||||||
pub struct Jack {
|
pub struct Jack {
|
||||||
pub client: Client,
|
pub client: Client,
|
||||||
pub midi_ins: Vec<String>,
|
pub midi_ins: Vec<String>,
|
||||||
pub audio_ins: Vec<String>,
|
pub audio_ins: Vec<String>,
|
||||||
pub midi_outs: Vec<String>,
|
pub midi_outs: Vec<String>,
|
||||||
pub audio_outs: Vec<String>,
|
pub audio_outs: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Jack {
|
impl Jack {
|
||||||
pub fn new (name: &str) -> Usually<Self> {
|
pub fn new(name: &str) -> Usually<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
midi_ins: vec![],
|
midi_ins: vec![],
|
||||||
audio_ins: vec![],
|
audio_ins: vec![],
|
||||||
midi_outs: vec![],
|
midi_outs: vec![],
|
||||||
audio_outs: vec![],
|
audio_outs: vec![],
|
||||||
client: Client::new(
|
client: Client::new(name, ClientOptions::NO_START_SERVER)?.0,
|
||||||
name,
|
|
||||||
ClientOptions::NO_START_SERVER
|
|
||||||
)?.0,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn run <'a: 'static, D, E> (
|
pub fn run<'a: 'static, D, E>(
|
||||||
self, state: impl FnOnce(JackPorts)->Box<D>
|
self,
|
||||||
|
state: impl FnOnce(JackPorts) -> Box<D>,
|
||||||
) -> Usually<JackDevice<E>>
|
) -> Usually<JackDevice<E>>
|
||||||
where D: AudioComponent<E> + Sized + 'static,
|
where
|
||||||
E: Engine + 'static,
|
D: AudioComponent<E> + Sized + 'static,
|
||||||
|
E: Engine + 'static,
|
||||||
{
|
{
|
||||||
let owned_ports = JackPorts {
|
let owned_ports = JackPorts {
|
||||||
audio_ins: register_ports(&self.client, self.audio_ins, AudioIn)?,
|
audio_ins: register_ports(&self.client, self.audio_ins, AudioIn::default())?,
|
||||||
audio_outs: register_ports(&self.client, self.audio_outs, AudioOut)?,
|
audio_outs: register_ports(&self.client, self.audio_outs, AudioOut::default())?,
|
||||||
midi_ins: register_ports(&self.client, self.midi_ins, MidiIn)?,
|
midi_ins: register_ports(&self.client, self.midi_ins, MidiIn::default())?,
|
||||||
midi_outs: register_ports(&self.client, self.midi_outs, MidiOut)?,
|
midi_outs: register_ports(&self.client, self.midi_outs, MidiOut::default())?,
|
||||||
};
|
};
|
||||||
let midi_outs = owned_ports.midi_outs.values()
|
let midi_outs = owned_ports
|
||||||
.map(|p|Ok(p.name()?)).collect::<Usually<Vec<_>>>()?;
|
.midi_outs
|
||||||
let midi_ins = owned_ports.midi_ins.values()
|
.values()
|
||||||
.map(|p|Ok(p.name()?)).collect::<Usually<Vec<_>>>()?;
|
.map(|p| Ok(p.name()?))
|
||||||
let audio_outs = owned_ports.audio_outs.values()
|
.collect::<Usually<Vec<_>>>()?;
|
||||||
.map(|p|Ok(p.name()?)).collect::<Usually<Vec<_>>>()?;
|
let midi_ins = owned_ports
|
||||||
let audio_ins = owned_ports.audio_ins.values()
|
.midi_ins
|
||||||
.map(|p|Ok(p.name()?)).collect::<Usually<Vec<_>>>()?;
|
.values()
|
||||||
|
.map(|p| Ok(p.name()?))
|
||||||
|
.collect::<Usually<Vec<_>>>()?;
|
||||||
|
let audio_outs = owned_ports
|
||||||
|
.audio_outs
|
||||||
|
.values()
|
||||||
|
.map(|p| Ok(p.name()?))
|
||||||
|
.collect::<Usually<Vec<_>>>()?;
|
||||||
|
let audio_ins = owned_ports
|
||||||
|
.audio_ins
|
||||||
|
.values()
|
||||||
|
.map(|p| Ok(p.name()?))
|
||||||
|
.collect::<Usually<Vec<_>>>()?;
|
||||||
let state = Arc::new(RwLock::new(state(owned_ports) as Box<dyn AudioComponent<E>>));
|
let state = Arc::new(RwLock::new(state(owned_ports) as Box<dyn AudioComponent<E>>));
|
||||||
let client = self.client.activate_async(
|
let client = self.client.activate_async(
|
||||||
Notifications(Box::new({
|
Notifications(Box::new({
|
||||||
let _state = state.clone();
|
let _state = state.clone();
|
||||||
move|_event|{
|
move |_event| {
|
||||||
// FIXME: this deadlocks
|
// FIXME: this deadlocks
|
||||||
//state.lock().unwrap().handle(&event).unwrap();
|
//state.lock().unwrap().handle(&event).unwrap();
|
||||||
}
|
}
|
||||||
}) as Box<dyn Fn(JackEvent) + Send + Sync>),
|
}) as Box<dyn Fn(JackEvent) + Send + Sync>),
|
||||||
ClosureProcessHandler::new(Box::new({
|
contrib::ClosureProcessHandler::new(Box::new({
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
move|c: &Client, s: &ProcessScope|{
|
move |c: &Client, s: &ProcessScope| state.write().unwrap().process(c, s)
|
||||||
state.write().unwrap().process(c, s)
|
}) as BoxedAudioHandler),
|
||||||
}
|
|
||||||
}) as BoxedAudioHandler)
|
|
||||||
)?;
|
)?;
|
||||||
Ok(JackDevice {
|
Ok(JackDevice {
|
||||||
ports: UnownedJackPorts {
|
ports: UnownedJackPorts {
|
||||||
audio_ins: query_ports(&client.as_client(), audio_ins),
|
audio_ins: query_ports(&client.as_client(), audio_ins),
|
||||||
audio_outs: query_ports(&client.as_client(), audio_outs),
|
audio_outs: query_ports(&client.as_client(), audio_outs),
|
||||||
midi_ins: query_ports(&client.as_client(), midi_ins),
|
midi_ins: query_ports(&client.as_client(), midi_ins),
|
||||||
midi_outs: query_ports(&client.as_client(), midi_outs),
|
midi_outs: query_ports(&client.as_client(), midi_outs),
|
||||||
},
|
},
|
||||||
client,
|
client,
|
||||||
state,
|
state,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn audio_in (mut self, name: &str) -> Self {
|
pub fn audio_in(mut self, name: &str) -> Self {
|
||||||
self.audio_ins.push(name.to_string());
|
self.audio_ins.push(name.to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn audio_out (mut self, name: &str) -> Self {
|
pub fn audio_out(mut self, name: &str) -> Self {
|
||||||
self.audio_outs.push(name.to_string());
|
self.audio_outs.push(name.to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn midi_in (mut self, name: &str) -> Self {
|
pub fn midi_in(mut self, name: &str) -> Self {
|
||||||
self.midi_ins.push(name.to_string());
|
self.midi_ins.push(name.to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn midi_out (mut self, name: &str) -> Self {
|
pub fn midi_out(mut self, name: &str) -> Self {
|
||||||
self.midi_outs.push(name.to_string());
|
self.midi_outs.push(name.to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_ports <T: PortSpec + Copy> (
|
fn register_ports<T: PortSpec + Copy>(
|
||||||
client: &Client, names: Vec<String>, spec: T
|
client: &Client,
|
||||||
|
names: Vec<String>,
|
||||||
|
spec: T,
|
||||||
) -> Usually<BTreeMap<String, Port<T>>> {
|
) -> Usually<BTreeMap<String, Port<T>>> {
|
||||||
names.into_iter().try_fold(BTreeMap::new(), |mut ports, name|{
|
names
|
||||||
let port = client.register_port(&name, spec)?;
|
.into_iter()
|
||||||
ports.insert(name, port);
|
.try_fold(BTreeMap::new(), |mut ports, name| {
|
||||||
Ok(ports)
|
let port = client.register_port(&name, spec)?;
|
||||||
})
|
ports.insert(name, port);
|
||||||
|
Ok(ports)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn query_ports (
|
fn query_ports(client: &Client, names: Vec<String>) -> BTreeMap<String, Port<Unowned>> {
|
||||||
client: &Client, names: Vec<String>
|
names.into_iter().fold(BTreeMap::new(), |mut ports, name| {
|
||||||
) -> BTreeMap<String, Port<Unowned>> {
|
|
||||||
names.into_iter().fold(BTreeMap::new(), |mut ports, name|{
|
|
||||||
let port = client.port_by_name(&name).unwrap();
|
let port = client.port_by_name(&name).unwrap();
|
||||||
ports.insert(name, port);
|
ports.insert(name, port);
|
||||||
ports
|
ports
|
||||||
|
|
@ -221,8 +232,7 @@ fn query_ports (
|
||||||
|
|
||||||
/// Notification handler used by the [Jack] factory
|
/// Notification handler used by the [Jack] factory
|
||||||
/// when constructing [JackDevice]s.
|
/// when constructing [JackDevice]s.
|
||||||
pub type DynamicNotifications =
|
pub type DynamicNotifications = Notifications<Box<dyn Fn(JackEvent) + Send + Sync>>;
|
||||||
Notifications<Box<dyn Fn(JackEvent) + Send + Sync>>;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
/// Event enum for JACK events.
|
/// Event enum for JACK events.
|
||||||
|
|
@ -243,46 +253,46 @@ pub enum JackEvent {
|
||||||
pub struct Notifications<T: Fn(JackEvent) + Send>(pub T);
|
pub struct Notifications<T: Fn(JackEvent) + Send>(pub T);
|
||||||
|
|
||||||
impl<T: Fn(JackEvent) + Send> NotificationHandler for Notifications<T> {
|
impl<T: Fn(JackEvent) + Send> NotificationHandler for Notifications<T> {
|
||||||
fn thread_init (&self, _: &Client) {
|
fn thread_init(&self, _: &Client) {
|
||||||
self.0(JackEvent::ThreadInit);
|
self.0(JackEvent::ThreadInit);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shutdown (&mut self, status: ClientStatus, reason: &str) {
|
unsafe fn shutdown(&mut self, status: ClientStatus, reason: &str) {
|
||||||
self.0(JackEvent::Shutdown(status, reason.into()));
|
self.0(JackEvent::Shutdown(status, reason.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn freewheel (&mut self, _: &Client, enabled: bool) {
|
fn freewheel(&mut self, _: &Client, enabled: bool) {
|
||||||
self.0(JackEvent::Freewheel(enabled));
|
self.0(JackEvent::Freewheel(enabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sample_rate (&mut self, _: &Client, frames: Frames) -> Control {
|
fn sample_rate(&mut self, _: &Client, frames: Frames) -> Control {
|
||||||
self.0(JackEvent::SampleRate(frames));
|
self.0(JackEvent::SampleRate(frames));
|
||||||
Control::Quit
|
Control::Quit
|
||||||
}
|
}
|
||||||
|
|
||||||
fn client_registration (&mut self, _: &Client, name: &str, reg: bool) {
|
fn client_registration(&mut self, _: &Client, name: &str, reg: bool) {
|
||||||
self.0(JackEvent::ClientRegistration(name.into(), reg));
|
self.0(JackEvent::ClientRegistration(name.into(), reg));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn port_registration (&mut self, _: &Client, id: PortId, reg: bool) {
|
fn port_registration(&mut self, _: &Client, id: PortId, reg: bool) {
|
||||||
self.0(JackEvent::PortRegistration(id, reg));
|
self.0(JackEvent::PortRegistration(id, reg));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
|
fn port_rename(&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
|
||||||
self.0(JackEvent::PortRename(id, old.into(), new.into()));
|
self.0(JackEvent::PortRename(id, old.into(), new.into()));
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ports_connected (&mut self, _: &Client, a: PortId, b: PortId, are: bool) {
|
fn ports_connected(&mut self, _: &Client, a: PortId, b: PortId, are: bool) {
|
||||||
self.0(JackEvent::PortsConnected(a, b, are));
|
self.0(JackEvent::PortsConnected(a, b, are));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn graph_reorder (&mut self, _: &Client) -> Control {
|
fn graph_reorder(&mut self, _: &Client) -> Control {
|
||||||
self.0(JackEvent::GraphReorder);
|
self.0(JackEvent::GraphReorder);
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fn xrun (&mut self, _: &Client) -> Control {
|
fn xrun(&mut self, _: &Client) -> Control {
|
||||||
self.0(JackEvent::XRun);
|
self.0(JackEvent::XRun);
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
|
|
@ -293,92 +303,106 @@ pub struct JackDevice<E: Engine> {
|
||||||
/// The active JACK client of this device.
|
/// The active JACK client of this device.
|
||||||
pub client: DynamicAsyncClient,
|
pub client: DynamicAsyncClient,
|
||||||
/// The device state, encapsulated for sharing between threads.
|
/// The device state, encapsulated for sharing between threads.
|
||||||
pub state: Arc<RwLock<Box<dyn AudioComponent<E>>>>,
|
pub state: Arc<RwLock<Box<dyn AudioComponent<E>>>>,
|
||||||
/// Unowned copies of the device's JACK ports, for connecting to the device.
|
/// Unowned copies of the device's JACK ports, for connecting to the device.
|
||||||
/// The "real" readable/writable `Port`s are owned by the `state`.
|
/// The "real" readable/writable `Port`s are owned by the `state`.
|
||||||
pub ports: UnownedJackPorts,
|
pub ports: UnownedJackPorts,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> std::fmt::Debug for JackDevice<E> {
|
impl<E: Engine> std::fmt::Debug for JackDevice<E> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("JackDevice").field("ports", &self.ports).finish()
|
f.debug_struct("JackDevice")
|
||||||
|
.field("ports", &self.ports)
|
||||||
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> Widget for JackDevice<E> {
|
impl<E: Engine> Widget for JackDevice<E> {
|
||||||
type Engine = E;
|
type Engine = E;
|
||||||
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
fn layout(&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
self.state.read().unwrap().layout(to)
|
self.state.read().unwrap().layout(to)
|
||||||
}
|
}
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
fn render(&self, to: &mut E::Output) -> Usually<()> {
|
||||||
self.state.read().unwrap().render(to)
|
self.state.read().unwrap().render(to)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> Handle<E> for JackDevice<E> {
|
impl<E: Engine> Handle<E> for JackDevice<E> {
|
||||||
fn handle (&mut self, from: &E::Input) -> Perhaps<E::Handled> {
|
fn handle(&mut self, from: &E::Input) -> Perhaps<E::Handled> {
|
||||||
self.state.write().unwrap().handle(from)
|
self.state.write().unwrap().handle(from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> Ports for JackDevice<E> {
|
impl<E: Engine> Ports for JackDevice<E> {
|
||||||
fn audio_ins (&self) -> Usually<Vec<&Port<Unowned>>> {
|
fn audio_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||||
Ok(self.ports.audio_ins.values().collect())
|
Ok(self.ports.audio_ins.values().collect())
|
||||||
}
|
}
|
||||||
fn audio_outs (&self) -> Usually<Vec<&Port<Unowned>>> {
|
fn audio_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||||
Ok(self.ports.audio_outs.values().collect())
|
Ok(self.ports.audio_outs.values().collect())
|
||||||
}
|
}
|
||||||
fn midi_ins (&self) -> Usually<Vec<&Port<Unowned>>> {
|
fn midi_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||||
Ok(self.ports.midi_ins.values().collect())
|
Ok(self.ports.midi_ins.values().collect())
|
||||||
}
|
}
|
||||||
fn midi_outs (&self) -> Usually<Vec<&Port<Unowned>>> {
|
fn midi_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||||
Ok(self.ports.midi_outs.values().collect())
|
Ok(self.ports.midi_outs.values().collect())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> JackDevice<E> {
|
impl<E: Engine> JackDevice<E> {
|
||||||
/// Returns a locked mutex of the state's contents.
|
/// Returns a locked mutex of the state's contents.
|
||||||
pub fn state (&self) -> LockResult<RwLockReadGuard<Box<dyn AudioComponent<E>>>> {
|
pub fn state(&self) -> LockResult<RwLockReadGuard<Box<dyn AudioComponent<E>>>> {
|
||||||
self.state.read()
|
self.state.read()
|
||||||
}
|
}
|
||||||
/// Returns a locked mutex of the state's contents.
|
/// Returns a locked mutex of the state's contents.
|
||||||
pub fn state_mut (&self) -> LockResult<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
|
pub fn state_mut(&self) -> LockResult<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
|
||||||
self.state.write()
|
self.state.write()
|
||||||
}
|
}
|
||||||
pub fn connect_midi_in (&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
pub fn connect_midi_in(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
||||||
Ok(self.client.as_client().connect_ports(port, self.midi_ins()?[index])?)
|
Ok(self
|
||||||
|
.client
|
||||||
|
.as_client()
|
||||||
|
.connect_ports(port, self.midi_ins()?[index])?)
|
||||||
}
|
}
|
||||||
pub fn connect_midi_out (&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
pub fn connect_midi_out(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
||||||
Ok(self.client.as_client().connect_ports(self.midi_outs()?[index], port)?)
|
Ok(self
|
||||||
|
.client
|
||||||
|
.as_client()
|
||||||
|
.connect_ports(self.midi_outs()?[index], port)?)
|
||||||
}
|
}
|
||||||
pub fn connect_audio_in (&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
pub fn connect_audio_in(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
||||||
Ok(self.client.as_client().connect_ports(port, self.audio_ins()?[index])?)
|
Ok(self
|
||||||
|
.client
|
||||||
|
.as_client()
|
||||||
|
.connect_ports(port, self.audio_ins()?[index])?)
|
||||||
}
|
}
|
||||||
pub fn connect_audio_out (&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
pub fn connect_audio_out(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
||||||
Ok(self.client.as_client().connect_ports(self.audio_outs()?[index], port)?)
|
Ok(self
|
||||||
|
.client
|
||||||
|
.as_client()
|
||||||
|
.connect_ports(self.audio_outs()?[index], port)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collection of JACK ports as [AudioIn]/[AudioOut]/[MidiIn]/[MidiOut].
|
/// Collection of JACK ports as [AudioIn]/[AudioOut]/[MidiIn]/[MidiOut].
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct JackPorts {
|
pub struct JackPorts {
|
||||||
pub audio_ins: BTreeMap<String, Port<AudioIn>>,
|
pub audio_ins: BTreeMap<String, Port<AudioIn>>,
|
||||||
pub midi_ins: BTreeMap<String, Port<MidiIn>>,
|
pub midi_ins: BTreeMap<String, Port<MidiIn>>,
|
||||||
pub audio_outs: BTreeMap<String, Port<AudioOut>>,
|
pub audio_outs: BTreeMap<String, Port<AudioOut>>,
|
||||||
pub midi_outs: BTreeMap<String, Port<MidiOut>>,
|
pub midi_outs: BTreeMap<String, Port<MidiOut>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collection of JACK ports as [Unowned].
|
/// Collection of JACK ports as [Unowned].
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct UnownedJackPorts {
|
pub struct UnownedJackPorts {
|
||||||
pub audio_ins: BTreeMap<String, Port<Unowned>>,
|
pub audio_ins: BTreeMap<String, Port<Unowned>>,
|
||||||
pub midi_ins: BTreeMap<String, Port<Unowned>>,
|
pub midi_ins: BTreeMap<String, Port<Unowned>>,
|
||||||
pub audio_outs: BTreeMap<String, Port<Unowned>>,
|
pub audio_outs: BTreeMap<String, Port<Unowned>>,
|
||||||
pub midi_outs: BTreeMap<String, Port<Unowned>>,
|
pub midi_outs: BTreeMap<String, Port<Unowned>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JackPorts {
|
impl JackPorts {
|
||||||
pub fn clone_unowned (&self) -> UnownedJackPorts {
|
pub fn clone_unowned(&self) -> UnownedJackPorts {
|
||||||
let mut unowned = UnownedJackPorts::default();
|
let mut unowned = UnownedJackPorts::default();
|
||||||
for (name, port) in self.midi_ins.iter() {
|
for (name, port) in self.midi_ins.iter() {
|
||||||
unowned.midi_ins.insert(name.clone(), port.clone_unowned());
|
unowned.midi_ins.insert(name.clone(), port.clone_unowned());
|
||||||
|
|
@ -390,14 +414,17 @@ impl JackPorts {
|
||||||
unowned.audio_ins.insert(name.clone(), port.clone_unowned());
|
unowned.audio_ins.insert(name.clone(), port.clone_unowned());
|
||||||
}
|
}
|
||||||
for (name, port) in self.audio_outs.iter() {
|
for (name, port) in self.audio_outs.iter() {
|
||||||
unowned.audio_outs.insert(name.clone(), port.clone_unowned());
|
unowned
|
||||||
|
.audio_outs
|
||||||
|
.insert(name.clone(), port.clone_unowned());
|
||||||
}
|
}
|
||||||
unowned
|
unowned
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implement the `Ports` trait.
|
/// Implement the `Ports` trait.
|
||||||
#[macro_export] macro_rules! ports {
|
#[macro_export]
|
||||||
|
macro_rules! ports {
|
||||||
($T:ty $({ $(audio: {
|
($T:ty $({ $(audio: {
|
||||||
$(ins: |$ai_arg:ident|$ai_impl:expr,)?
|
$(ins: |$ai_arg:ident|$ai_impl:expr,)?
|
||||||
$(outs: |$ao_arg:ident|$ao_impl:expr,)?
|
$(outs: |$ao_arg:ident|$ao_impl:expr,)?
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue