1
0
Fork 0

Add a way to output note events

This supports all note events supported by NIH-plug, and both CLAP and
VST3.
This commit is contained in:
Robbert van der Helm 2022-04-11 19:52:51 +02:00
parent 1f8db9d9c2
commit 65d87f87ed
9 changed files with 584 additions and 15 deletions

View file

@ -94,8 +94,9 @@ for download links.
- A simple and safe API for state saving and restoring from the editor is
provided by the framework if you want to do your own internal preset
management.
- Full support for both modern polyphonic note expressions as well as MIDI CCs,
channel pressure, and pitch bend for CLAP and VST3.
- Full support for receiving and outputting both modern polyphonic note
expression events as well as MIDI CCs, channel pressure, and pitch bend for
CLAP and VST3.
- A plugin bundler accessible through the
`cargo xtask bundle <package> <build_arguments>` command that automatically
detects which plugin targets your plugin exposes and creates the correct

View file

@ -53,6 +53,11 @@ pub trait ProcessContext {
/// ```
fn next_event(&mut self) -> Option<NoteEvent>;
/// Send an event to the host. Only available when
/// [`Plugin::MIDI_OUTPUT`][crate::prelude::Plugin::MIDI_INPUT] is set. Will not do anything
/// otherwise.
fn send_event(&mut self, event: NoteEvent);
/// Update the current latency of the plugin. If the plugin is currently processing audio, then
/// this may cause audio playback to be restarted.
fn set_latency_samples(&self, samples: u32);

View file

@ -40,9 +40,14 @@ pub trait Plugin: Default + Send + Sync + 'static {
/// instead of setting up the busses properly.
const DEFAULT_NUM_OUTPUTS: u32 = 2;
/// Whether the plugin accepts note events, and which level of . If this is set to
/// [`MidiConfig::None`], then the plugin won't receive any note events.
/// Whether the plugin accepts note events, and what which events it wants to receive. If this
/// is set to [`MidiConfig::None`], then the plugin won't receive any note events.
const MIDI_INPUT: MidiConfig = MidiConfig::None;
/// Whether the plugin can output note events. If this is set to [`MidiConfig::None`], then the
/// plugin won't have a note output port. When this is set to another value, then in most hsots
/// the plugin will consume all note and MIDI CC input. If you don't want that, then you will
/// need to forward those events yourself.
const MIDI_OUTPUT: MidiConfig = MidiConfig::None;
/// If enabled, the audio processing cycle may be split up into multiple smaller chunks if
/// 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

View file

@ -23,6 +23,7 @@ pub(crate) struct WrapperGuiContext<P: ClapPlugin> {
pub(crate) struct WrapperProcessContext<'a, P: ClapPlugin> {
pub(super) wrapper: &'a Wrapper<P>,
pub(super) input_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>,
pub(super) output_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>,
pub(super) transport: Transport,
}
@ -99,6 +100,10 @@ impl<P: ClapPlugin> ProcessContext for WrapperProcessContext<'_, P> {
self.input_events_guard.pop_front()
}
fn send_event(&mut self, event: NoteEvent) {
self.output_events_guard.push_back(event);
}
fn set_latency_samples(&self, samples: u32) {
// Only make a callback if it's actually needed
// XXX: For CLAP we could move this handling to the Plugin struct, but it may be worthwhile

View file

@ -32,6 +32,7 @@ use clap_sys::ext::gui::{
use clap_sys::ext::latency::{clap_host_latency, clap_plugin_latency, CLAP_EXT_LATENCY};
use clap_sys::ext::note_ports::{
clap_note_port_info, clap_plugin_note_ports, CLAP_EXT_NOTE_PORTS, CLAP_NOTE_DIALECT_CLAP,
CLAP_NOTE_DIALECT_MIDI,
};
use clap_sys::ext::params::{
clap_host_params, clap_param_info, clap_plugin_params, CLAP_EXT_PARAMS,
@ -128,6 +129,9 @@ pub struct Wrapper<P: ClapPlugin> {
/// TODO: Maybe load these lazily at some point instead of needing to spool them all to this
/// queue first
input_events: AtomicRefCell<VecDeque<NoteEvent>>,
/// Stores any events the plugin has output during the current processing cycle, analogous to
/// `input_events`.
output_events: AtomicRefCell<VecDeque<NoteEvent>>,
/// The last process status returned by the plugin. This is used for tail handling.
last_process_status: AtomicCell<ProcessStatus>,
/// The current latency in samples, as set by the plugin through the [`ProcessContext`]. uses
@ -478,6 +482,7 @@ impl<P: ClapPlugin> Wrapper<P> {
}),
current_buffer_config: AtomicCell::new(None),
input_events: AtomicRefCell::new(VecDeque::with_capacity(512)),
output_events: AtomicRefCell::new(VecDeque::with_capacity(512)),
last_process_status: AtomicCell::new(ProcessStatus::Normal),
current_latency: AtomicU32::new(0),
output_buffer: AtomicRefCell::new(Buffer::default()),
@ -580,6 +585,7 @@ impl<P: ClapPlugin> Wrapper<P> {
WrapperProcessContext {
wrapper: self,
input_events_guard: self.input_events.borrow_mut(),
output_events_guard: self.output_events.borrow_mut(),
transport,
}
}
@ -831,6 +837,301 @@ impl<P: ClapPlugin> Wrapper<P> {
if parameter_values_changed {
self.notify_param_values_changed();
}
// Also send all note events generated by the plugin
let mut output_events = self.output_events.borrow_mut();
while let Some(event) = output_events.pop_front() {
let time = event.timing() + current_sample_idx as u32;
let push_successful = match event {
NoteEvent::NoteOn {
timing: _,
channel,
note,
velocity,
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
let event = clap_event_note {
header: clap_event_header {
size: mem::size_of::<clap_event_param_gesture>() as u32,
time,
space_id: CLAP_CORE_EVENT_SPACE_ID,
type_: CLAP_EVENT_NOTE_ON,
// We don't have a way to denote live events
flags: 0,
},
port_index: 0,
key: note as i16,
channel: channel as i16,
velocity: velocity as f64,
};
(out.try_push)(out, &event.header)
}
NoteEvent::NoteOff {
timing: _,
channel,
note,
velocity,
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
let event = clap_event_note {
header: clap_event_header {
size: mem::size_of::<clap_event_param_gesture>() as u32,
time,
space_id: CLAP_CORE_EVENT_SPACE_ID,
type_: CLAP_EVENT_NOTE_OFF,
flags: 0,
},
port_index: 0,
key: note as i16,
channel: channel as i16,
velocity: velocity as f64,
};
(out.try_push)(out, &event.header)
}
NoteEvent::PolyPressure {
timing: _,
channel,
note,
pressure,
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
let event = clap_event_note_expression {
header: clap_event_header {
size: mem::size_of::<clap_event_param_gesture>() as u32,
time,
space_id: CLAP_CORE_EVENT_SPACE_ID,
type_: CLAP_EVENT_NOTE_EXPRESSION,
flags: 0,
},
expression_id: CLAP_NOTE_EXPRESSION_PRESSURE,
port_index: 0,
key: note as i16,
channel: channel as i16,
value: pressure as f64,
};
(out.try_push)(out, &event.header)
}
NoteEvent::PolyVolume {
timing: _,
channel,
note,
gain,
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
let event = clap_event_note_expression {
header: clap_event_header {
size: mem::size_of::<clap_event_param_gesture>() as u32,
time,
space_id: CLAP_CORE_EVENT_SPACE_ID,
type_: CLAP_EVENT_NOTE_EXPRESSION,
flags: 0,
},
expression_id: CLAP_NOTE_EXPRESSION_VOLUME,
port_index: 0,
key: note as i16,
channel: channel as i16,
value: gain as f64,
};
(out.try_push)(out, &event.header)
}
NoteEvent::PolyPan {
timing: _,
channel,
note,
pan,
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
let event = clap_event_note_expression {
header: clap_event_header {
size: mem::size_of::<clap_event_param_gesture>() as u32,
time,
space_id: CLAP_CORE_EVENT_SPACE_ID,
type_: CLAP_EVENT_NOTE_EXPRESSION,
flags: 0,
},
expression_id: CLAP_NOTE_EXPRESSION_PAN,
port_index: 0,
key: note as i16,
channel: channel as i16,
value: (pan as f64 + 1.0) / 2.0,
};
(out.try_push)(out, &event.header)
}
NoteEvent::PolyTuning {
timing: _,
channel,
note,
tuning,
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
let event = clap_event_note_expression {
header: clap_event_header {
size: mem::size_of::<clap_event_param_gesture>() as u32,
time,
space_id: CLAP_CORE_EVENT_SPACE_ID,
type_: CLAP_EVENT_NOTE_EXPRESSION,
flags: 0,
},
expression_id: CLAP_NOTE_EXPRESSION_TUNING,
port_index: 0,
key: note as i16,
channel: channel as i16,
value: tuning as f64,
};
(out.try_push)(out, &event.header)
}
NoteEvent::PolyVibrato {
timing: _,
channel,
note,
vibrato,
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
let event = clap_event_note_expression {
header: clap_event_header {
size: mem::size_of::<clap_event_param_gesture>() as u32,
time,
space_id: CLAP_CORE_EVENT_SPACE_ID,
type_: CLAP_EVENT_NOTE_EXPRESSION,
flags: 0,
},
expression_id: CLAP_NOTE_EXPRESSION_VIBRATO,
port_index: 0,
key: note as i16,
channel: channel as i16,
value: vibrato as f64,
};
(out.try_push)(out, &event.header)
}
NoteEvent::PolyExpression {
timing: _,
channel,
note,
expression,
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
let event = clap_event_note_expression {
header: clap_event_header {
size: mem::size_of::<clap_event_param_gesture>() as u32,
time,
space_id: CLAP_CORE_EVENT_SPACE_ID,
type_: CLAP_EVENT_NOTE_EXPRESSION,
flags: 0,
},
expression_id: CLAP_NOTE_EXPRESSION_EXPRESSION,
port_index: 0,
key: note as i16,
channel: channel as i16,
value: expression as f64,
};
(out.try_push)(out, &event.header)
}
NoteEvent::PolyBrightness {
timing: _,
channel,
note,
brightness,
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
let event = clap_event_note_expression {
header: clap_event_header {
size: mem::size_of::<clap_event_param_gesture>() as u32,
time,
space_id: CLAP_CORE_EVENT_SPACE_ID,
type_: CLAP_EVENT_NOTE_EXPRESSION,
flags: 0,
},
expression_id: CLAP_NOTE_EXPRESSION_BRIGHTNESS,
port_index: 0,
key: note as i16,
channel: channel as i16,
value: brightness as f64,
};
(out.try_push)(out, &event.header)
}
NoteEvent::MidiChannelPressure {
timing: _,
channel,
pressure,
} if P::MIDI_OUTPUT >= MidiConfig::MidiCCs => {
let event = clap_event_midi {
header: clap_event_header {
size: mem::size_of::<clap_event_param_gesture>() as u32,
time,
space_id: CLAP_CORE_EVENT_SPACE_ID,
type_: CLAP_EVENT_MIDI,
flags: 0,
},
port_index: 0,
data: [
midi::CHANNEL_KEY_PRESSURE | channel as u8,
(pressure * 127.0).round() as u8,
0,
],
};
(out.try_push)(out, &event.header)
}
NoteEvent::MidiPitchBend {
timing: _,
channel,
value,
} if P::MIDI_OUTPUT >= MidiConfig::MidiCCs => {
let scaled = (value * ((1 << 14) - 1) as f32).round() as i32;
let event = clap_event_midi {
header: clap_event_header {
size: mem::size_of::<clap_event_param_gesture>() as u32,
time,
space_id: CLAP_CORE_EVENT_SPACE_ID,
type_: CLAP_EVENT_MIDI,
flags: 0,
},
port_index: 0,
data: [
midi::PITCH_BEND_CHANGE | channel as u8,
(scaled & 0b01111111) as u8,
((scaled >> 7) & 0b01111111) as u8,
],
};
(out.try_push)(out, &event.header)
}
NoteEvent::MidiCC {
timing: _,
channel,
cc,
value,
} if P::MIDI_OUTPUT >= MidiConfig::MidiCCs => {
let event = clap_event_midi {
header: clap_event_header {
size: mem::size_of::<clap_event_param_gesture>() as u32,
time,
space_id: CLAP_CORE_EVENT_SPACE_ID,
type_: CLAP_EVENT_MIDI,
flags: 0,
},
port_index: 0,
data: [
midi::CONTROL_CHANGE | channel as u8,
cc,
(value * 127.0).round() as u8,
],
};
(out.try_push)(out, &event.header)
}
_ => {
nih_debug_assert_failure!(
"Invalid output event for the current MIDI_OUTPUT setting"
);
continue;
}
};
nih_debug_assert!(push_successful, "Could not send note event");
}
}
/// Handle an incoming CLAP event. The sample index is provided to support block splitting for
@ -1462,7 +1763,8 @@ impl<P: ClapPlugin> Wrapper<P> {
ProcessStatus::KeepAlive => CLAP_PROCESS_CONTINUE,
};
// After processing audio, send all spooled events to the host
// After processing audio, send all spooled events to the host. This include note
// events.
if !process.out_events.is_null() {
wrapper.handle_out_events(&*process.out_events, block_start);
}
@ -2031,6 +2333,7 @@ impl<P: ClapPlugin> Wrapper<P> {
// TODO: Outputting notes
match is_input {
true if P::MIDI_INPUT >= MidiConfig::Basic => 1,
false if P::MIDI_OUTPUT >= MidiConfig::Basic => 1,
_ => 0,
}
}
@ -2047,14 +2350,30 @@ impl<P: ClapPlugin> Wrapper<P> {
let info = &mut *info;
info.id = 0;
// TODO: Implement MIDI CC handling
// TODO: Implement MPE and MIDI2
// TODO: Implement MPE (would just be a toggle for the plugin to expose it) and MIDI2
info.supported_dialects = CLAP_NOTE_DIALECT_CLAP;
if P::MIDI_INPUT >= MidiConfig::MidiCCs {
info.supported_dialects |= CLAP_NOTE_DIALECT_MIDI;
}
info.preferred_dialect = CLAP_NOTE_DIALECT_CLAP;
strlcpy(&mut info.name, "Note Input");
true
}
(0, false) if P::MIDI_OUTPUT >= MidiConfig::Basic => {
*info = std::mem::zeroed();
let info = &mut *info;
info.id = 0;
info.supported_dialects = CLAP_NOTE_DIALECT_CLAP;
if P::MIDI_OUTPUT >= MidiConfig::MidiCCs {
info.supported_dialects |= CLAP_NOTE_DIALECT_MIDI;
}
info.preferred_dialect = CLAP_NOTE_DIALECT_CLAP;
strlcpy(&mut info.name, "Note Output");
true
}
_ => false,
}
}

View file

@ -25,6 +25,7 @@ pub(crate) struct WrapperGuiContext<P: Vst3Plugin> {
pub(crate) struct WrapperProcessContext<'a, P: Vst3Plugin> {
pub(super) inner: &'a WrapperInner<P>,
pub(super) input_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>,
pub(super) output_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>,
pub(super) transport: Transport,
}
@ -108,6 +109,10 @@ impl<P: Vst3Plugin> ProcessContext for WrapperProcessContext<'_, P> {
self.input_events_guard.pop_front()
}
fn send_event(&mut self, event: NoteEvent) {
self.output_events_guard.push_back(event);
}
fn set_latency_samples(&self, samples: u32) {
// Only trigger a restart if it's actually needed
let old_latency = self.inner.current_latency.swap(samples, Ordering::SeqCst);

View file

@ -82,6 +82,9 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
/// interleave parameter changes and note events, this queue has to be sorted when
/// creating the process context
pub input_events: AtomicRefCell<VecDeque<NoteEvent>>,
/// Stores any events the plugin has output during the current processing cycle, analogous to
/// `input_events`.
pub output_events: AtomicRefCell<VecDeque<NoteEvent>>,
/// VST3 has several useful predefined note expressions, but for some reason they are the only
/// note event type that don't have MIDI note ID and channel fields. So we need to keep track of
/// the msot recent VST3 note IDs we've seen, and then map those back to MIDI note IDs and
@ -273,6 +276,7 @@ impl<P: Vst3Plugin> WrapperInner<P> {
current_latency: AtomicU32::new(0),
output_buffer: AtomicRefCell::new(Buffer::default()),
input_events: AtomicRefCell::new(VecDeque::with_capacity(1024)),
output_events: AtomicRefCell::new(VecDeque::with_capacity(1024)),
note_expression_controller: AtomicRefCell::new(NoteExpressionController::default()),
process_events: AtomicRefCell::new(Vec::with_capacity(4096)),
updated_state_sender,
@ -303,6 +307,7 @@ impl<P: Vst3Plugin> WrapperInner<P> {
WrapperProcessContext {
inner: self,
input_events_guard: self.input_events.borrow_mut(),
output_events_guard: self.output_events.borrow_mut(),
transport,
}
}

View file

@ -98,4 +98,46 @@ impl NoteExpressionController {
_ => None,
}
}
/// Translate a NIH-plug note expression event a VST3 `NoteExpressionValueEvent`. Will return
/// `None` if the event is not a polyphonic expression event, i.e. one of the events handled by
/// `translate_event()`.
pub fn translate_event_reverse(
note_id: i32,
event: &NoteEvent,
) -> Option<NoteExpressionValueEvent> {
match &event {
NoteEvent::PolyVolume { gain, .. } => Some(NoteExpressionValueEvent {
type_id: 0, // kVolumeTypeID
note_id,
value: *gain as f64 / 4.0,
}),
NoteEvent::PolyPan { pan, .. } => Some(NoteExpressionValueEvent {
type_id: 1, // kPanTypeId
note_id,
value: (*pan as f64 + 1.0) / 2.0,
}),
NoteEvent::PolyTuning { tuning, .. } => Some(NoteExpressionValueEvent {
type_id: 2, // kTuningTypeID
note_id,
value: (*tuning as f64 / 240.0) + 0.5,
}),
NoteEvent::PolyVibrato { vibrato, .. } => Some(NoteExpressionValueEvent {
type_id: 3, // kVibratoTypeID
note_id,
value: *vibrato as f64,
}),
NoteEvent::PolyExpression { expression, .. } => Some(NoteExpressionValueEvent {
type_id: 4, // kExpressionTypeID
note_id,
value: *expression as f64,
}),
NoteEvent::PolyBrightness { brightness, .. } => Some(NoteExpressionValueEvent {
type_id: 5, // kBrightnessTypeID
note_id,
value: *brightness as f64,
}),
_ => None,
}
}
}

View file

@ -8,9 +8,9 @@ use vst3_sys::base::{kInvalidArgument, kNoInterface, kResultFalse, kResultOk, tr
use vst3_sys::base::{IBStream, IPluginBase};
use vst3_sys::utils::SharedVstPtr;
use vst3_sys::vst::{
kNoProgramListId, kRootUnitId, EventTypes, IAudioProcessor, IComponent, IEditController,
IEventList, IMidiMapping, IParamValueQueue, IParameterChanges, IUnitInfo, ParameterFlags,
ProgramListInfo, TChar, UnitInfo,
kNoProgramListId, kRootUnitId, Event, EventTypes, IAudioProcessor, IComponent, IEditController,
IEventList, IMidiMapping, IParamValueQueue, IParameterChanges, IUnitInfo, LegacyMidiCCOutEvent,
NoteOffEvent, NoteOnEvent, ParameterFlags, PolyPressureEvent, ProgramListInfo, TChar, UnitInfo,
};
use vst3_sys::VST3;
use widestring::U16CStr;
@ -26,6 +26,7 @@ use crate::util::permit_alloc;
use crate::wrapper::state;
use crate::wrapper::util::{process_wrapper, u16strlcpy};
use crate::wrapper::vst3::inner::ProcessEvent;
use crate::wrapper::vst3::note_expressions::NoteExpressionController;
use crate::wrapper::vst3::util::{VST3_MIDI_CHANNELS, VST3_MIDI_PARAMS_END};
// Alias needed for the VST3 attribute macro
@ -78,6 +79,12 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
{
1
}
x if x == vst3_sys::vst::MediaTypes::kEvent as i32
&& dir == vst3_sys::vst::BusDirections::kOutput as i32
&& P::MIDI_OUTPUT >= MidiConfig::Basic =>
{
1
}
_ => 0,
}
}
@ -130,7 +137,23 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
info.media_type = vst3_sys::vst::MediaTypes::kEvent as i32;
info.direction = vst3_sys::vst::BusDirections::kInput as i32;
info.channel_count = 16;
u16strlcpy(&mut info.name, "MIDI");
u16strlcpy(&mut info.name, "Note Input");
info.bus_type = vst3_sys::vst::BusTypes::kMain as i32;
info.flags = vst3_sys::vst::BusFlags::kDefaultActive as u32;
kResultOk
}
(t, d, 0)
if t == vst3_sys::vst::MediaTypes::kEvent as i32
&& d == vst3_sys::vst::BusDirections::kOutput as i32
&& P::MIDI_OUTPUT >= MidiConfig::Basic =>
{
*info = mem::zeroed();
let info = &mut *info;
info.media_type = vst3_sys::vst::MediaTypes::kEvent as i32;
info.direction = vst3_sys::vst::BusDirections::kOutput as i32;
info.channel_count = 16;
u16strlcpy(&mut info.name, "Note Output");
info.bus_type = vst3_sys::vst::BusTypes::kMain as i32;
info.flags = vst3_sys::vst::BusFlags::kDefaultActive as u32;
kResultOk
@ -158,6 +181,17 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
kResultOk
}
(t, 0)
if t == vst3_sys::vst::MediaTypes::kEvent as i32
&& P::MIDI_INPUT >= MidiConfig::Basic
&& P::MIDI_OUTPUT >= MidiConfig::Basic =>
{
out_info.media_type = vst3_sys::vst::MediaTypes::kEvent as i32;
out_info.bus_index = in_info.bus_index;
out_info.channel = in_info.channel;
kResultOk
}
_ => kInvalidArgument,
}
}
@ -179,6 +213,13 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
{
kResultOk
}
(t, d, 0)
if t == vst3_sys::vst::MediaTypes::kEvent as i32
&& d == vst3_sys::vst::BusDirections::kOutput as i32
&& P::MIDI_OUTPUT >= MidiConfig::Basic =>
{
kResultOk
}
_ => kInvalidArgument,
}
}
@ -1029,11 +1070,152 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
}
}
let mut plugin = self.inner.plugin.write();
let mut context = self.inner.make_process_context(transport);
let result = plugin.process(&mut output_buffer, &mut context);
let result = {
let mut plugin = self.inner.plugin.write();
let mut context = self.inner.make_process_context(transport);
plugin.process(&mut output_buffer, &mut context)
};
self.inner.last_process_status.store(result);
// Send any events output by the plugin during the process cycle
if let Some(events) = data.output_events.upgrade() {
let mut output_events = self.inner.output_events.borrow_mut();
while let Some(event) = output_events.pop_front() {
// 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,
length: 0, // What?
// We'll use this for our note IDs, that way we don't have
// to do anything complicated here
note_id: ((channel as i32) << 8) | note as i32,
};
}
NoteEvent::NoteOff {
timing: _,
channel,
note,
velocity,
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
vst3_event.type_ = EventTypes::kNoteOffEvent as u16;
vst3_event.event.note_off = NoteOffEvent {
channel: channel as i16,
pitch: note as i16,
velocity,
note_id: ((channel as i32) << 8) | note as i32,
tuning: 0.0,
};
}
NoteEvent::PolyPressure {
timing: _,
channel,
note,
pressure,
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
vst3_event.type_ = EventTypes::kPolyPressureEvent as u16;
vst3_event.event.poly_pressure = PolyPressureEvent {
channel: channel as i16,
pitch: note as i16,
note_id: ((channel as i32) << 8) | note as i32,
pressure,
};
}
event @ (NoteEvent::PolyVolume { channel, note, .. }
| NoteEvent::PolyPan { channel, note, .. }
| NoteEvent::PolyTuning { channel, note, .. }
| NoteEvent::PolyVibrato { channel, note, .. }
| NoteEvent::PolyExpression { channel, note, .. }
| NoteEvent::PolyBrightness { channel, note, .. })
if P::MIDI_OUTPUT >= MidiConfig::Basic =>
{
match NoteExpressionController::translate_event_reverse(
((channel as i32) << 8) | note as i32,
&event,
) {
Some(translated_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: _,
channel,
pressure,
} if P::MIDI_OUTPUT >= MidiConfig::MidiCCs => {
vst3_event.type_ = EventTypes::kLegacyMIDICCOutEvent as u16;
vst3_event.event.legacy_midi_cc_out = LegacyMidiCCOutEvent {
control_number: 128, // kAfterTouch
channel: channel as i8,
value: (pressure * 127.0).round() as i8,
value2: 0,
};
}
NoteEvent::MidiPitchBend {
timing: _,
channel,
value,
} 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.event.legacy_midi_cc_out = LegacyMidiCCOutEvent {
control_number: 129, // kPitchBend
channel: channel as i8,
value: (scaled & 0b01111111) as i8,
value2: ((scaled >> 7) & 0b01111111) as i8,
};
}
NoteEvent::MidiCC {
timing: _,
channel,
cc,
value,
} if P::MIDI_OUTPUT >= MidiConfig::MidiCCs => {
vst3_event.type_ = EventTypes::kLegacyMIDICCOutEvent as u16;
vst3_event.event.legacy_midi_cc_out = LegacyMidiCCOutEvent {
control_number: cc,
channel: channel as i8,
value: (value * 127.0).round() as i8,
value2: 0,
};
}
_ => {
nih_debug_assert_failure!(
"Invalid output event for the current MIDI_OUTPUT setting"
);
continue;
}
};
let result = events.add_event(&mut vst3_event);
nih_debug_assert_eq!(result, kResultOk);
}
}
match result {
ProcessStatus::Error(err) => {
nih_debug_assert_failure!("Process error: {}", err);