1
0
Fork 0

Support auxiliary IO for JACK standalones

This commit is contained in:
Robbert van der Helm 2023-02-23 22:58:11 +01:00
parent 72c43adda6
commit d7e766ca00

View file

@ -12,7 +12,7 @@ use parking_lot::Mutex;
use super::super::config::WrapperConfig; use super::super::config::WrapperConfig;
use super::Backend; use super::Backend;
use crate::audio_setup::AuxiliaryBuffers; use crate::audio_setup::{AudioIOLayout, AuxiliaryBuffers};
use crate::buffer::Buffer; use crate::buffer::Buffer;
use crate::context::process::Transport; use crate::context::process::Transport;
use crate::midi::{MidiConfig, MidiResult, NoteEvent, PluginNoteEvent}; use crate::midi::{MidiConfig, MidiResult, NoteEvent, PluginNoteEvent};
@ -20,17 +20,17 @@ use crate::plugin::Plugin;
use crate::wrapper::util::{clamp_input_event_timing, clamp_output_event_timing}; use crate::wrapper::util::{clamp_input_event_timing, clamp_output_event_timing};
/// Uses JACK audio and MIDI. /// Uses JACK audio and MIDI.
//
// TODO: Use the bus names
// TODO: Support auxiliary inputs and outputs
pub struct Jack { pub struct Jack {
audio_io_layout: AudioIOLayout,
config: WrapperConfig, config: WrapperConfig,
/// The JACK client, wrapped in an option since it needs to be transformed into an `AsyncClient` /// The JACK client, wrapped in an option since it needs to be transformed into an `AsyncClient`
/// and then back into a regular `Client`. /// and then back into a regular `Client`.
client: Option<Client>, client: Option<Client>,
inputs: Arc<Vec<Port<AudioIn>>>, main_inputs: Arc<Vec<Port<AudioIn>>>,
outputs: Arc<Mutex<Vec<Port<AudioOut>>>>, main_outputs: Arc<Mutex<Vec<Port<AudioOut>>>>,
aux_input_ports: Arc<Mutex<Vec<Vec<Port<AudioIn>>>>>,
aux_output_ports: Arc<Mutex<Vec<Vec<Port<AudioOut>>>>>,
midi_input: Option<Arc<Port<MidiIn>>>, midi_input: Option<Arc<Port<MidiIn>>>,
midi_output: Option<Arc<Mutex<Port<MidiOut>>>>, midi_output: Option<Arc<Mutex<Port<MidiOut>>>>,
} }
@ -51,13 +51,46 @@ impl<P: Plugin> Backend<P> for Jack {
let client = self.client.take().unwrap(); let client = self.client.take().unwrap();
let buffer_size = client.buffer_size(); let buffer_size = client.buffer_size();
// We'll preallocate the buffers here, and then assign them to the slices belonging to the
// JACK ports later
let mut buffer = Buffer::default(); let mut buffer = Buffer::default();
unsafe { unsafe {
buffer.set_slices(0, |output_slices| { buffer.set_slices(0, |output_slices| {
output_slices.resize_with(self.outputs.lock().len(), || &mut []); output_slices.resize_with(self.main_outputs.lock().len(), || &mut []);
}) })
} }
// For the inputs we'll need to allocate storage because the NIH-plug API expects all
// buffers to be mutable, and the jack crate doesn't give us mutable slices on audio input
// ports
let mut aux_input_storage: Vec<Vec<Vec<f32>>> = Vec::new();
let mut aux_input_buffers: Vec<Buffer> = Vec::new();
for channel_count in self.audio_io_layout.aux_input_ports {
aux_input_storage.push(vec![
vec![0.0f32; self.config.period_size as usize];
channel_count.get() as usize
]);
let mut aux_buffer = Buffer::default();
unsafe {
aux_buffer.set_slices(0, |output_slices| {
output_slices.resize_with(channel_count.get() as usize, || &mut []);
})
}
aux_input_buffers.push(aux_buffer);
}
let mut aux_output_buffers: Vec<Buffer> = Vec::new();
for channel_count in self.audio_io_layout.aux_output_ports {
let mut aux_buffer = Buffer::default();
unsafe {
aux_buffer.set_slices(0, |output_slices| {
output_slices.resize_with(channel_count.get() as usize, || &mut []);
})
}
aux_output_buffers.push(aux_buffer);
}
let mut input_events: Vec<PluginNoteEvent<P>> = Vec::with_capacity(2048); let mut input_events: Vec<PluginNoteEvent<P>> = Vec::with_capacity(2048);
let mut output_events: Vec<PluginNoteEvent<P>> = Vec::with_capacity(2048); let mut output_events: Vec<PluginNoteEvent<P>> = Vec::with_capacity(2048);
@ -66,8 +99,10 @@ impl<P: Plugin> Backend<P> for Jack {
let unparker = parker.unparker().clone(); let unparker = parker.unparker().clone();
let config = self.config.clone(); let config = self.config.clone();
let inputs = self.inputs.clone(); let main_inputs = self.main_inputs.clone();
let outputs = self.outputs.clone(); let main_outputs = self.main_outputs.clone();
let aux_input_ports = self.aux_input_ports.clone();
let aux_output_ports = self.aux_output_ports.clone();
let midi_input = self.midi_input.clone(); let midi_input = self.midi_input.clone();
let midi_output = self.midi_output.clone(); let midi_output = self.midi_output.clone();
let process_handler = ClosureProcessHandler::new(move |client, ps| { let process_handler = ClosureProcessHandler::new(move |client, ps| {
@ -108,8 +143,8 @@ impl<P: Plugin> Backend<P> for Jack {
// Just like all of the plugin backends, we need to grab the output slices and copy the // Just like all of the plugin backends, we need to grab the output slices and copy the
// inputs to the outputs // inputs to the outputs
let mut outputs = outputs.lock(); let mut main_outputs = main_outputs.lock();
for (input, output) in inputs.iter().zip(outputs.iter_mut()) { for (input, output) in main_inputs.iter().zip(main_outputs.iter_mut()) {
// XXX: Since the JACK bindings let us do this, presumably these two can't alias, // XXX: Since the JACK bindings let us do this, presumably these two can't alias,
// right? // right?
output.as_mut_slice(ps).copy_from_slice(input.as_slice(ps)); output.as_mut_slice(ps).copy_from_slice(input.as_slice(ps));
@ -118,12 +153,58 @@ impl<P: Plugin> Backend<P> for Jack {
// And the buffer's slices need to point to the JACK output ports // And the buffer's slices need to point to the JACK output ports
unsafe { unsafe {
buffer.set_slices(num_frames as usize, |output_slices| { buffer.set_slices(num_frames as usize, |output_slices| {
for (output_slice, output) in output_slices.iter_mut().zip(outputs.iter_mut()) { for (output_slice, output) in
output_slices.iter_mut().zip(main_outputs.iter_mut())
{
// SAFETY: This buffer is only read from after in this callback, and the // SAFETY: This buffer is only read from after in this callback, and the
// reference passed to `cb` cannot outlive that function call // reference passed to `cb` cannot outlive that function call
*output_slice = &mut *(output.as_mut_slice(ps) as *mut _); *output_slice = &mut *(output.as_mut_slice(ps) as *mut _);
} }
}) });
}
// For auxiliary input ports we first need to copy the port data to our input storage
// because the NIH-plug API expects every buffer to be mutable
let mut aux_input_ports = aux_input_ports.lock();
for (aux_inputs, (aux_storage, aux_buffer)) in aux_input_ports.iter_mut().zip(
aux_input_storage
.iter_mut()
.zip(aux_input_buffers.iter_mut()),
) {
for (aux_input, channel) in aux_inputs.iter_mut().zip(aux_storage.iter_mut()) {
channel.copy_from_slice(aux_input.as_slice(ps));
}
unsafe {
aux_buffer.set_slices(num_frames as usize, |input_slices| {
for (input_slice, channel) in
input_slices.iter_mut().zip(aux_storage.iter_mut())
{
// SAFETY: This buffer is only read from after in this callback, and the
// reference passed to `cb` cannot outlive that function call
*input_slice = &mut *(channel.as_mut_slice() as *mut _);
}
});
}
}
// We can point the buffers for the auxiliary output pots directly at the ports
let mut aux_output_ports = aux_output_ports.lock();
for (aux_outputs, aux_buffer) in aux_output_ports
.iter_mut()
.zip(aux_output_buffers.iter_mut())
{
unsafe {
aux_buffer.set_slices(num_frames as usize, |output_slices| {
for (output_slice, channel) in
output_slices.iter_mut().zip(aux_outputs.iter_mut())
{
// SAFETY: This buffer is only read from after in this callback, and the
// reference passed to `cb` cannot outlive that function call
*output_slice = &mut *(channel.as_mut_slice(ps) as *mut _);
}
});
}
} }
input_events.clear(); input_events.clear();
@ -135,14 +216,20 @@ impl<P: Plugin> Backend<P> for Jack {
})); }));
} }
// SAFETY: Shortening these borrows is safe as even if the plugin overwrites the
// slices (which it cannot do without using unsafe code), then they
// would still be reset on the next iteration
let mut aux = unsafe {
AuxiliaryBuffers {
inputs: &mut *(aux_input_buffers.as_mut_slice() as *mut [Buffer]),
outputs: &mut *(aux_output_buffers.as_mut_slice() as *mut [Buffer]),
}
};
output_events.clear(); output_events.clear();
if cb( if cb(
&mut buffer, &mut buffer,
// TODO: Support auxiliary IO in the JACK backend &mut aux,
&mut AuxiliaryBuffers {
inputs: &mut [],
outputs: &mut [],
},
transport, transport,
&input_events, &input_events,
&mut output_events, &mut output_events,
@ -223,7 +310,7 @@ impl Jack {
) )
} }
let mut inputs = Vec::new(); let mut main_inputs = Vec::new();
let num_input_channels = audio_io_layout let num_input_channels = audio_io_layout
.main_input_channels .main_input_channels
.map(NonZeroU32::get) .map(NonZeroU32::get)
@ -233,13 +320,14 @@ impl Jack {
.to_lowercase() .to_lowercase()
.replace(' ', "_"); .replace(' ', "_");
for port_no in 1..num_input_channels + 1 { for port_no in 1..num_input_channels + 1 {
inputs.push(client.register_port(&format!("{main_input_name}_{port_no}"), AudioIn)?); main_inputs
.push(client.register_port(&format!("{main_input_name}_{port_no}"), AudioIn)?);
} }
// We can't immediately connect the outputs. Or well we can with PipeWire, but JACK2 says // We can't immediately connect the outputs. Or well we can with PipeWire, but JACK2 says
// no. So the connections are made just after activating the client in the `run()` function // no. So the connections are made just after activating the client in the `run()` function
// above. // above.
let mut outputs = Vec::new(); let mut main_outputs = Vec::new();
let num_output_channels = audio_io_layout let num_output_channels = audio_io_layout
.main_output_channels .main_output_channels
.map(NonZeroU32::get) .map(NonZeroU32::get)
@ -249,7 +337,42 @@ impl Jack {
.to_lowercase() .to_lowercase()
.replace(' ', "_"); .replace(' ', "_");
for port_no in 1..num_output_channels + 1 { for port_no in 1..num_output_channels + 1 {
outputs.push(client.register_port(&format!("{main_output_name}_{port_no}"), AudioOut)?); main_outputs
.push(client.register_port(&format!("{main_output_name}_{port_no}"), AudioOut)?);
}
// The JACK backend also exposes ports for auxiliary inputs and outputs
let mut aux_input_ports = Vec::new();
for (aux_input_idx, channel_count) in audio_io_layout.aux_input_ports.iter().enumerate() {
let aux_input_name = audio_io_layout
.aux_input_name(aux_input_idx)
.expect("Out of range aux input port")
.to_lowercase()
.replace(' ', "_");
let mut ports = Vec::new();
for port_no in 1..channel_count.get() + 1 {
ports.push(client.register_port(&format!("{aux_input_name}_{port_no}"), AudioIn)?);
}
aux_input_ports.push(ports);
}
let mut aux_output_ports = Vec::new();
for (aux_output_idx, channel_count) in audio_io_layout.aux_output_ports.iter().enumerate() {
let aux_output_name = audio_io_layout
.aux_output_name(aux_output_idx)
.expect("Out of range aux output port")
.to_lowercase()
.replace(' ', "_");
let mut ports = Vec::new();
for port_no in 1..channel_count.get() + 1 {
ports
.push(client.register_port(&format!("{aux_output_name}_{port_no}"), AudioOut)?);
}
aux_output_ports.push(ports);
} }
let midi_input = if P::MIDI_INPUT >= MidiConfig::Basic { let midi_input = if P::MIDI_INPUT >= MidiConfig::Basic {
@ -267,11 +390,14 @@ impl Jack {
}; };
Ok(Self { Ok(Self {
audio_io_layout,
config, config,
client: Some(client), client: Some(client),
inputs: Arc::new(inputs), main_inputs: Arc::new(main_inputs),
outputs: Arc::new(Mutex::new(outputs)), main_outputs: Arc::new(Mutex::new(main_outputs)),
aux_input_ports: Arc::new(Mutex::new(aux_input_ports)),
aux_output_ports: Arc::new(Mutex::new(aux_output_ports)),
midi_input, midi_input,
midi_output, midi_output,
}) })
@ -285,7 +411,7 @@ impl Jack {
// We don't connect the inputs automatically to avoid feedback loops, but this should be // We don't connect the inputs automatically to avoid feedback loops, but this should be
// safe. And if this fails, then that's fine. // safe. And if this fails, then that's fine.
for (i, output) in self.outputs.lock().iter().enumerate() { for (i, output) in self.main_outputs.lock().iter().enumerate() {
// The system ports are 1-indexed // The system ports are 1-indexed
let port_no = i + 1; let port_no = i + 1;
@ -297,13 +423,13 @@ impl Jack {
// comma separated list of ports // comma separated list of ports
if let Some(port_name) = &self.config.connect_jack_inputs { if let Some(port_name) = &self.config.connect_jack_inputs {
if port_name.contains(',') { if port_name.contains(',') {
for (port_name, input) in port_name.split(',').zip(self.inputs.iter()) { for (port_name, input) in port_name.split(',').zip(self.main_inputs.iter()) {
if let Err(err) = client.connect_ports_by_name(port_name, &input.name()?) { if let Err(err) = client.connect_ports_by_name(port_name, &input.name()?) {
nih_error!("Could not connect to '{port_name}': {err}"); nih_error!("Could not connect to '{port_name}': {err}");
} }
} }
} else { } else {
for input in self.inputs.iter() { for input in self.main_inputs.iter() {
if let Err(err) = client.connect_ports_by_name(port_name, &input.name()?) { if let Err(err) = client.connect_ports_by_name(port_name, &input.name()?) {
nih_error!("Could not connect to '{port_name}': {err}"); nih_error!("Could not connect to '{port_name}': {err}");
break; break;