diff --git a/src/wrapper/standalone/backend.rs b/src/wrapper/standalone/backend.rs index e1af0876..448da915 100644 --- a/src/wrapper/standalone/backend.rs +++ b/src/wrapper/standalone/backend.rs @@ -1,6 +1,7 @@ pub use self::dummy::Dummy; pub use self::jack::Jack; pub use crate::buffer::Buffer; +use crate::context::Transport; use crate::midi::NoteEvent; mod dummy; @@ -16,6 +17,8 @@ pub trait Backend: 'static + Send + Sync { /// TODO: Auxiliary inputs and outputs fn run( &mut self, - cb: impl FnMut(&mut Buffer, &[NoteEvent], &mut Vec) -> bool + 'static + Send, + cb: impl FnMut(&mut Buffer, Transport, &[NoteEvent], &mut Vec) -> bool + + 'static + + Send, ); } diff --git a/src/wrapper/standalone/backend/dummy.rs b/src/wrapper/standalone/backend/dummy.rs index af501fac..ec8ddd23 100644 --- a/src/wrapper/standalone/backend/dummy.rs +++ b/src/wrapper/standalone/backend/dummy.rs @@ -3,6 +3,7 @@ use std::time::{Duration, Instant}; use super::super::config::WrapperConfig; use super::Backend; use crate::buffer::Buffer; +use crate::context::Transport; use crate::midi::NoteEvent; /// 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 { fn run( &mut self, - mut cb: impl FnMut(&mut Buffer, &[NoteEvent], &mut Vec) -> bool + 'static + Send, + mut cb: impl FnMut(&mut Buffer, Transport, &[NoteEvent], &mut Vec) -> bool + + 'static + + Send, ) { // We can't really do anything meaningful here, so we'll simply periodically call the // 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 num_processed_samples = 0; loop { 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() { channel.fill(0.0); } midi_output_events.clear(); - if !cb(&mut buffer, &[], &mut midi_output_events) { + if !cb(&mut buffer, transport, &[], &mut midi_output_events) { break; } + num_processed_samples += buffer.len() as i64; + let period_end = Instant::now(); std::thread::sleep((period_start + interval).saturating_duration_since(period_end)); } diff --git a/src/wrapper/standalone/backend/jack.rs b/src/wrapper/standalone/backend/jack.rs index 20f39571..09e91499 100644 --- a/src/wrapper/standalone/backend/jack.rs +++ b/src/wrapper/standalone/backend/jack.rs @@ -10,11 +10,13 @@ use jack::{ use super::super::config::WrapperConfig; use super::Backend; use crate::buffer::Buffer; +use crate::context::Transport; use crate::midi::{MidiConfig, NoteEvent}; use crate::plugin::Plugin; /// Uses JACK audio and MIDI. pub struct Jack { + config: WrapperConfig, /// 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, @@ -34,7 +36,9 @@ enum Task { impl Backend for Jack { fn run( &mut self, - mut cb: impl FnMut(&mut Buffer, &[NoteEvent], &mut Vec) -> bool + 'static + Send, + mut cb: impl FnMut(&mut Buffer, Transport, &[NoteEvent], &mut Vec) -> bool + + 'static + + Send, ) { let client = self.client.take().unwrap(); let buffer_size = client.buffer_size(); @@ -50,11 +54,12 @@ impl Backend for Jack { let mut output_events = Vec::with_capacity(2048); let (control_sender, control_receiver) = channel::bounded(32); + let config = self.config.clone(); let inputs = self.inputs.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 // buffers like that so we'll just make it easier for ourselves by not supporting that let num_frames = ps.n_frames(); @@ -64,6 +69,29 @@ impl Backend for Jack { 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 // inputs to the outputs let mut outputs = outputs.borrow_mut(); @@ -94,7 +122,7 @@ impl Backend for Jack { } 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 { let mut midi_output = midi_output.borrow_mut(); 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 // 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(',') { for (port_name, input) in port_name.split(',').zip(&inputs) { if let Err(err) = client.connect_ports_by_name(port_name, &input.name()?) { @@ -192,7 +220,7 @@ impl Jack { } } else { 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}"); break; } @@ -200,18 +228,19 @@ impl Jack { } } - 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 (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()?) { 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 Err(err) = client.connect_ports_by_name(&port.borrow().name()?, &port_name) { + 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) { nih_error!("Could not connect to '{port_name}': {err}"); } } Ok(Self { + config, client: Some(client), inputs: Arc::new(inputs), diff --git a/src/wrapper/standalone/wrapper.rs b/src/wrapper/standalone/wrapper.rs index 8adc14eb..b8663b67 100644 --- a/src/wrapper/standalone/wrapper.rs +++ b/src/wrapper/standalone/wrapper.rs @@ -368,25 +368,13 @@ impl Wrapper { should_terminate: Arc, gui_task_sender: channel::Sender, ) { - // TODO: We should add a way to pull the transport information from the JACK backend - let mut num_processed_samples = 0; - - self.clone() - .backend - .borrow_mut() - .run(move |buffer, input_events, output_events| { + self.clone().backend.borrow_mut().run( + move |buffer, transport, input_events, output_events| { if should_terminate.load(Ordering::SeqCst) { 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 @@ -460,10 +448,9 @@ impl Wrapper { }; } - num_processed_samples += buffer.len() as i64; - true - }); + }, + ); } /// Tell the editor that the parameter values have changed, if the plugin has an editor.