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: MIDI
|
||||||
/// TODO: Auxiliary inputs and outputs
|
/// 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 {
|
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
|
// We can't really do anything meaningful here, so we'll simply periodically call the
|
||||||
// callback with empty buffers.
|
// callback with empty buffers.
|
||||||
let interval =
|
let interval =
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
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::super::config::WrapperConfig;
|
||||||
use super::Backend;
|
use super::Backend;
|
||||||
|
@ -7,17 +11,91 @@ use crate::buffer::Buffer;
|
||||||
|
|
||||||
/// Uses JACK audio and MIDI.
|
/// Uses JACK audio and MIDI.
|
||||||
pub struct Jack {
|
pub struct Jack {
|
||||||
config: WrapperConfig,
|
/// The JACK client, wrapped in an option since it needs to be transformed into an `AsyncClient`
|
||||||
client: Client,
|
/// and then back into a regular `Client`.
|
||||||
|
client: Option<Client>,
|
||||||
|
|
||||||
inputs: Vec<Port<AudioIn>>,
|
inputs: Arc<Vec<Port<AudioIn>>>,
|
||||||
outputs: Vec<Port<AudioOut>>,
|
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 {
|
impl Backend for Jack {
|
||||||
fn run(&mut self, cb: impl FnMut(&mut Buffer) -> bool) {
|
fn run(&mut self, mut cb: impl FnMut(&mut Buffer) -> bool + 'static + Send) {
|
||||||
// TODO: Create an async client and do The Thing (tm)
|
let client = self.client.take().unwrap();
|
||||||
todo!()
|
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?
|
// TODO: Command line argument to connect the inputs?
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
config,
|
client: Some(client),
|
||||||
client,
|
|
||||||
|
|
||||||
inputs,
|
inputs: Arc::new(inputs),
|
||||||
outputs,
|
outputs: Arc::new(AtomicRefCell::new(outputs)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue