1
0
Fork 0

Support CLAP transport information events

This will cause the buffer to be split when the transport information
changes.
This commit is contained in:
Robbert van der Helm 2022-05-05 17:23:46 +02:00
parent 7cb671319e
commit a74d8264fb

View file

@ -6,13 +6,14 @@ use atomic_float::AtomicF32;
use atomic_refcell::{AtomicRefCell, AtomicRefMut}; use atomic_refcell::{AtomicRefCell, AtomicRefMut};
use clap_sys::events::{ use clap_sys::events::{
clap_event_header, clap_event_midi, clap_event_note, clap_event_note_expression, clap_event_header, clap_event_midi, clap_event_note, clap_event_note_expression,
clap_event_param_gesture, clap_event_param_mod, clap_event_param_value, clap_event_type, clap_event_param_gesture, clap_event_param_mod, clap_event_param_value, clap_event_transport,
clap_input_events, clap_output_events, CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_IS_LIVE, clap_event_type, clap_input_events, clap_output_events, CLAP_CORE_EVENT_SPACE_ID,
CLAP_EVENT_MIDI, CLAP_EVENT_NOTE_EXPRESSION, CLAP_EVENT_NOTE_OFF, CLAP_EVENT_NOTE_ON, CLAP_EVENT_IS_LIVE, CLAP_EVENT_MIDI, CLAP_EVENT_NOTE_EXPRESSION, CLAP_EVENT_NOTE_OFF,
CLAP_EVENT_PARAM_GESTURE_BEGIN, CLAP_EVENT_PARAM_GESTURE_END, CLAP_EVENT_PARAM_MOD, CLAP_EVENT_NOTE_ON, CLAP_EVENT_PARAM_GESTURE_BEGIN, CLAP_EVENT_PARAM_GESTURE_END,
CLAP_EVENT_PARAM_VALUE, CLAP_NOTE_EXPRESSION_BRIGHTNESS, CLAP_NOTE_EXPRESSION_EXPRESSION, CLAP_EVENT_PARAM_MOD, CLAP_EVENT_PARAM_VALUE, CLAP_EVENT_TRANSPORT,
CLAP_NOTE_EXPRESSION_PAN, CLAP_NOTE_EXPRESSION_PRESSURE, CLAP_NOTE_EXPRESSION_TUNING, CLAP_NOTE_EXPRESSION_BRIGHTNESS, CLAP_NOTE_EXPRESSION_EXPRESSION, CLAP_NOTE_EXPRESSION_PAN,
CLAP_NOTE_EXPRESSION_VIBRATO, CLAP_NOTE_EXPRESSION_VOLUME, CLAP_TRANSPORT_HAS_BEATS_TIMELINE, CLAP_NOTE_EXPRESSION_PRESSURE, CLAP_NOTE_EXPRESSION_TUNING, CLAP_NOTE_EXPRESSION_VIBRATO,
CLAP_NOTE_EXPRESSION_VOLUME, CLAP_TRANSPORT_HAS_BEATS_TIMELINE,
CLAP_TRANSPORT_HAS_SECONDS_TIMELINE, CLAP_TRANSPORT_HAS_TEMPO, CLAP_TRANSPORT_HAS_SECONDS_TIMELINE, CLAP_TRANSPORT_HAS_TEMPO,
CLAP_TRANSPORT_HAS_TIME_SIGNATURE, CLAP_TRANSPORT_IS_LOOP_ACTIVE, CLAP_TRANSPORT_IS_PLAYING, CLAP_TRANSPORT_HAS_TIME_SIGNATURE, CLAP_TRANSPORT_IS_LOOP_ACTIVE, CLAP_TRANSPORT_IS_PLAYING,
CLAP_TRANSPORT_IS_RECORDING, CLAP_TRANSPORT_IS_WITHIN_PRE_ROLL, CLAP_TRANSPORT_IS_RECORDING, CLAP_TRANSPORT_IS_WITHIN_PRE_ROLL,
@ -706,7 +707,7 @@ impl<P: ClapPlugin> Wrapper<P> {
for event_idx in 0..num_events { for event_idx in 0..num_events {
let event = ((*in_).get)(&*in_, event_idx); let event = ((*in_).get)(&*in_, event_idx);
parameter_values_changed |= parameter_values_changed |=
self.handle_in_event(event, &mut input_events, current_sample_idx); self.handle_in_event(event, &mut input_events, None, current_sample_idx);
} }
// Allow the GUI to react to any parameter values that might have been changed // Allow the GUI to react to any parameter values that might have been changed
@ -715,17 +716,22 @@ impl<P: ClapPlugin> Wrapper<P> {
} }
} }
/// Similar to [`handle_in_events()`][Self::handle_in_events()], but will stop just before the /// Similar to [`handle_in_events()`][Self::handle_in_events()], but will stop just before an
/// next parameter change event with `raw_event.time > current_sample_idx` and return the /// event if the preducate returns true for that events. This predicate is only called for
/// **absolute** (relative to the entire buffer that's being split) sample index of that event /// events that occur after `current_sample_idx`. This is used to stop before a tempo or time
/// along with the its index in the event queue as a `(sample_idx, event_idx)` tuple. This /// signature change, or before next parameter change event with `raw_event.time >
/// allows for splitting the audio buffer into segments with distinct sample values to enable /// current_sample_idx` and return the **absolute** (relative to the entire buffer that's being
/// sample accurate automation without modifcations to the wrapped plugin. /// split) sample index of that event along with the its index in the event queue as a
pub unsafe fn handle_in_events_until_next_param_change( /// `(sample_idx, event_idx)` tuple. This allows for splitting the audio buffer into segments
/// with distinct sample values to enable sample accurate automation without modifcations to the
/// wrapped plugin.
pub unsafe fn handle_in_events_until(
&self, &self,
in_: &clap_input_events, in_: &clap_input_events,
transport_info: &mut *const clap_event_transport,
current_sample_idx: usize, current_sample_idx: usize,
resume_from_event_idx: usize, resume_from_event_idx: usize,
stop_predicate: impl Fn(*const clap_event_header) -> bool,
) -> Option<(usize, usize)> { ) -> Option<(usize, usize)> {
let mut input_events = self.input_events.borrow_mut(); let mut input_events = self.input_events.borrow_mut();
input_events.clear(); input_events.clear();
@ -740,27 +746,30 @@ impl<P: ClapPlugin> Wrapper<P> {
let mut event: *const clap_event_header = ((*in_).get)(&*in_, start_idx); let mut event: *const clap_event_header = ((*in_).get)(&*in_, start_idx);
let mut parameter_values_changed = false; let mut parameter_values_changed = false;
for next_event_idx in (start_idx + 1)..num_events { for next_event_idx in (start_idx + 1)..num_events {
parameter_values_changed |= parameter_values_changed |= self.handle_in_event(
self.handle_in_event(event, &mut input_events, current_sample_idx); event,
&mut input_events,
Some(transport_info),
current_sample_idx,
);
// Stop just before the next parameter change event at a sample after the current sample // Stop just before the next parameter change or transport information event at a sample
// after the current sample
let next_event: *const clap_event_header = ((*in_).get)(&*in_, next_event_idx); let next_event: *const clap_event_header = ((*in_).get)(&*in_, next_event_idx);
match ((*next_event).space_id, (*next_event).type_) { if (*next_event).time > current_sample_idx as u32 && stop_predicate(next_event) {
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_VALUE)
| (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_MOD)
if (*next_event).time > current_sample_idx as u32 =>
{
return Some(((*next_event).time as usize, next_event_idx as usize)); return Some(((*next_event).time as usize, next_event_idx as usize));
} }
_ => (),
}
event = next_event; event = next_event;
} }
// Don't forget about the last event // Don't forget about the last event
parameter_values_changed |= parameter_values_changed |= self.handle_in_event(
self.handle_in_event(event, &mut input_events, current_sample_idx); event,
&mut input_events,
Some(transport_info),
current_sample_idx,
);
// NOTE: We explicitly did not do this on a block split because that seems a bit excessive. // NOTE: We explicitly did not do this on a block split because that seems a bit excessive.
// When we're performing a block split we're guarenteed that there's still at least one more // When we're performing a block split we're guarenteed that there's still at least one more
@ -1154,6 +1163,9 @@ impl<P: ClapPlugin> Wrapper<P> {
/// To save on mutex operations when handing MIDI events, the lock guard for the input events /// To save on mutex operations when handing MIDI events, the lock guard for the input events
/// need to be passed into this function. /// need to be passed into this function.
/// ///
/// If the event was a transport event and the `transport_info` argument is not `None`, then the
/// pointer will be changed to point to the transport information from this event.
///
/// The return value indicates whether this was a parameter event. If it is a parameter event, /// The return value indicates whether this was a parameter event. If it is a parameter event,
/// then [`notify_param_values_changed()`][Self::notify_param_values_changed()] should be called /// then [`notify_param_values_changed()`][Self::notify_param_values_changed()] should be called
/// once all of these events have been processed. /// once all of these events have been processed.
@ -1162,6 +1174,7 @@ impl<P: ClapPlugin> Wrapper<P> {
&self, &self,
event: *const clap_event_header, event: *const clap_event_header,
input_events: &mut AtomicRefMut<VecDeque<NoteEvent>>, input_events: &mut AtomicRefMut<VecDeque<NoteEvent>>,
transport_info: Option<&mut *const clap_event_transport>,
current_sample_idx: usize, current_sample_idx: usize,
) -> bool { ) -> bool {
let raw_event = &*event; let raw_event = &*event;
@ -1186,6 +1199,14 @@ impl<P: ClapPlugin> Wrapper<P> {
true true
} }
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_TRANSPORT) => {
let event = &*(event as *const clap_event_transport);
if let Some(transport_info) = transport_info {
*transport_info = event;
}
false
}
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_ON) => { (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_ON) => {
if P::MIDI_INPUT >= MidiConfig::Basic { if P::MIDI_INPUT >= MidiConfig::Basic {
let event = &*(event as *const clap_event_note); let event = &*(event as *const clap_event_note);
@ -1567,18 +1588,43 @@ impl<P: ClapPlugin> Wrapper<P> {
let mut block_start = 0; let mut block_start = 0;
let mut block_end = process.frames_count as usize; let mut block_end = process.frames_count as usize;
let mut event_start_idx = 0; let mut event_start_idx = 0;
// The host may send new transport information as an event. In that case we'll also
// split the buffer.
let mut transport_info = process.transport;
let result = loop { let result = loop {
if !process.in_events.is_null() { if !process.in_events.is_null() {
if P::SAMPLE_ACCURATE_AUTOMATION { let split_result = wrapper.handle_in_events_until(
let split_result = wrapper.handle_in_events_until_next_param_change(
&*process.in_events, &*process.in_events,
&mut transport_info,
block_start, block_start,
event_start_idx, event_start_idx,
|next_event| {
// Always split the buffer on transport information changes (tempo, time
// signature, or position changes), and also split on parameter value
// changes after the current sample if sample accurate automation is
// enabled
if P::SAMPLE_ACCURATE_AUTOMATION {
matches!(
((*next_event).space_id, (*next_event).type_,),
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_VALUE)
| (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_MOD)
| (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_TRANSPORT)
)
} else {
matches!(
((*next_event).space_id, (*next_event).type_,),
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_TRANSPORT)
)
}
},
); );
// If there are any parameter changes after `block_start`, then we'll do a // If there are any parameter changes after `block_start` and sample
// new block just after that. Otherwise we can process all audio until the // accurate automatoin is enabled or the host sends new transport
// end of the buffer. // inforamtion, then we'll process a new block just after that. Otherwise we can
// process all audio until the end of the buffer.
match split_result { match split_result {
Some((next_param_change_sample_idx, next_param_change_event_idx)) => { Some((next_param_change_sample_idx, next_param_change_event_idx)) => {
block_end = next_param_change_sample_idx as usize; block_end = next_param_change_sample_idx as usize;
@ -1586,9 +1632,6 @@ impl<P: ClapPlugin> Wrapper<P> {
} }
None => block_end = process.frames_count as usize, None => block_end = process.frames_count as usize,
} }
} else {
wrapper.handle_in_events(&*process.in_events, block_start);
}
} }
// The setups we suppport are: // The setups we suppport are:
@ -1686,8 +1729,8 @@ impl<P: ClapPlugin> Wrapper<P> {
.expect("Process call without prior initialization call") .expect("Process call without prior initialization call")
.sample_rate; .sample_rate;
let mut transport = Transport::new(sample_rate); let mut transport = Transport::new(sample_rate);
if !process.transport.is_null() { if !transport_info.is_null() {
let context = &*process.transport; let context = &*transport_info;
transport.playing = context.flags & CLAP_TRANSPORT_IS_PLAYING != 0; transport.playing = context.flags & CLAP_TRANSPORT_IS_PLAYING != 0;
transport.recording = context.flags & CLAP_TRANSPORT_IS_RECORDING != 0; transport.recording = context.flags & CLAP_TRANSPORT_IS_RECORDING != 0;
@ -2083,7 +2126,8 @@ impl<P: ClapPlugin> Wrapper<P> {
) -> bool { ) -> bool {
match (space_id, event_type) { match (space_id, event_type) {
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_VALUE) (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_VALUE)
| (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_MOD) => true, | (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_MOD)
| (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_TRANSPORT) => true,
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_ON) (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_ON)
| (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_OFF) | (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_OFF)
| (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_EXPRESSION) | (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_EXPRESSION)