Use JACK transport data for JACK standalone
This commit is contained in:
parent
4219acaf83
commit
e891e1fdb7
4 changed files with 62 additions and 30 deletions
|
@ -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::context::Transport;
|
||||||
use crate::midi::NoteEvent;
|
use crate::midi::NoteEvent;
|
||||||
|
|
||||||
mod dummy;
|
mod dummy;
|
||||||
|
@ -16,6 +17,8 @@ pub trait Backend: 'static + Send + Sync {
|
||||||
/// TODO: Auxiliary inputs and outputs
|
/// TODO: Auxiliary inputs and outputs
|
||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
cb: impl FnMut(&mut Buffer, &[NoteEvent], &mut Vec<NoteEvent>) -> bool + 'static + Send,
|
cb: impl FnMut(&mut Buffer, Transport, &[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::context::Transport;
|
||||||
use crate::midi::NoteEvent;
|
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
|
||||||
|
@ -15,7 +16,9 @@ pub struct Dummy {
|
||||||
impl Backend for Dummy {
|
impl Backend for Dummy {
|
||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut cb: impl FnMut(&mut Buffer, &[NoteEvent], &mut Vec<NoteEvent>) -> bool + 'static + Send,
|
mut cb: impl FnMut(&mut Buffer, Transport, &[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.
|
||||||
|
@ -37,20 +40,30 @@ impl Backend for Dummy {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// These will never actually be used
|
// This queue will never actually be used
|
||||||
let mut midi_output_events = Vec::with_capacity(1024);
|
let mut midi_output_events = Vec::with_capacity(1024);
|
||||||
|
let mut num_processed_samples = 0;
|
||||||
loop {
|
loop {
|
||||||
let period_start = Instant::now();
|
let period_start = Instant::now();
|
||||||
|
|
||||||
|
let mut transport = Transport::new(self.config.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;
|
||||||
|
|
||||||
for channel in buffer.as_slice() {
|
for channel in buffer.as_slice() {
|
||||||
channel.fill(0.0);
|
channel.fill(0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
midi_output_events.clear();
|
midi_output_events.clear();
|
||||||
if !cb(&mut buffer, &[], &mut midi_output_events) {
|
if !cb(&mut buffer, transport, &[], &mut midi_output_events) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
num_processed_samples += buffer.len() as i64;
|
||||||
|
|
||||||
let period_end = Instant::now();
|
let period_end = Instant::now();
|
||||||
std::thread::sleep((period_start + interval).saturating_duration_since(period_end));
|
std::thread::sleep((period_start + interval).saturating_duration_since(period_end));
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,13 @@ 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::context::Transport;
|
||||||
use crate::midi::{MidiConfig, NoteEvent};
|
use crate::midi::{MidiConfig, NoteEvent};
|
||||||
use crate::plugin::Plugin;
|
use crate::plugin::Plugin;
|
||||||
|
|
||||||
/// 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`
|
/// 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>,
|
||||||
|
@ -34,7 +36,9 @@ enum Task {
|
||||||
impl Backend for Jack {
|
impl Backend for Jack {
|
||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut cb: impl FnMut(&mut Buffer, &[NoteEvent], &mut Vec<NoteEvent>) -> bool + 'static + Send,
|
mut cb: impl FnMut(&mut Buffer, Transport, &[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();
|
||||||
|
@ -50,11 +54,12 @@ impl Backend for Jack {
|
||||||
let mut output_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 config = self.config.clone();
|
||||||
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_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| {
|
||||||
// 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
|
||||||
let num_frames = ps.n_frames();
|
let num_frames = ps.n_frames();
|
||||||
|
@ -64,6 +69,29 @@ impl Backend for Jack {
|
||||||
return Control::Quit;
|
return Control::Quit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut transport = Transport::new(client.sample_rate() as f32);
|
||||||
|
transport.tempo = Some(config.tempo as f64);
|
||||||
|
transport.time_sig_numerator = Some(config.timesig_num as i32);
|
||||||
|
transport.time_sig_denominator = Some(config.timesig_denom as i32);
|
||||||
|
|
||||||
|
if let Ok(jack_transport) = client.transport().query() {
|
||||||
|
transport.pos_samples = Some(jack_transport.pos.frame() as i64);
|
||||||
|
transport.playing = jack_transport.state == jack::TransportState::Rolling;
|
||||||
|
|
||||||
|
if let Some(bbt) = jack_transport.pos.bbt() {
|
||||||
|
transport.tempo = Some(bbt.bpm);
|
||||||
|
transport.time_sig_numerator = Some(bbt.sig_num as i32);
|
||||||
|
transport.time_sig_denominator = Some(bbt.sig_denom as i32);
|
||||||
|
|
||||||
|
transport.pos_beats = Some(
|
||||||
|
(bbt.bar as f64 * 4.0)
|
||||||
|
+ (bbt.beat as f64 / bbt.sig_denom as f64 * 4.0)
|
||||||
|
+ (bbt.tick as f64 / bbt.ticks_per_beat),
|
||||||
|
);
|
||||||
|
transport.bar_number = Some(bbt.bar as i32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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.borrow_mut();
|
let mut outputs = outputs.borrow_mut();
|
||||||
|
@ -94,7 +122,7 @@ impl Backend for Jack {
|
||||||
}
|
}
|
||||||
|
|
||||||
output_events.clear();
|
output_events.clear();
|
||||||
if cb(&mut buffer, &input_events, &mut output_events) {
|
if cb(&mut buffer, transport, &input_events, &mut output_events) {
|
||||||
if let Some(midi_output) = &midi_output {
|
if let Some(midi_output) = &midi_output {
|
||||||
let mut midi_output = midi_output.borrow_mut();
|
let mut midi_output = midi_output.borrow_mut();
|
||||||
let mut midi_writer = midi_output.writer(ps);
|
let mut midi_writer = midi_output.writer(ps);
|
||||||
|
@ -182,7 +210,7 @@ impl Jack {
|
||||||
|
|
||||||
// This option can either be set to a single port all inputs should be connected to, or a
|
// This option can either be set to a single port all inputs should be connected to, or a
|
||||||
// comma separated list of ports
|
// comma separated list of ports
|
||||||
if let Some(port_name) = config.connect_jack_inputs {
|
if let Some(port_name) = &config.connect_jack_inputs {
|
||||||
if port_name.contains(',') {
|
if port_name.contains(',') {
|
||||||
for (port_name, input) in port_name.split(',').zip(&inputs) {
|
for (port_name, input) in port_name.split(',').zip(&inputs) {
|
||||||
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()?) {
|
||||||
|
@ -192,7 +220,7 @@ impl Jack {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for input in &inputs {
|
for input in &inputs {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -200,18 +228,19 @@ impl Jack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (Some(port), Some(port_name)) = (&midi_input, config.connect_jack_midi_input) {
|
if let (Some(port), Some(port_name)) = (&midi_input, &config.connect_jack_midi_input) {
|
||||||
if let Err(err) = client.connect_ports_by_name(&port_name, &port.name()?) {
|
if let Err(err) = client.connect_ports_by_name(port_name, &port.name()?) {
|
||||||
nih_error!("Could not connect to '{port_name}': {err}");
|
nih_error!("Could not connect to '{port_name}': {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let (Some(port), Some(port_name)) = (&midi_output, config.connect_jack_midi_output) {
|
if let (Some(port), Some(port_name)) = (&midi_output, &config.connect_jack_midi_output) {
|
||||||
if let Err(err) = client.connect_ports_by_name(&port.borrow().name()?, &port_name) {
|
if let Err(err) = client.connect_ports_by_name(&port.borrow().name()?, port_name) {
|
||||||
nih_error!("Could not connect to '{port_name}': {err}");
|
nih_error!("Could not connect to '{port_name}': {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
config,
|
||||||
client: Some(client),
|
client: Some(client),
|
||||||
|
|
||||||
inputs: Arc::new(inputs),
|
inputs: Arc::new(inputs),
|
||||||
|
|
|
@ -368,25 +368,13 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
|
||||||
should_terminate: Arc<AtomicBool>,
|
should_terminate: Arc<AtomicBool>,
|
||||||
gui_task_sender: channel::Sender<GuiTask>,
|
gui_task_sender: channel::Sender<GuiTask>,
|
||||||
) {
|
) {
|
||||||
// TODO: We should add a way to pull the transport information from the JACK backend
|
self.clone().backend.borrow_mut().run(
|
||||||
let mut num_processed_samples = 0;
|
move |buffer, transport, input_events, output_events| {
|
||||||
|
|
||||||
self.clone()
|
|
||||||
.backend
|
|
||||||
.borrow_mut()
|
|
||||||
.run(move |buffer, input_events, output_events| {
|
|
||||||
if should_terminate.load(Ordering::SeqCst) {
|
if should_terminate.load(Ordering::SeqCst) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sample_rate = self.buffer_config.sample_rate;
|
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(
|
if let ProcessStatus::Error(err) = self.plugin.write().process(
|
||||||
buffer,
|
buffer,
|
||||||
// TODO: Provide extra inputs and outputs in the JACk backend
|
// TODO: Provide extra inputs and outputs in the JACk backend
|
||||||
|
@ -460,10 +448,9 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
num_processed_samples += buffer.len() as i64;
|
|
||||||
|
|
||||||
true
|
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…
Add table
Reference in a new issue