Also implement sample accurate automation for VST3
This commit is contained in:
parent
1559afe080
commit
5d3527c5c2
4 changed files with 279 additions and 159 deletions
|
@ -18,7 +18,6 @@ use crate::param::internals::Params;
|
||||||
/// - Sidechain inputs
|
/// - Sidechain inputs
|
||||||
/// - Multiple output busses
|
/// - Multiple output busses
|
||||||
/// - Special handling for offline processing
|
/// - Special handling for offline processing
|
||||||
/// - Sample accurate automation for VST3, this can already be enabled for CLAP
|
|
||||||
/// - Parameter hierarchies/groups
|
/// - Parameter hierarchies/groups
|
||||||
/// - Bypass parameters, right now the plugin wrappers generates one for you but there's no way to
|
/// - Bypass parameters, right now the plugin wrappers generates one for you but there's no way to
|
||||||
/// interact with it yet
|
/// interact with it yet
|
||||||
|
@ -51,8 +50,6 @@ pub trait Plugin: Default + Send + Sync + 'static {
|
||||||
/// parameter values change occur in the middle of the buffer. Depending on the host these
|
/// parameter values change occur in the middle of the buffer. Depending on the host these
|
||||||
/// blocks may be as small as a single sample. Bitwig Studio sends at most one parameter change
|
/// blocks may be as small as a single sample. Bitwig Studio sends at most one parameter change
|
||||||
/// every 64 samples.
|
/// every 64 samples.
|
||||||
///
|
|
||||||
/// TODO: Implement this for VST3, this currently is only implemetned for CLAP.
|
|
||||||
const SAMPLE_ACCURATE_AUTOMATION: bool = false;
|
const SAMPLE_ACCURATE_AUTOMATION: bool = false;
|
||||||
|
|
||||||
/// The plugin's parameters. The host will update the parameter values before calling
|
/// The plugin's parameters. The host will update the parameter values before calling
|
||||||
|
|
|
@ -926,7 +926,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Most hosts process data in place, in which case we don't need to do any copying
|
// Some hosts process data in place, in which case we don't need to do any copying
|
||||||
// ourselves. If the pointers do not alias, then we'll do the copy here and then the
|
// ourselves. If the pointers do not alias, then we'll do the copy here and then the
|
||||||
// plugin can just do normal in place processing.
|
// plugin can just do normal in place processing.
|
||||||
if !process.audio_inputs.is_null() {
|
if !process.audio_inputs.is_null() {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use atomic_refcell::AtomicRefCell;
|
use atomic_refcell::AtomicRefCell;
|
||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::cmp::Reverse;
|
||||||
|
use std::collections::{BinaryHeap, HashMap, VecDeque};
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -66,11 +67,16 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
|
||||||
/// between process calls. This buffer owns the vector, because otherwise it would need to store
|
/// between process calls. This buffer owns the vector, because otherwise it would need to store
|
||||||
/// a mutable reference to the data contained in this mutex.
|
/// a mutable reference to the data contained in this mutex.
|
||||||
pub output_buffer: AtomicRefCell<Buffer<'static>>,
|
pub output_buffer: AtomicRefCell<Buffer<'static>>,
|
||||||
/// The incoming events for the plugin, if `P::ACCEPTS_MIDI` is set.
|
/// The incoming events for the plugin, if `P::ACCEPTS_MIDI` is set. If
|
||||||
///
|
/// `P::SAMPLE_ACCURATE_AUTOMATION`, this is also read in lockstep with the parameter change
|
||||||
/// TODO: Maybe load these lazily at some point instead of needing to spool them all to this
|
/// block splitting.
|
||||||
/// queue first
|
|
||||||
pub input_events: AtomicRefCell<VecDeque<NoteEvent>>,
|
pub input_events: AtomicRefCell<VecDeque<NoteEvent>>,
|
||||||
|
/// Unprocessed parameter changes sent by the host as pairs of `(sample_idx_in_buffer, change)`.
|
||||||
|
/// Needed because VST3 does not have a single queue containing all parameter changes. If
|
||||||
|
/// `P::SAMPLE_ACCURATE_AUTOMATION` is set, then all parameter changes will be read into this
|
||||||
|
/// priority queue and the buffer will be processed in small chunks whenever there's a parameter
|
||||||
|
/// change at a new sample index.
|
||||||
|
pub input_param_changes: AtomicRefCell<BinaryHeap<Reverse<(usize, ParameterChange)>>>,
|
||||||
|
|
||||||
/// The keys from `param_map` in a stable order.
|
/// The keys from `param_map` in a stable order.
|
||||||
pub param_hashes: Vec<u32>,
|
pub param_hashes: Vec<u32>,
|
||||||
|
@ -102,6 +108,26 @@ pub enum Task {
|
||||||
TriggerRestart(i32),
|
TriggerRestart(i32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An incoming parameter change sent by the host. Kept in a queue to support block-based sample
|
||||||
|
/// accurate automation.
|
||||||
|
#[derive(Debug, PartialEq, PartialOrd)]
|
||||||
|
pub struct ParameterChange {
|
||||||
|
/// The parameter's hash, as used everywhere else.
|
||||||
|
pub hash: u32,
|
||||||
|
/// The normalized values, as provided by the host.
|
||||||
|
pub normalized_value: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instances needed for the binary heap, we'll just pray the host doesn't send NaN values
|
||||||
|
impl Eq for ParameterChange {}
|
||||||
|
|
||||||
|
#[allow(clippy::derive_ord_xor_partial_ord)]
|
||||||
|
impl Ord for ParameterChange {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.partial_cmp(other).unwrap_or(std::cmp::Ordering::Equal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<P: Vst3Plugin> WrapperInner<P> {
|
impl<P: Vst3Plugin> WrapperInner<P> {
|
||||||
#[allow(unused_unsafe)]
|
#[allow(unused_unsafe)]
|
||||||
pub fn new() -> Arc<Self> {
|
pub fn new() -> Arc<Self> {
|
||||||
|
@ -132,7 +158,14 @@ impl<P: Vst3Plugin> WrapperInner<P> {
|
||||||
last_process_status: AtomicCell::new(ProcessStatus::Normal),
|
last_process_status: AtomicCell::new(ProcessStatus::Normal),
|
||||||
current_latency: AtomicU32::new(0),
|
current_latency: AtomicU32::new(0),
|
||||||
output_buffer: AtomicRefCell::new(Buffer::default()),
|
output_buffer: AtomicRefCell::new(Buffer::default()),
|
||||||
input_events: AtomicRefCell::new(VecDeque::with_capacity(512)),
|
input_events: AtomicRefCell::new(VecDeque::with_capacity(1024)),
|
||||||
|
input_param_changes: AtomicRefCell::new(BinaryHeap::with_capacity(
|
||||||
|
if P::SAMPLE_ACCURATE_AUTOMATION {
|
||||||
|
4096
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
|
||||||
param_hashes: Vec::new(),
|
param_hashes: Vec::new(),
|
||||||
param_by_hash: HashMap::new(),
|
param_by_hash: HashMap::new(),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::cmp;
|
use std::cmp::{self, Reverse};
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::mem::{self, MaybeUninit};
|
use std::mem::{self, MaybeUninit};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
@ -21,6 +21,7 @@ use crate::context::Transport;
|
||||||
use crate::plugin::{BufferConfig, BusConfig, NoteEvent, ProcessStatus, Vst3Plugin};
|
use crate::plugin::{BufferConfig, BusConfig, NoteEvent, ProcessStatus, Vst3Plugin};
|
||||||
use crate::wrapper::state;
|
use crate::wrapper::state;
|
||||||
use crate::wrapper::util::{process_wrapper, u16strlcpy};
|
use crate::wrapper::util::{process_wrapper, u16strlcpy};
|
||||||
|
use crate::wrapper::vst3::inner::ParameterChange;
|
||||||
|
|
||||||
// Alias needed for the VST3 attribute macro
|
// Alias needed for the VST3 attribute macro
|
||||||
use vst3_sys as vst3_com;
|
use vst3_sys as vst3_com;
|
||||||
|
@ -645,74 +646,17 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
||||||
.load()
|
.load()
|
||||||
.expect("Process call without prior setup call")
|
.expect("Process call without prior setup call")
|
||||||
.sample_rate;
|
.sample_rate;
|
||||||
if let Some(param_changes) = data.input_param_changes.upgrade() {
|
|
||||||
let num_param_queues = param_changes.get_parameter_count();
|
|
||||||
for change_queue_idx in 0..num_param_queues {
|
|
||||||
if let Some(param_change_queue) =
|
|
||||||
param_changes.get_parameter_data(change_queue_idx).upgrade()
|
|
||||||
{
|
|
||||||
let param_hash = param_change_queue.get_parameter_id();
|
|
||||||
let num_changes = param_change_queue.get_point_count();
|
|
||||||
|
|
||||||
// TODO: Handle sample accurate parameter changes, possibly in a similar way
|
|
||||||
// to the smoothing
|
|
||||||
let mut sample_offset = 0i32;
|
|
||||||
let mut value = 0.0f64;
|
|
||||||
if num_changes > 0
|
|
||||||
&& param_change_queue.get_point(
|
|
||||||
num_changes - 1,
|
|
||||||
&mut sample_offset,
|
|
||||||
&mut value,
|
|
||||||
) == kResultOk
|
|
||||||
{
|
|
||||||
self.inner.set_normalized_value_by_hash(
|
|
||||||
param_hash,
|
|
||||||
value as f32,
|
|
||||||
Some(sample_rate),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// And also incoming note events if the plugin accepts MDII
|
|
||||||
if P::ACCEPTS_MIDI {
|
|
||||||
let mut input_events = self.inner.input_events.borrow_mut();
|
|
||||||
if let Some(events) = data.input_events.upgrade() {
|
|
||||||
let num_events = events.get_event_count();
|
|
||||||
|
|
||||||
input_events.clear();
|
|
||||||
let mut event: MaybeUninit<_> = MaybeUninit::uninit();
|
|
||||||
for i in 0..num_events {
|
|
||||||
assert_eq!(events.get_event(i, event.as_mut_ptr()), kResultOk);
|
|
||||||
let event = event.assume_init();
|
|
||||||
let timing = event.sample_offset as u32;
|
|
||||||
if event.type_ == vst3_sys::vst::EventTypes::kNoteOnEvent as u16 {
|
|
||||||
let event = event.event.note_on;
|
|
||||||
input_events.push_back(NoteEvent::NoteOn {
|
|
||||||
timing,
|
|
||||||
channel: event.channel as u8,
|
|
||||||
note: event.pitch as u8,
|
|
||||||
velocity: (event.velocity * 127.0).round() as u8,
|
|
||||||
});
|
|
||||||
} else if event.type_ == vst3_sys::vst::EventTypes::kNoteOffEvent as u16 {
|
|
||||||
let event = event.event.note_off;
|
|
||||||
input_events.push_back(NoteEvent::NoteOff {
|
|
||||||
timing,
|
|
||||||
channel: event.channel as u8,
|
|
||||||
note: event.pitch as u8,
|
|
||||||
velocity: (event.velocity * 127.0).round() as u8,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's possible the host only wanted to send new parameter values
|
// It's possible the host only wanted to send new parameter values
|
||||||
// TOOD: Send the output events when doing a flush
|
let is_parameter_flush = data.num_outputs == 0;
|
||||||
if data.num_outputs == 0 {
|
if is_parameter_flush {
|
||||||
nih_log!("VST3 parameter flush");
|
nih_log!("VST3 parameter flush");
|
||||||
return kResultOk;
|
} else {
|
||||||
|
check_null_ptr_msg!(
|
||||||
|
"Process output pointer is null",
|
||||||
|
data.outputs,
|
||||||
|
(*data.outputs).buffers,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The setups we suppport are:
|
// The setups we suppport are:
|
||||||
|
@ -732,57 +676,176 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
||||||
);
|
);
|
||||||
nih_debug_assert!(data.num_samples >= 0);
|
nih_debug_assert!(data.num_samples >= 0);
|
||||||
|
|
||||||
let num_output_channels = (*data.outputs).num_channels as usize;
|
// If `P::SAMPLE_ACCURATE_AUTOMATION` is set, then we'll split up the audio buffer into
|
||||||
check_null_ptr_msg!(
|
// chunks whenever a parameter change occurs. Otherwise all parameter changes are
|
||||||
"Process output pointer is null",
|
// handled right here and now.
|
||||||
data.outputs,
|
let mut input_param_changes = self.inner.input_param_changes.borrow_mut();
|
||||||
(*data.outputs).buffers,
|
input_param_changes.clear();
|
||||||
);
|
if let Some(param_changes) = data.input_param_changes.upgrade() {
|
||||||
|
let num_param_queues = param_changes.get_parameter_count();
|
||||||
|
for change_queue_idx in 0..num_param_queues {
|
||||||
|
if let Some(param_change_queue) =
|
||||||
|
param_changes.get_parameter_data(change_queue_idx).upgrade()
|
||||||
|
{
|
||||||
|
let param_hash = param_change_queue.get_parameter_id();
|
||||||
|
let num_changes = param_change_queue.get_point_count();
|
||||||
|
|
||||||
// This vector has been preallocated to contain enough slices as there are output
|
let mut sample_offset = 0i32;
|
||||||
// channels
|
let mut value = 0.0f64;
|
||||||
|
if num_changes > 0
|
||||||
|
&& param_change_queue.get_point(
|
||||||
|
num_changes - 1,
|
||||||
|
&mut sample_offset,
|
||||||
|
&mut value,
|
||||||
|
) == kResultOk
|
||||||
|
{
|
||||||
|
if P::SAMPLE_ACCURATE_AUTOMATION {
|
||||||
|
input_param_changes.push(Reverse((
|
||||||
|
sample_offset as usize,
|
||||||
|
ParameterChange {
|
||||||
|
hash: param_hash,
|
||||||
|
normalized_value: value as f32,
|
||||||
|
},
|
||||||
|
)));
|
||||||
|
} else {
|
||||||
|
self.inner.set_normalized_value_by_hash(
|
||||||
|
param_hash,
|
||||||
|
value as f32,
|
||||||
|
Some(sample_rate),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut block_start = 0;
|
||||||
|
let mut block_end = data.num_samples as usize;
|
||||||
|
let mut event_start_idx = 0;
|
||||||
|
loop {
|
||||||
|
// In sample-accurate automation mode we'll handle any parameter changes for the
|
||||||
|
// current sample, and then process the block between the current sample and the
|
||||||
|
// sample containing the next parameter change, if any. All timings also need to be
|
||||||
|
// compensated for this.
|
||||||
|
if P::SAMPLE_ACCURATE_AUTOMATION {
|
||||||
|
if input_param_changes.is_empty() {
|
||||||
|
block_end = data.num_samples as usize;
|
||||||
|
} else {
|
||||||
|
while let Some(Reverse((sample_idx, _))) = input_param_changes.peek() {
|
||||||
|
if *sample_idx != block_start {
|
||||||
|
block_end = *sample_idx;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Reverse((_, change)) = input_param_changes.pop().unwrap();
|
||||||
|
self.inner.set_normalized_value_by_hash(
|
||||||
|
change.hash,
|
||||||
|
change.normalized_value,
|
||||||
|
Some(sample_rate),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if P::ACCEPTS_MIDI {
|
||||||
|
let mut input_events = self.inner.input_events.borrow_mut();
|
||||||
|
if let Some(events) = data.input_events.upgrade() {
|
||||||
|
let num_events = events.get_event_count();
|
||||||
|
|
||||||
|
input_events.clear();
|
||||||
|
let mut event: MaybeUninit<_> = MaybeUninit::uninit();
|
||||||
|
for i in event_start_idx..num_events {
|
||||||
|
assert_eq!(events.get_event(i, event.as_mut_ptr()), kResultOk);
|
||||||
|
let event = event.assume_init();
|
||||||
|
|
||||||
|
// Make sure to only process the events for this block if we're
|
||||||
|
// splitting the buffer
|
||||||
|
if P::SAMPLE_ACCURATE_AUTOMATION
|
||||||
|
&& event.sample_offset as u32 >= block_end as u32
|
||||||
|
{
|
||||||
|
event_start_idx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let timing = event.sample_offset as u32 - block_start as u32;
|
||||||
|
if event.type_ == vst3_sys::vst::EventTypes::kNoteOnEvent as u16 {
|
||||||
|
let event = event.event.note_on;
|
||||||
|
input_events.push_back(NoteEvent::NoteOn {
|
||||||
|
timing,
|
||||||
|
channel: event.channel as u8,
|
||||||
|
note: event.pitch as u8,
|
||||||
|
velocity: (event.velocity * 127.0).round() as u8,
|
||||||
|
});
|
||||||
|
} else if event.type_ == vst3_sys::vst::EventTypes::kNoteOffEvent as u16
|
||||||
|
{
|
||||||
|
let event = event.event.note_off;
|
||||||
|
input_events.push_back(NoteEvent::NoteOff {
|
||||||
|
timing,
|
||||||
|
channel: event.channel as u8,
|
||||||
|
note: event.pitch as u8,
|
||||||
|
velocity: (event.velocity * 127.0).round() as u8,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = if is_parameter_flush {
|
||||||
|
kResultOk
|
||||||
|
} else {
|
||||||
|
let num_output_channels = (*data.outputs).num_channels as usize;
|
||||||
|
|
||||||
|
// This vector has been preallocated to contain enough slices as there are
|
||||||
|
// output channels
|
||||||
let mut output_buffer = self.inner.output_buffer.borrow_mut();
|
let mut output_buffer = self.inner.output_buffer.borrow_mut();
|
||||||
output_buffer.with_raw_vec(|output_slices| {
|
output_buffer.with_raw_vec(|output_slices| {
|
||||||
nih_debug_assert_eq!(num_output_channels, output_slices.len());
|
nih_debug_assert_eq!(num_output_channels, output_slices.len());
|
||||||
for (output_channel_idx, output_channel_slice) in
|
for (output_channel_idx, output_channel_slice) in
|
||||||
output_slices.iter_mut().enumerate()
|
output_slices.iter_mut().enumerate()
|
||||||
{
|
{
|
||||||
|
// If `P::SAMPLE_ACCURATE_AUTOMATION` is set, then we may be iterating over
|
||||||
|
// the buffer in smaller sections.
|
||||||
// SAFETY: These pointers may not be valid outside of this function even though
|
// SAFETY: These pointers may not be valid outside of this function even though
|
||||||
// their lifetime is equal to this structs. This is still safe because they are
|
// their lifetime is equal to this structs. This is still safe because they are
|
||||||
// only dereferenced here later as part of this process function.
|
// only dereferenced here later as part of this process function.
|
||||||
|
let channel_ptr =
|
||||||
|
*((*data.outputs).buffers as *mut *mut f32).add(output_channel_idx);
|
||||||
*output_channel_slice = std::slice::from_raw_parts_mut(
|
*output_channel_slice = std::slice::from_raw_parts_mut(
|
||||||
*((*data.outputs).buffers as *mut *mut f32).add(output_channel_idx),
|
channel_ptr.add(block_start),
|
||||||
data.num_samples as usize,
|
block_end - block_start,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Most hosts process data in place, in which case we don't need to do any copying
|
// Some hosts process data in place, in which case we don't need to do any
|
||||||
// ourselves. If the pointers do not alias, then we'll do the copy here and then the
|
// copying ourselves. If the pointers do not alias, then we'll do the copy here
|
||||||
// plugin can just do normal in place processing.
|
// and then the plugin can just do normal in place processing.
|
||||||
if !data.inputs.is_null() {
|
if !data.inputs.is_null() {
|
||||||
let num_input_channels = (*data.inputs).num_channels as usize;
|
let num_input_channels = (*data.inputs).num_channels as usize;
|
||||||
nih_debug_assert!(
|
nih_debug_assert!(
|
||||||
num_input_channels <= num_output_channels,
|
num_input_channels <= num_output_channels,
|
||||||
"Stereo to mono and similar configurations are not supported"
|
"Stereo to mono and similar configurations are not supported"
|
||||||
);
|
);
|
||||||
for input_channel_idx in 0..cmp::min(num_input_channels, num_output_channels) {
|
for input_channel_idx in
|
||||||
|
0..cmp::min(num_input_channels, num_output_channels)
|
||||||
|
{
|
||||||
let output_channel_ptr =
|
let output_channel_ptr =
|
||||||
*((*data.outputs).buffers as *mut *mut f32).add(input_channel_idx);
|
*((*data.outputs).buffers as *mut *mut f32).add(input_channel_idx);
|
||||||
let input_channel_ptr =
|
let input_channel_ptr = *((*data.inputs).buffers as *const *const f32)
|
||||||
*((*data.inputs).buffers as *const *const f32).add(input_channel_idx);
|
.add(input_channel_idx);
|
||||||
if input_channel_ptr != output_channel_ptr {
|
if input_channel_ptr != output_channel_ptr {
|
||||||
ptr::copy_nonoverlapping(
|
ptr::copy_nonoverlapping(
|
||||||
input_channel_ptr,
|
input_channel_ptr.add(block_start),
|
||||||
output_channel_ptr,
|
output_channel_ptr.add(block_start),
|
||||||
data.num_samples as usize,
|
block_end - block_start,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some of the fields are left empty because VST3 does not provide this information, but
|
// Some of the fields are left empty because VST3 does not provide this
|
||||||
// the methods on [`Transport`] can reconstruct these values from the other fields
|
// information, but the methods on [`Transport`] can reconstruct these values
|
||||||
|
// from the other fields
|
||||||
let mut transport = Transport::new(sample_rate);
|
let mut transport = Transport::new(sample_rate);
|
||||||
if !data.context.is_null() {
|
if !data.context.is_null() {
|
||||||
let context = &*data.context;
|
let context = &*data.context;
|
||||||
|
@ -800,11 +863,24 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
||||||
transport.time_sig_numerator = Some(context.time_sig_num);
|
transport.time_sig_numerator = Some(context.time_sig_num);
|
||||||
transport.time_sig_denominator = Some(context.time_sig_den);
|
transport.time_sig_denominator = Some(context.time_sig_den);
|
||||||
}
|
}
|
||||||
transport.pos_samples = Some(context.project_time_samples);
|
|
||||||
|
// We need to compensate for the block splitting here
|
||||||
|
transport.pos_samples =
|
||||||
|
Some(context.project_time_samples + block_start as i64);
|
||||||
if context.state & (1 << 9) != 0 {
|
if context.state & (1 << 9) != 0 {
|
||||||
// kProjectTimeMusicValid
|
// kProjectTimeMusicValid
|
||||||
|
if P::SAMPLE_ACCURATE_AUTOMATION && (context.state & (1 << 10) != 0) {
|
||||||
|
// kTempoValid
|
||||||
|
transport.pos_beats = Some(
|
||||||
|
context.project_time_music
|
||||||
|
+ (block_start as f64 / sample_rate as f64 / 60.0
|
||||||
|
* context.tempo),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
transport.pos_beats = Some(context.project_time_music);
|
transport.pos_beats = Some(context.project_time_music);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if context.state & (1 << 11) != 0 {
|
if context.state & (1 << 11) != 0 {
|
||||||
// kBarPositionValid
|
// kBarPositionValid
|
||||||
transport.bar_start_pos_beats = Some(context.bar_position_music);
|
transport.bar_start_pos_beats = Some(context.bar_position_music);
|
||||||
|
@ -818,14 +894,28 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
||||||
|
|
||||||
let mut plugin = self.inner.plugin.write();
|
let mut plugin = self.inner.plugin.write();
|
||||||
let mut context = self.inner.make_process_context(transport);
|
let mut context = self.inner.make_process_context(transport);
|
||||||
match plugin.process(&mut output_buffer, &mut context) {
|
|
||||||
|
let result = plugin.process(&mut output_buffer, &mut context);
|
||||||
|
self.inner.last_process_status.store(result);
|
||||||
|
match result {
|
||||||
ProcessStatus::Error(err) => {
|
ProcessStatus::Error(err) => {
|
||||||
nih_debug_assert_failure!("Process error: {}", err);
|
nih_debug_assert_failure!("Process error: {}", err);
|
||||||
|
|
||||||
kResultFalse
|
return kResultFalse;
|
||||||
}
|
}
|
||||||
_ => kResultOk,
|
_ => kResultOk,
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If our block ends at the end of the buffer then that means there are no more
|
||||||
|
// unprocessed (parameter) events. If there are more events, we'll just keep going
|
||||||
|
// through this process until we've processed the entire buffer.
|
||||||
|
if block_end as i32 == data.num_samples {
|
||||||
|
break result;
|
||||||
|
} else {
|
||||||
|
block_start = block_end;
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue