Explicitly handle 0 channel IO
Instead of treating it as a flush. This is needed for note effect plugins.
This commit is contained in:
parent
7ce86cc788
commit
5e486ab3d9
2 changed files with 268 additions and 282 deletions
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue