Add input and output events to Backend trait
This commit is contained in:
parent
e967e04856
commit
4e021dd0bb
|
@ -1,6 +1,7 @@
|
||||||
pub use self::dummy::Dummy;
|
pub use self::dummy::Dummy;
|
||||||
pub use self::jack::Jack;
|
pub use self::jack::Jack;
|
||||||
pub use crate::buffer::Buffer;
|
pub use crate::buffer::Buffer;
|
||||||
|
use crate::midi::NoteEvent;
|
||||||
|
|
||||||
mod dummy;
|
mod dummy;
|
||||||
mod jack;
|
mod jack;
|
||||||
|
@ -12,7 +13,9 @@ pub trait Backend: 'static + Send + Sync {
|
||||||
/// buffers for the wrapped plugin's outputs. Any inputs will have already been copied to this
|
/// buffers for the wrapped plugin's outputs. Any inputs will have already been copied to this
|
||||||
/// buffer. This will block until the process callback returns `false`.
|
/// buffer. This will block until the process callback returns `false`.
|
||||||
///
|
///
|
||||||
/// TODO: MIDI
|
|
||||||
/// TODO: Auxiliary inputs and outputs
|
/// TODO: Auxiliary inputs and outputs
|
||||||
fn run(&mut self, cb: impl FnMut(&mut Buffer) -> bool + 'static + Send);
|
fn run(
|
||||||
|
&mut self,
|
||||||
|
cb: impl FnMut(&mut Buffer, &[NoteEvent], &mut Vec<NoteEvent>) -> bool + 'static + Send,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::time::{Duration, Instant};
|
||||||
use super::super::config::WrapperConfig;
|
use super::super::config::WrapperConfig;
|
||||||
use super::Backend;
|
use super::Backend;
|
||||||
use crate::buffer::Buffer;
|
use crate::buffer::Buffer;
|
||||||
|
use crate::midi::NoteEvent;
|
||||||
|
|
||||||
/// This backend doesn't input or output any audio or MIDI. It only exists so the standalone
|
/// This backend doesn't input or output any audio or MIDI. It only exists so the standalone
|
||||||
/// application can continue to run even when there is no audio backend available. This can be
|
/// application can continue to run even when there is no audio backend available. This can be
|
||||||
|
@ -12,7 +13,10 @@ pub struct Dummy {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Backend for Dummy {
|
impl Backend for Dummy {
|
||||||
fn run(&mut self, mut cb: impl FnMut(&mut Buffer) -> bool + 'static + Send) {
|
fn run(
|
||||||
|
&mut self,
|
||||||
|
mut cb: impl FnMut(&mut Buffer, &[NoteEvent], &mut Vec<NoteEvent>) -> 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 =
|
||||||
|
@ -33,6 +37,8 @@ impl Backend for Dummy {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These will never actually be used
|
||||||
|
let mut midi_output_events = Vec::with_capacity(1024);
|
||||||
loop {
|
loop {
|
||||||
let period_start = Instant::now();
|
let period_start = Instant::now();
|
||||||
|
|
||||||
|
@ -40,7 +46,8 @@ impl Backend for Dummy {
|
||||||
channel.fill(0.0);
|
channel.fill(0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cb(&mut buffer) {
|
midi_output_events.clear();
|
||||||
|
if !cb(&mut buffer, &[], &mut midi_output_events) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ use jack::{
|
||||||
use super::super::config::WrapperConfig;
|
use super::super::config::WrapperConfig;
|
||||||
use super::Backend;
|
use super::Backend;
|
||||||
use crate::buffer::Buffer;
|
use crate::buffer::Buffer;
|
||||||
use crate::midi::MidiConfig;
|
use crate::midi::{MidiConfig, NoteEvent};
|
||||||
use crate::plugin::Plugin;
|
use crate::plugin::Plugin;
|
||||||
|
|
||||||
/// Uses JACK audio and MIDI.
|
/// Uses JACK audio and MIDI.
|
||||||
|
@ -32,7 +32,10 @@ enum Task {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Backend for Jack {
|
impl Backend for Jack {
|
||||||
fn run(&mut self, mut cb: impl FnMut(&mut Buffer) -> bool + 'static + Send) {
|
fn run(
|
||||||
|
&mut self,
|
||||||
|
mut cb: impl FnMut(&mut Buffer, &[NoteEvent], &mut Vec<NoteEvent>) -> bool + 'static + Send,
|
||||||
|
) {
|
||||||
let client = self.client.take().unwrap();
|
let client = self.client.take().unwrap();
|
||||||
let buffer_size = client.buffer_size();
|
let buffer_size = client.buffer_size();
|
||||||
|
|
||||||
|
@ -43,9 +46,14 @@ impl Backend for Jack {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut input_events = Vec::with_capacity(2048);
|
||||||
|
let mut output_events = Vec::with_capacity(2048);
|
||||||
|
|
||||||
let (control_sender, control_receiver) = channel::bounded(32);
|
let (control_sender, control_receiver) = channel::bounded(32);
|
||||||
let inputs = self.inputs.clone();
|
let inputs = self.inputs.clone();
|
||||||
let outputs = self.outputs.clone();
|
let outputs = self.outputs.clone();
|
||||||
|
let midi_input = self.midi_input.clone();
|
||||||
|
let midi_output = self.midi_output.clone();
|
||||||
let process_handler = ClosureProcessHandler::new(move |_client, ps| {
|
let process_handler = ClosureProcessHandler::new(move |_client, ps| {
|
||||||
// In theory we could handle `num_frames <= buffer_size`, but JACK will never chop up
|
// 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
|
// buffers like that so we'll just make it easier for ourselves by not supporting that
|
||||||
|
@ -76,7 +84,12 @@ impl Backend for Jack {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if cb(&mut buffer) {
|
// TODO: Read MIDI, convert to events
|
||||||
|
|
||||||
|
output_events.clear();
|
||||||
|
if cb(&mut buffer, &input_events, &mut output_events) {
|
||||||
|
// TODO: Convert output events to MIDI, send to the output port
|
||||||
|
|
||||||
Control::Continue
|
Control::Continue
|
||||||
} else {
|
} else {
|
||||||
control_sender.send(Task::Shutdown).unwrap();
|
control_sender.send(Task::Shutdown).unwrap();
|
||||||
|
|
|
@ -364,96 +364,100 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
|
||||||
// TODO: We should add a way to pull the transport information from the JACK backend
|
// TODO: We should add a way to pull the transport information from the JACK backend
|
||||||
let mut num_processed_samples = 0;
|
let mut num_processed_samples = 0;
|
||||||
|
|
||||||
self.clone().backend.borrow_mut().run(move |buffer| {
|
self.clone()
|
||||||
if should_terminate.load(Ordering::SeqCst) {
|
.backend
|
||||||
return false;
|
.borrow_mut()
|
||||||
}
|
.run(move |buffer, input_events, output_events| {
|
||||||
|
if should_terminate.load(Ordering::SeqCst) {
|
||||||
// TODO: Process incoming events
|
return false;
|
||||||
|
|
||||||
let sample_rate = self.buffer_config.sample_rate;
|
|
||||||
let mut transport = Transport::new(sample_rate);
|
|
||||||
transport.pos_samples = Some(num_processed_samples);
|
|
||||||
transport.tempo = Some(self.config.tempo as f64);
|
|
||||||
transport.time_sig_numerator = Some(self.config.timesig_num as i32);
|
|
||||||
transport.time_sig_denominator = Some(self.config.timesig_denom as i32);
|
|
||||||
transport.playing = true;
|
|
||||||
|
|
||||||
if let ProcessStatus::Error(err) = self.plugin.write().process(
|
|
||||||
buffer,
|
|
||||||
// TODO: Provide extra inputs and outputs in the JACk backend
|
|
||||||
&mut AuxiliaryBuffers {
|
|
||||||
inputs: &mut [],
|
|
||||||
outputs: &mut [],
|
|
||||||
},
|
|
||||||
&mut self.make_process_context(transport),
|
|
||||||
) {
|
|
||||||
nih_error!("The plugin returned an error while processing:");
|
|
||||||
nih_error!("{}", err);
|
|
||||||
|
|
||||||
let push_successful = gui_task_sender.send(GuiTask::Close).is_ok();
|
|
||||||
nih_debug_assert!(
|
|
||||||
push_successful,
|
|
||||||
"Could not queue window close, the editor will remain open"
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We'll always write these events to the first sample, so even when we add note output we
|
|
||||||
// shouldn't have to think about interleaving events here
|
|
||||||
let mut parameter_values_changed = false;
|
|
||||||
while let Some((param_ptr, normalized_value)) = self.unprocessed_param_changes.pop() {
|
|
||||||
unsafe { param_ptr.set_normalized_value(normalized_value) };
|
|
||||||
unsafe { param_ptr.update_smoother(sample_rate, false) };
|
|
||||||
parameter_values_changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow the editor to react to the new parameter values if the editor uses a reactive data
|
|
||||||
// binding model
|
|
||||||
if parameter_values_changed {
|
|
||||||
self.notify_param_values_changed();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: MIDI output
|
|
||||||
|
|
||||||
// After processing audio, we'll check if the editor has sent us updated plugin state.
|
|
||||||
// We'll restore that here on the audio thread to prevent changing the values during the
|
|
||||||
// process call and also to prevent inconsistent state when the host also wants to load
|
|
||||||
// plugin state.
|
|
||||||
// FIXME: Zero capacity channels allocate on receiving, find a better alternative that
|
|
||||||
// doesn't do that
|
|
||||||
let updated_state = permit_alloc(|| self.updated_state_receiver.try_recv());
|
|
||||||
if let Ok(state) = updated_state {
|
|
||||||
unsafe {
|
|
||||||
state::deserialize_object(
|
|
||||||
&state,
|
|
||||||
self.params.clone(),
|
|
||||||
|param_id| self.param_map.get(param_id).copied(),
|
|
||||||
Some(&self.buffer_config),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.notify_param_values_changed();
|
// TODO: Do something with the input and output events
|
||||||
|
|
||||||
// TODO: Normally we'd also call initialize after deserializing state, but that's
|
let sample_rate = self.buffer_config.sample_rate;
|
||||||
// not guaranteed to be realtime safe. Should we do it anyways?
|
let mut transport = Transport::new(sample_rate);
|
||||||
self.plugin.write().reset();
|
transport.pos_samples = Some(num_processed_samples);
|
||||||
|
transport.tempo = Some(self.config.tempo as f64);
|
||||||
|
transport.time_sig_numerator = Some(self.config.timesig_num as i32);
|
||||||
|
transport.time_sig_denominator = Some(self.config.timesig_denom as i32);
|
||||||
|
transport.playing = true;
|
||||||
|
|
||||||
// We'll pass the state object back to the GUI thread so deallocation can happen
|
if let ProcessStatus::Error(err) = self.plugin.write().process(
|
||||||
// there without potentially blocking the audio thread
|
buffer,
|
||||||
if let Err(err) = self.updated_state_sender.send(state) {
|
// TODO: Provide extra inputs and outputs in the JACk backend
|
||||||
nih_debug_assert_failure!(
|
&mut AuxiliaryBuffers {
|
||||||
"Failed to send state object back to GUI thread: {}",
|
inputs: &mut [],
|
||||||
err
|
outputs: &mut [],
|
||||||
|
},
|
||||||
|
&mut self.make_process_context(transport),
|
||||||
|
) {
|
||||||
|
nih_error!("The plugin returned an error while processing:");
|
||||||
|
nih_error!("{}", err);
|
||||||
|
|
||||||
|
let push_successful = gui_task_sender.send(GuiTask::Close).is_ok();
|
||||||
|
nih_debug_assert!(
|
||||||
|
push_successful,
|
||||||
|
"Could not queue window close, the editor will remain open"
|
||||||
);
|
);
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
num_processed_samples += buffer.len() as i64;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
true
|
// We'll always write these events to the first sample, so even when we add note output we
|
||||||
});
|
// shouldn't have to think about interleaving events here
|
||||||
|
let mut parameter_values_changed = false;
|
||||||
|
while let Some((param_ptr, normalized_value)) = self.unprocessed_param_changes.pop()
|
||||||
|
{
|
||||||
|
unsafe { param_ptr.set_normalized_value(normalized_value) };
|
||||||
|
unsafe { param_ptr.update_smoother(sample_rate, false) };
|
||||||
|
parameter_values_changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow the editor to react to the new parameter values if the editor uses a reactive data
|
||||||
|
// binding model
|
||||||
|
if parameter_values_changed {
|
||||||
|
self.notify_param_values_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: MIDI output
|
||||||
|
|
||||||
|
// After processing audio, we'll check if the editor has sent us updated plugin state.
|
||||||
|
// We'll restore that here on the audio thread to prevent changing the values during the
|
||||||
|
// process call and also to prevent inconsistent state when the host also wants to load
|
||||||
|
// plugin state.
|
||||||
|
// FIXME: Zero capacity channels allocate on receiving, find a better alternative that
|
||||||
|
// doesn't do that
|
||||||
|
let updated_state = permit_alloc(|| self.updated_state_receiver.try_recv());
|
||||||
|
if let Ok(state) = updated_state {
|
||||||
|
unsafe {
|
||||||
|
state::deserialize_object(
|
||||||
|
&state,
|
||||||
|
self.params.clone(),
|
||||||
|
|param_id| self.param_map.get(param_id).copied(),
|
||||||
|
Some(&self.buffer_config),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.notify_param_values_changed();
|
||||||
|
|
||||||
|
// TODO: Normally we'd also call initialize after deserializing state, but that's
|
||||||
|
// not guaranteed to be realtime safe. Should we do it anyways?
|
||||||
|
self.plugin.write().reset();
|
||||||
|
|
||||||
|
// We'll pass the state object back to the GUI thread so deallocation can happen
|
||||||
|
// there without potentially blocking the audio thread
|
||||||
|
if let Err(err) = self.updated_state_sender.send(state) {
|
||||||
|
nih_debug_assert_failure!(
|
||||||
|
"Failed to send state object back to GUI thread: {}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
num_processed_samples += buffer.len() as i64;
|
||||||
|
|
||||||
|
true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell the editor that the parameter values have changed, if the plugin has an editor.
|
/// Tell the editor that the parameter values have changed, if the plugin has an editor.
|
||||||
|
|
Loading…
Reference in a new issue