Clamp event timings for CLAP plugins
This commit is contained in:
parent
5cb826725d
commit
8d2d293b49
|
@ -39,8 +39,8 @@ pub enum MidiConfig {
|
||||||
/// [`Plugin::MIDI_INPUT`][crate::prelude::Plugin::MIDI_INPUT]. Also check out the
|
/// [`Plugin::MIDI_INPUT`][crate::prelude::Plugin::MIDI_INPUT]. Also check out the
|
||||||
/// [`util`][crate::util] module for convenient conversion functions.
|
/// [`util`][crate::util] module for convenient conversion functions.
|
||||||
///
|
///
|
||||||
/// All of the timings are sample offsets within the current buffer. All sample, channel and note
|
/// All of the timings are sample offsets within the current buffer. Out of bounds timings are
|
||||||
/// numbers are zero-indexed.
|
/// clamped to the current buffer's length. All sample, channel and note numbers are zero-indexed.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum NoteEvent<S: SysExMessage> {
|
pub enum NoteEvent<S: SysExMessage> {
|
||||||
|
|
|
@ -914,14 +914,25 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle all incoming events from an event queue. This will clear `self.input_events` first.
|
/// Handle all incoming events from an event queue. This will clear `self.input_events` first.
|
||||||
pub unsafe fn handle_in_events(&self, in_: &clap_input_events, current_sample_idx: usize) {
|
pub unsafe fn handle_in_events(
|
||||||
|
&self,
|
||||||
|
in_: &clap_input_events,
|
||||||
|
current_sample_idx: usize,
|
||||||
|
total_buffer_len: 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();
|
||||||
|
|
||||||
let num_events = clap_call! { in_=>size(in_) };
|
let num_events = clap_call! { in_=>size(in_) };
|
||||||
for event_idx in 0..num_events {
|
for event_idx in 0..num_events {
|
||||||
let event = clap_call! { in_=>get(in_, event_idx) };
|
let event = clap_call! { in_=>get(in_, event_idx) };
|
||||||
self.handle_in_event(event, &mut input_events, None, current_sample_idx);
|
self.handle_in_event(
|
||||||
|
event,
|
||||||
|
&mut input_events,
|
||||||
|
None,
|
||||||
|
current_sample_idx,
|
||||||
|
total_buffer_len,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -939,6 +950,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
in_: &clap_input_events,
|
in_: &clap_input_events,
|
||||||
transport_info: &mut *const clap_event_transport,
|
transport_info: &mut *const clap_event_transport,
|
||||||
current_sample_idx: usize,
|
current_sample_idx: usize,
|
||||||
|
total_buffer_len: usize,
|
||||||
resume_from_event_idx: usize,
|
resume_from_event_idx: usize,
|
||||||
stop_predicate: impl Fn(*const clap_event_header) -> bool,
|
stop_predicate: impl Fn(*const clap_event_header) -> bool,
|
||||||
) -> Option<(usize, usize)> {
|
) -> Option<(usize, usize)> {
|
||||||
|
@ -959,6 +971,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
&mut input_events,
|
&mut input_events,
|
||||||
Some(transport_info),
|
Some(transport_info),
|
||||||
current_sample_idx,
|
current_sample_idx,
|
||||||
|
total_buffer_len,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Stop just before the next parameter change or transport information event at a sample
|
// Stop just before the next parameter change or transport information event at a sample
|
||||||
|
@ -977,6 +990,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
&mut input_events,
|
&mut input_events,
|
||||||
Some(transport_info),
|
Some(transport_info),
|
||||||
current_sample_idx,
|
current_sample_idx,
|
||||||
|
total_buffer_len,
|
||||||
);
|
);
|
||||||
|
|
||||||
None
|
None
|
||||||
|
@ -986,7 +1000,14 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
/// used as part of splitting up the input buffer for sample accurate automation changes. This
|
/// used as part of splitting up the input buffer for sample accurate automation changes. This
|
||||||
/// will also modify the actual parameter values, since we should only do that while the wrapped
|
/// will also modify the actual parameter values, since we should only do that while the wrapped
|
||||||
/// plugin is not actually processing audio.
|
/// plugin is not actually processing audio.
|
||||||
pub unsafe fn handle_out_events(&self, out: &clap_output_events, current_sample_idx: usize) {
|
///
|
||||||
|
/// The `total_buffer_len` argument is used to clamp out of bounds events to the buffer's length.
|
||||||
|
pub unsafe fn handle_out_events(
|
||||||
|
&self,
|
||||||
|
out: &clap_output_events,
|
||||||
|
current_sample_idx: usize,
|
||||||
|
total_buffer_len: usize,
|
||||||
|
) {
|
||||||
// We'll always write these events to the first sample, so even when we add note output we
|
// 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
|
// shouldn't have to think about interleaving events here
|
||||||
let sample_rate = self.current_buffer_config.load().map(|c| c.sample_rate);
|
let sample_rate = self.current_buffer_config.load().map(|c| c.sample_rate);
|
||||||
|
@ -1057,7 +1078,13 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
// Also send all note events generated by the plugin
|
// Also send all note events generated by the plugin
|
||||||
let mut output_events = self.output_events.borrow_mut();
|
let mut output_events = self.output_events.borrow_mut();
|
||||||
while let Some(event) = output_events.pop_front() {
|
while let Some(event) = output_events.pop_front() {
|
||||||
|
// Out of bounds events are clamped to the buffer's size
|
||||||
let time = event.timing() + current_sample_idx as u32;
|
let time = event.timing() + current_sample_idx as u32;
|
||||||
|
nih_debug_assert!(
|
||||||
|
time < total_buffer_len as u32,
|
||||||
|
"Output event is out of bounds, will be clamped to the buffer's size"
|
||||||
|
);
|
||||||
|
let time = time.min(total_buffer_len as u32 - 1);
|
||||||
|
|
||||||
let push_successful = match event {
|
let push_successful = match event {
|
||||||
NoteEvent::NoteOn {
|
NoteEvent::NoteOn {
|
||||||
|
@ -1391,9 +1418,17 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
input_events: &mut AtomicRefMut<VecDeque<PluginNoteEvent<P>>>,
|
input_events: &mut AtomicRefMut<VecDeque<PluginNoteEvent<P>>>,
|
||||||
transport_info: Option<&mut *const clap_event_transport>,
|
transport_info: Option<&mut *const clap_event_transport>,
|
||||||
current_sample_idx: usize,
|
current_sample_idx: usize,
|
||||||
|
total_buffer_len: usize,
|
||||||
) {
|
) {
|
||||||
let raw_event = &*event;
|
let raw_event = &*event;
|
||||||
|
|
||||||
|
// Out of bounds events are clamped to the buffer's size
|
||||||
let timing = raw_event.time - current_sample_idx as u32;
|
let timing = raw_event.time - current_sample_idx as u32;
|
||||||
|
nih_debug_assert!(
|
||||||
|
timing < total_buffer_len as u32,
|
||||||
|
"Input event is out of bounds, will be clamped to the buffer's size"
|
||||||
|
);
|
||||||
|
let timing = timing.min(total_buffer_len as u32 - 1);
|
||||||
|
|
||||||
match (raw_event.space_id, raw_event.type_) {
|
match (raw_event.space_id, raw_event.type_) {
|
||||||
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_VALUE) => {
|
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_VALUE) => {
|
||||||
|
@ -1622,8 +1657,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
// messages to stay consistent with the VST3 wrapper.
|
// messages to stay consistent with the VST3 wrapper.
|
||||||
let event = &*(event as *const clap_event_midi);
|
let event = &*(event as *const clap_event_midi);
|
||||||
|
|
||||||
match NoteEvent::from_midi(raw_event.time - current_sample_idx as u32, &event.data)
|
match NoteEvent::from_midi(timing, &event.data) {
|
||||||
{
|
|
||||||
Ok(
|
Ok(
|
||||||
note_event @ (NoteEvent::NoteOn { .. }
|
note_event @ (NoteEvent::NoteOn { .. }
|
||||||
| NoteEvent::NoteOff { .. }
|
| NoteEvent::NoteOff { .. }
|
||||||
|
@ -1647,9 +1681,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
// necessarily an error
|
// necessarily an error
|
||||||
assert!(!event.buffer.is_null());
|
assert!(!event.buffer.is_null());
|
||||||
let sysex_buffer = std::slice::from_raw_parts(event.buffer, event.size as usize);
|
let sysex_buffer = std::slice::from_raw_parts(event.buffer, event.size as usize);
|
||||||
if let Ok(note_event) =
|
if let Ok(note_event) = NoteEvent::from_midi(timing, sysex_buffer) {
|
||||||
NoteEvent::from_midi(raw_event.time - current_sample_idx as u32, sysex_buffer)
|
|
||||||
{
|
|
||||||
input_events.push_back(note_event);
|
input_events.push_back(note_event);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1946,6 +1978,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
// accuration automation yet and there's no way to get the last event for a parameter,
|
// accuration automation yet and there's no way to get the last event for a parameter,
|
||||||
// we'll process every incoming event.
|
// we'll process every incoming event.
|
||||||
let process = &*process;
|
let process = &*process;
|
||||||
|
let total_buffer_len = process.frames_count as usize;
|
||||||
|
|
||||||
// Before doing anything, clear out any auxiliary outputs since they may contain
|
// Before doing anything, clear out any auxiliary outputs since they may contain
|
||||||
// uninitialized data when the host assumes that we'll always write something there
|
// uninitialized data when the host assumes that we'll always write something there
|
||||||
|
@ -1962,7 +1995,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
ptr::write_bytes(
|
ptr::write_bytes(
|
||||||
*((*host_output).data32.offset(channel_idx)) as *mut f32,
|
*((*host_output).data32.offset(channel_idx)) as *mut f32,
|
||||||
0,
|
0,
|
||||||
process.frames_count as usize,
|
total_buffer_len,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1972,7 +2005,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
// If `P::SAMPLE_ACCURATE_AUTOMATION` is set, then we'll split up the audio buffer into
|
// If `P::SAMPLE_ACCURATE_AUTOMATION` is set, then we'll split up the audio buffer into
|
||||||
// chunks whenever a parameter change occurs
|
// chunks whenever a parameter change occurs
|
||||||
let mut block_start = 0;
|
let mut block_start = 0;
|
||||||
let mut block_end = process.frames_count as usize;
|
let mut block_end = total_buffer_len;
|
||||||
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
|
// The host may send new transport information as an event. In that case we'll also
|
||||||
|
@ -1985,6 +2018,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
&*process.in_events,
|
&*process.in_events,
|
||||||
&mut transport_info,
|
&mut transport_info,
|
||||||
block_start,
|
block_start,
|
||||||
|
total_buffer_len,
|
||||||
event_start_idx,
|
event_start_idx,
|
||||||
|next_event| {
|
|next_event| {
|
||||||
// Always split the buffer on transport information changes (tempo, time
|
// Always split the buffer on transport information changes (tempo, time
|
||||||
|
@ -2026,7 +2060,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
block_end = next_param_change_sample_idx;
|
block_end = next_param_change_sample_idx;
|
||||||
event_start_idx = next_param_change_event_idx;
|
event_start_idx = next_param_change_event_idx;
|
||||||
}
|
}
|
||||||
None => block_end = process.frames_count as usize,
|
None => block_end = total_buffer_len,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2336,13 +2370,13 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
// After processing audio, send all spooled events to the host. This include note
|
// After processing audio, send all spooled events to the host. This include note
|
||||||
// events.
|
// events.
|
||||||
if !process.out_events.is_null() {
|
if !process.out_events.is_null() {
|
||||||
wrapper.handle_out_events(&*process.out_events, block_start);
|
wrapper.handle_out_events(&*process.out_events, block_start, total_buffer_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If our block ends at the end of the buffer then that means there are no more
|
// 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
|
// unprocessed (parameter) events. If there are more events, we'll just keep going
|
||||||
// through this process until we've processed the entire buffer.
|
// through this process until we've processed the entire buffer.
|
||||||
if block_end as u32 == process.frames_count {
|
if block_end == total_buffer_len {
|
||||||
break clap_result;
|
break clap_result;
|
||||||
} else {
|
} else {
|
||||||
block_start = block_end;
|
block_start = block_end;
|
||||||
|
@ -3153,11 +3187,11 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
let wrapper = &*((*plugin).plugin_data as *const Self);
|
let wrapper = &*((*plugin).plugin_data as *const Self);
|
||||||
|
|
||||||
if !in_.is_null() {
|
if !in_.is_null() {
|
||||||
wrapper.handle_in_events(&*in_, 0);
|
wrapper.handle_in_events(&*in_, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !out.is_null() {
|
if !out.is_null() {
|
||||||
wrapper.handle_out_events(&*out, 0);
|
wrapper.handle_out_events(&*out, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue