1
0
Fork 0

Explicitly handle 0 channel IO

Instead of treating it as a flush. This is needed for note effect
plugins.
This commit is contained in:
Robbert van der Helm 2022-04-11 20:34:42 +02:00
parent 7ce86cc788
commit 5e486ab3d9
2 changed files with 268 additions and 282 deletions

View file

@ -1547,14 +1547,6 @@ impl<P: ClapPlugin> Wrapper<P> {
// we'll process every incoming event. // we'll process every incoming event.
let process = &*process; let process = &*process;
// I don't think this is a thing for CLAP since there's a dedicated flush function, but
// might as well protect against this
// TOOD: Send the output events when doing a flush
if process.audio_outputs_count == 0 || process.frames_count == 0 {
nih_log!("CLAP process call event flush");
return CLAP_PROCESS_CONTINUE;
}
// 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;
@ -1588,21 +1580,16 @@ impl<P: ClapPlugin> Wrapper<P> {
// - 1 input bus // - 1 input bus
// - 1 output bus // - 1 output bus
// - 1 input bus and 1 output bus // - 1 input bus and 1 output bus
// - 1 input bus and 1 output bus
//
// Depending on the host either of these may also be missing if the number of
// channels is set to 0.
nih_debug_assert!( nih_debug_assert!(
process.audio_inputs_count <= 1 && process.audio_outputs_count <= 1, process.audio_inputs_count <= 1 && process.audio_outputs_count <= 1,
"The host provides more than one input or output bus" "The host provides more than one input or output bus"
); );
// Right now we don't handle any auxiliary outputs // Right now we don't handle any auxiliary outputs
check_null_ptr_msg!(
"Null pointers passed for audio outputs in process function",
CLAP_PROCESS_ERROR,
process.audio_outputs,
(*process.audio_outputs).data32
);
let audio_outputs = &*process.audio_outputs;
let num_output_channels = audio_outputs.channel_count as usize;
// This vector has been preallocated to contain enough slices as there are output // This vector has been preallocated to contain enough slices as there are output
// channels // channels
// TODO: The audio buffers have a latency field, should we use those? // TODO: The audio buffers have a latency field, should we use those?
@ -1610,30 +1597,43 @@ impl<P: ClapPlugin> Wrapper<P> {
// flags? // flags?
let mut output_buffer = wrapper.output_buffer.borrow_mut(); let mut output_buffer = wrapper.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()); if !process.audio_outputs.is_null()
for (output_channel_idx, output_channel_slice) in && !(*process.audio_outputs).data32.is_null()
output_slices.iter_mut().enumerate()
{ {
// If `P::SAMPLE_ACCURATE_AUTOMATION` is set, then we may be iterating over let audio_outputs = &*process.audio_outputs;
// the buffer in smaller sections. let num_output_channels = audio_outputs.channel_count as usize;
// SAFETY: These pointers may not be valid outside of this function even though nih_debug_assert_eq!(num_output_channels, output_slices.len());
// their lifetime is equal to this structs. This is still safe because they are
// only dereferenced here later as part of this process function. for (output_channel_idx, output_channel_slice) in
let channel_ptr = output_slices.iter_mut().enumerate()
*(audio_outputs.data32 as *mut *mut f32).add(output_channel_idx); {
*output_channel_slice = std::slice::from_raw_parts_mut( // If `P::SAMPLE_ACCURATE_AUTOMATION` is set, then we may be iterating over
channel_ptr.add(block_start), // the buffer in smaller sections.
block_end - block_start, // 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
// only dereferenced here later as part of this process function.
let channel_ptr =
*(audio_outputs.data32 as *mut *mut f32).add(output_channel_idx);
*output_channel_slice = std::slice::from_raw_parts_mut(
channel_ptr.add(block_start),
block_end - block_start,
);
}
} }
}); });
// Some 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_outputs.is_null()
&& !(*process.audio_outputs).data32.is_null()
&& !process.audio_inputs.is_null()
&& !(*process.audio_inputs).data32.is_null()
{
// We currently don't support sidechain inputs // We currently don't support sidechain inputs
let audio_outputs = &*process.audio_outputs;
let audio_inputs = &*process.audio_inputs; let audio_inputs = &*process.audio_inputs;
let num_output_channels = audio_outputs.channel_count as usize;
let num_input_channels = audio_inputs.channel_count as usize; let num_input_channels = audio_inputs.channel_count as usize;
nih_debug_assert!( nih_debug_assert!(
num_input_channels <= num_output_channels, num_input_channels <= num_output_channels,

View file

@ -697,22 +697,13 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
.expect("Process call without prior setup call") .expect("Process call without prior setup call")
.sample_rate; .sample_rate;
// It's possible the host only wanted to send new parameter values
let is_parameter_flush = data.num_outputs == 0;
if is_parameter_flush {
nih_log!("VST3 parameter flush");
} 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:
// - 1 input bus // - 1 input bus
// - 1 output bus // - 1 output bus
// - 1 input bus and 1 output bus // - 1 input bus and 1 output bus
//
// Depending on the host either of these may also be missing if the number of channels
// is set to 0.
nih_debug_assert!( nih_debug_assert!(
data.num_inputs >= 0 data.num_inputs >= 0
&& data.num_inputs <= 1 && data.num_inputs <= 1
@ -956,24 +947,23 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
parameter_values_changed = false; parameter_values_changed = false;
} }
let result = if is_parameter_flush { // This vector has been preallocated to contain enough slices as there are
kResultOk // output channels
} else { let mut output_buffer = self.inner.output_buffer.borrow_mut();
let num_output_channels = (*data.outputs).num_channels as usize; output_buffer.with_raw_vec(|output_slices| {
if !data.outputs.is_null() {
// This vector has been preallocated to contain enough slices as there are let num_output_channels = (*data.outputs).num_channels as usize;
// output channels
let mut output_buffer = self.inner.output_buffer.borrow_mut();
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 // If `P::SAMPLE_ACCURATE_AUTOMATION` is set, then we may be iterating
// the buffer in smaller sections. // 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
// their lifetime is equal to this structs. This is still safe because they are // though their lifetime is equal to this structs. This is still safe
// only dereferenced here later as part of this process function. // because they are only dereferenced here later as part of this process
// function.
let channel_ptr = let channel_ptr =
*((*data.outputs).buffers as *mut *mut f32).add(output_channel_idx); *((*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(
@ -981,249 +971,245 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
block_end - block_start, block_end - block_start,
); );
} }
}); }
});
// Some hosts process data in place, in which case we don't need to do any // Some hosts process data in place, in which case we don't need to do any copying
// copying ourselves. If the pointers do not alias, then we'll do the copy here // ourselves. If the pointers do not alias, then we'll do the copy here and then the
// and then the plugin can just do normal in place processing. // plugin can just do normal in place processing.
if !data.inputs.is_null() { if !data.outputs.is_null() && !data.inputs.is_null() {
let num_input_channels = (*data.inputs).num_channels as usize; let num_output_channels = (*data.outputs).num_channels as usize;
nih_debug_assert!( let num_input_channels = (*data.inputs).num_channels as usize;
num_input_channels <= num_output_channels, nih_debug_assert!(
"Stereo to mono and similar configurations are not supported" num_input_channels <= num_output_channels,
); "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 =
*((*data.outputs).buffers as *mut *mut f32).add(input_channel_idx);
let input_channel_ptr =
*((*data.inputs).buffers as *const *const f32).add(input_channel_idx);
if input_channel_ptr != output_channel_ptr {
ptr::copy_nonoverlapping(
input_channel_ptr.add(block_start),
output_channel_ptr.add(block_start),
block_end - block_start,
);
}
}
}
// Some of the fields are left empty because VST3 does not provide this
// information, but the methods on [`Transport`] can reconstruct these values
// from the other fields
let mut transport = Transport::new(sample_rate);
if !data.context.is_null() {
let context = &*data.context;
// These constants are missing from vst3-sys, see:
// https://steinbergmedia.github.io/vst3_doc/vstinterfaces/structSteinberg_1_1Vst_1_1ProcessContext.html
transport.playing = context.state & (1 << 1) != 0; // kPlaying
transport.recording = context.state & (1 << 3) != 0; // kRecording
if context.state & (1 << 10) != 0 {
// kTempoValid
transport.tempo = Some(context.tempo);
}
if context.state & (1 << 13) != 0 {
// kTimeSigValid
transport.time_sig_numerator = Some(context.time_sig_num);
transport.time_sig_denominator = Some(context.time_sig_den);
}
// 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 {
// kProjectTimeMusicValid
if P::SAMPLE_ACCURATE_AUTOMATION
&& block_start > 0
&& (context.state & (1 << 10) != 0)
{ {
let output_channel_ptr =
*((*data.outputs).buffers as *mut *mut f32).add(input_channel_idx);
let input_channel_ptr = *((*data.inputs).buffers as *const *const f32)
.add(input_channel_idx);
if input_channel_ptr != output_channel_ptr {
ptr::copy_nonoverlapping(
input_channel_ptr.add(block_start),
output_channel_ptr.add(block_start),
block_end - block_start,
);
}
}
}
// Some of the fields are left empty because VST3 does not provide this
// information, but the methods on [`Transport`] can reconstruct these values
// from the other fields
let mut transport = Transport::new(sample_rate);
if !data.context.is_null() {
let context = &*data.context;
// These constants are missing from vst3-sys, see:
// https://steinbergmedia.github.io/vst3_doc/vstinterfaces/structSteinberg_1_1Vst_1_1ProcessContext.html
transport.playing = context.state & (1 << 1) != 0; // kPlaying
transport.recording = context.state & (1 << 3) != 0; // kRecording
if context.state & (1 << 10) != 0 {
// kTempoValid // kTempoValid
transport.tempo = Some(context.tempo); transport.pos_beats = Some(
} context.project_time_music
if context.state & (1 << 13) != 0 { + (block_start as f64 / sample_rate as f64 / 60.0
// kTimeSigValid * context.tempo),
transport.time_sig_numerator = Some(context.time_sig_num); );
transport.time_sig_denominator = Some(context.time_sig_den); } else {
} transport.pos_beats = Some(context.project_time_music);
// 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 {
// kProjectTimeMusicValid
if P::SAMPLE_ACCURATE_AUTOMATION
&& block_start > 0
&& (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);
}
}
if context.state & (1 << 11) != 0 {
// kBarPositionValid
if P::SAMPLE_ACCURATE_AUTOMATION && block_start > 0 {
// The transport object knows how to recompute this from the other information
transport.bar_start_pos_beats =
match transport.bar_start_pos_beats() {
Some(updated) => Some(updated),
None => Some(context.bar_position_music),
};
} else {
transport.bar_start_pos_beats = Some(context.bar_position_music);
}
}
if context.state & (1 << 2) != 0 && context.state & (1 << 12) != 0 {
// kCycleActive && kCycleValid
transport.loop_range_beats =
Some((context.cycle_start_music, context.cycle_end_music));
} }
} }
let result = { if context.state & (1 << 11) != 0 {
let mut plugin = self.inner.plugin.write(); // kBarPositionValid
let mut context = self.inner.make_process_context(transport); if P::SAMPLE_ACCURATE_AUTOMATION && block_start > 0 {
plugin.process(&mut output_buffer, &mut context) // The transport object knows how to recompute this from the other information
}; transport.bar_start_pos_beats = match transport.bar_start_pos_beats() {
self.inner.last_process_status.store(result); Some(updated) => Some(updated),
None => Some(context.bar_position_music),
};
} else {
transport.bar_start_pos_beats = Some(context.bar_position_music);
}
}
if context.state & (1 << 2) != 0 && context.state & (1 << 12) != 0 {
// kCycleActive && kCycleValid
transport.loop_range_beats =
Some((context.cycle_start_music, context.cycle_end_music));
}
}
// Send any events output by the plugin during the process cycle let result = {
if let Some(events) = data.output_events.upgrade() { let mut plugin = self.inner.plugin.write();
let mut output_events = self.inner.output_events.borrow_mut(); let mut context = self.inner.make_process_context(transport);
while let Some(event) = output_events.pop_front() { plugin.process(&mut output_buffer, &mut context)
// We'll set the correct variant on this struct, or skip to the next };
// loop iteration if we don't handle the event type self.inner.last_process_status.store(result);
let mut vst3_event: Event = mem::zeroed();
vst3_event.bus_index = 0;
// There's also a ppqPos field, but uh how about no
vst3_event.sample_offset = event.timing() as i32 + block_start as i32;
match event { // Send any events output by the plugin during the process cycle
NoteEvent::NoteOn { if let Some(events) = data.output_events.upgrade() {
timing: _, let mut output_events = self.inner.output_events.borrow_mut();
channel, while let Some(event) = output_events.pop_front() {
note, // We'll set the correct variant on this struct, or skip to the next
// loop iteration if we don't handle the event type
let mut vst3_event: Event = mem::zeroed();
vst3_event.bus_index = 0;
// There's also a ppqPos field, but uh how about no
vst3_event.sample_offset = event.timing() as i32 + block_start as i32;
match event {
NoteEvent::NoteOn {
timing: _,
channel,
note,
velocity,
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
vst3_event.type_ = EventTypes::kNoteOnEvent as u16;
vst3_event.event.note_on = NoteOnEvent {
channel: channel as i16,
pitch: note as i16,
tuning: 0.0,
velocity, velocity,
} if P::MIDI_OUTPUT >= MidiConfig::Basic => { length: 0, // What?
vst3_event.type_ = EventTypes::kNoteOnEvent as u16; // We'll use this for our note IDs, that way we don't have
vst3_event.event.note_on = NoteOnEvent { // to do anything complicated here
channel: channel as i16, note_id: ((channel as i32) << 8) | note as i32,
pitch: note as i16, };
tuning: 0.0, }
velocity, NoteEvent::NoteOff {
length: 0, // What? timing: _,
// We'll use this for our note IDs, that way we don't have channel,
// to do anything complicated here note,
note_id: ((channel as i32) << 8) | note as i32, velocity,
}; } if P::MIDI_OUTPUT >= MidiConfig::Basic => {
} vst3_event.type_ = EventTypes::kNoteOffEvent as u16;
NoteEvent::NoteOff { vst3_event.event.note_off = NoteOffEvent {
timing: _, channel: channel as i16,
channel, pitch: note as i16,
note,
velocity, velocity,
} if P::MIDI_OUTPUT >= MidiConfig::Basic => { note_id: ((channel as i32) << 8) | note as i32,
vst3_event.type_ = EventTypes::kNoteOffEvent as u16; tuning: 0.0,
vst3_event.event.note_off = NoteOffEvent { };
channel: channel as i16, }
pitch: note as i16, NoteEvent::PolyPressure {
velocity, timing: _,
note_id: ((channel as i32) << 8) | note as i32, channel,
tuning: 0.0, note,
}; pressure,
} } if P::MIDI_OUTPUT >= MidiConfig::Basic => {
NoteEvent::PolyPressure { vst3_event.type_ = EventTypes::kPolyPressureEvent as u16;
timing: _, vst3_event.event.poly_pressure = PolyPressureEvent {
channel, channel: channel as i16,
note, pitch: note as i16,
note_id: ((channel as i32) << 8) | note as i32,
pressure, pressure,
} if P::MIDI_OUTPUT >= MidiConfig::Basic => { };
vst3_event.type_ = EventTypes::kPolyPressureEvent as u16; }
vst3_event.event.poly_pressure = PolyPressureEvent { event @ (NoteEvent::PolyVolume { channel, note, .. }
channel: channel as i16, | NoteEvent::PolyPan { channel, note, .. }
pitch: note as i16, | NoteEvent::PolyTuning { channel, note, .. }
note_id: ((channel as i32) << 8) | note as i32, | NoteEvent::PolyVibrato { channel, note, .. }
pressure, | NoteEvent::PolyExpression { channel, note, .. }
}; | NoteEvent::PolyBrightness { channel, note, .. })
} if P::MIDI_OUTPUT >= MidiConfig::Basic =>
event @ (NoteEvent::PolyVolume { channel, note, .. } {
| NoteEvent::PolyPan { channel, note, .. } match NoteExpressionController::translate_event_reverse(
| NoteEvent::PolyTuning { channel, note, .. } ((channel as i32) << 8) | note as i32,
| NoteEvent::PolyVibrato { channel, note, .. } &event,
| NoteEvent::PolyExpression { channel, note, .. } ) {
| NoteEvent::PolyBrightness { channel, note, .. }) Some(translated_event) => {
if P::MIDI_OUTPUT >= MidiConfig::Basic => vst3_event.type_ =
{ EventTypes::kNoteExpressionValueEvent as u16;
match NoteExpressionController::translate_event_reverse( vst3_event.event.note_expression_value = translated_event;
((channel as i32) << 8) | note as i32, }
&event, None => {
) { nih_debug_assert_failure!(
Some(translated_event) => { "Mishandled note expression value event"
vst3_event.type_ = );
EventTypes::kNoteExpressionValueEvent as u16;
vst3_event.event.note_expression_value =
translated_event;
}
None => {
nih_debug_assert_failure!(
"Mishandled note expression value event"
);
}
} }
} }
NoteEvent::MidiChannelPressure { }
timing: _, NoteEvent::MidiChannelPressure {
channel, timing: _,
pressure, channel,
} if P::MIDI_OUTPUT >= MidiConfig::MidiCCs => { pressure,
vst3_event.type_ = EventTypes::kLegacyMIDICCOutEvent as u16; } if P::MIDI_OUTPUT >= MidiConfig::MidiCCs => {
vst3_event.event.legacy_midi_cc_out = LegacyMidiCCOutEvent { vst3_event.type_ = EventTypes::kLegacyMIDICCOutEvent as u16;
control_number: 128, // kAfterTouch vst3_event.event.legacy_midi_cc_out = LegacyMidiCCOutEvent {
channel: channel as i8, control_number: 128, // kAfterTouch
value: (pressure * 127.0).round() as i8, channel: channel as i8,
value2: 0, value: (pressure * 127.0).round() as i8,
}; value2: 0,
} };
NoteEvent::MidiPitchBend { }
timing: _, NoteEvent::MidiPitchBend {
channel, timing: _,
value, channel,
} if P::MIDI_OUTPUT >= MidiConfig::MidiCCs => { value,
let scaled = (value * ((1 << 14) - 1) as f32).round() as i32; } if P::MIDI_OUTPUT >= MidiConfig::MidiCCs => {
let scaled = (value * ((1 << 14) - 1) as f32).round() as i32;
vst3_event.type_ = EventTypes::kLegacyMIDICCOutEvent as u16; vst3_event.type_ = EventTypes::kLegacyMIDICCOutEvent as u16;
vst3_event.event.legacy_midi_cc_out = LegacyMidiCCOutEvent { vst3_event.event.legacy_midi_cc_out = LegacyMidiCCOutEvent {
control_number: 129, // kPitchBend control_number: 129, // kPitchBend
channel: channel as i8, channel: channel as i8,
value: (scaled & 0b01111111) as i8, value: (scaled & 0b01111111) as i8,
value2: ((scaled >> 7) & 0b01111111) as i8, value2: ((scaled >> 7) & 0b01111111) as i8,
}; };
} }
NoteEvent::MidiCC { NoteEvent::MidiCC {
timing: _, timing: _,
channel, channel,
cc, cc,
value, value,
} if P::MIDI_OUTPUT >= MidiConfig::MidiCCs => { } if P::MIDI_OUTPUT >= MidiConfig::MidiCCs => {
vst3_event.type_ = EventTypes::kLegacyMIDICCOutEvent as u16; vst3_event.type_ = EventTypes::kLegacyMIDICCOutEvent as u16;
vst3_event.event.legacy_midi_cc_out = LegacyMidiCCOutEvent { vst3_event.event.legacy_midi_cc_out = LegacyMidiCCOutEvent {
control_number: cc, control_number: cc,
channel: channel as i8, channel: channel as i8,
value: (value * 127.0).round() as i8, value: (value * 127.0).round() as i8,
value2: 0, value2: 0,
}; };
} }
_ => { _ => {
nih_debug_assert_failure!( nih_debug_assert_failure!(
"Invalid output event for the current MIDI_OUTPUT setting" "Invalid output event for the current MIDI_OUTPUT setting"
); );
continue; continue;
} }
}; };
let result = events.add_event(&mut vst3_event); let result = events.add_event(&mut vst3_event);
nih_debug_assert_eq!(result, kResultOk); nih_debug_assert_eq!(result, kResultOk);
}
} }
}
match result { let result = match result {
ProcessStatus::Error(err) => { ProcessStatus::Error(err) => {
nih_debug_assert_failure!("Process error: {}", err); nih_debug_assert_failure!("Process error: {}", err);
return kResultFalse; return kResultFalse;
}
_ => kResultOk,
} }
_ => kResultOk,
}; };
// 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