1
0
Fork 0

Add audio support to the JACK backend

This commit is contained in:
Robbert van der Helm 2022-06-14 18:59:54 +02:00
parent 7216627a01
commit 7e0caa4c8d
3 changed files with 91 additions and 14 deletions

View file

@ -14,5 +14,5 @@ pub trait Backend: 'static + Send + Sync {
///
/// TODO: MIDI
/// TODO: Auxiliary inputs and outputs
fn run(&mut self, cb: impl FnMut(&mut Buffer) -> bool);
fn run(&mut self, cb: impl FnMut(&mut Buffer) -> bool + 'static + Send);
}

View file

@ -12,7 +12,7 @@ pub struct Dummy {
}
impl Backend for Dummy {
fn run(&mut self, mut cb: impl FnMut(&mut Buffer) -> bool) {
fn run(&mut self, mut cb: impl FnMut(&mut Buffer) -> bool + 'static + Send) {
// We can't really do anything meaningful here, so we'll simply periodically call the
// callback with empty buffers.
let interval =

View file

@ -1,5 +1,9 @@
use std::sync::Arc;
use anyhow::{Context, Result};
use jack::{AudioIn, AudioOut, Client, ClientOptions, Port};
use atomic_refcell::AtomicRefCell;
use crossbeam::channel;
use jack::{AudioIn, AudioOut, Client, ClientOptions, ClosureProcessHandler, Control, Port};
use super::super::config::WrapperConfig;
use super::Backend;
@ -7,17 +11,91 @@ use crate::buffer::Buffer;
/// Uses JACK audio and MIDI.
pub struct Jack {
config: WrapperConfig,
client: Client,
/// The JACK client, wrapped in an option since it needs to be transformed into an `AsyncClient`
/// and then back into a regular `Client`.
client: Option<Client>,
inputs: Vec<Port<AudioIn>>,
outputs: Vec<Port<AudioOut>>,
inputs: Arc<Vec<Port<AudioIn>>>,
outputs: Arc<AtomicRefCell<Vec<Port<AudioOut>>>>,
}
/// A simple message to tell the audio thread to shut down, since the actual processing happens in
/// these callbacks.
enum Task {
Shutdown,
}
impl Backend for Jack {
fn run(&mut self, cb: impl FnMut(&mut Buffer) -> bool) {
// TODO: Create an async client and do The Thing (tm)
todo!()
fn run(&mut self, mut cb: impl FnMut(&mut Buffer) -> bool + 'static + Send) {
let client = self.client.take().unwrap();
let buffer_size = client.buffer_size();
let mut buffer = Buffer::default();
unsafe {
buffer.with_raw_vec(|output_slices| {
output_slices.resize_with(self.outputs.borrow().len(), || &mut []);
})
}
let (control_sender, control_receiver) = channel::bounded(32);
let inputs = self.inputs.clone();
let outputs = self.outputs.clone();
let process_handler = ClosureProcessHandler::new(move |_client, ps| {
// In theory we could handle `num_frames <= buffer_size`, but JACK will never chop up
// buffers like that so we'll just make it easier for ourselves by not supporting that
let num_frames = ps.n_frames();
if num_frames != buffer_size {
nih_error!("Buffer size changed from {buffer_size} to {num_frames}. Buffer size changes are currently not supported, aborting...");
control_sender.send(Task::Shutdown).unwrap();
return Control::Quit;
}
// Just like all of the plugin backends, we need to grab the output slices and copy the
// inputs to the outputs
let mut outputs = outputs.borrow_mut();
for (input, output) in inputs.iter().zip(outputs.iter_mut()) {
// XXX: Since the JACK bindings let us do this, presumably these two can't alias,
// right?
output.as_mut_slice(ps).copy_from_slice(input.as_slice(ps));
}
// And the buffer's slices need to point to the JACK output ports
unsafe {
buffer.with_raw_vec(|output_slices| {
for (output_slice, output) in output_slices.iter_mut().zip(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 *(output.as_mut_slice(ps) as *mut _);
}
})
}
if cb(&mut buffer) {
Control::Continue
} else {
control_sender.send(Task::Shutdown).unwrap();
Control::Quit
}
});
// TODO: What can go wrong here that would cause an error?
let async_client = client.activate_async((), process_handler).unwrap();
// The process callback happens on another thread, so we need to block this thread until we
// get the request to shut down or until the process callback runs into an error
#[allow(clippy::never_loop)]
loop {
match control_receiver.recv() {
Ok(Task::Shutdown) => break,
Err(err) => {
nih_debug_assert_failure!("Error reading from channel: {}", err);
break;
}
}
}
// And put the client back where it belongs in case this function is called a second time
let (client, _, _) = async_client.deactivate().unwrap();
self.client = Some(client);
}
}
@ -50,11 +128,10 @@ impl Jack {
// TODO: Command line argument to connect the inputs?
Ok(Self {
config,
client,
client: Some(client),
inputs,
outputs,
inputs: Arc::new(inputs),
outputs: Arc::new(AtomicRefCell::new(outputs)),
})
}
}