Add audio support to the JACK backend
This commit is contained in:
parent
7216627a01
commit
7e0caa4c8d
3 changed files with 91 additions and 14 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue