From 65d87f87edea944fa46dfbd8fb85d18e6a747b25 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 11 Apr 2022 19:52:51 +0200 Subject: [PATCH] Add a way to output note events This supports all note events supported by NIH-plug, and both CLAP and VST3. --- README.md | 5 +- src/context.rs | 5 + src/plugin.rs | 9 +- src/wrapper/clap/context.rs | 5 + src/wrapper/clap/wrapper.rs | 325 ++++++++++++++++++++++++++- src/wrapper/vst3/context.rs | 5 + src/wrapper/vst3/inner.rs | 5 + src/wrapper/vst3/note_expressions.rs | 42 ++++ src/wrapper/vst3/wrapper.rs | 198 +++++++++++++++- 9 files changed, 584 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7817919a..ff3a859a 100644 --- a/README.md +++ b/README.md @@ -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 ` command that automatically detects which plugin targets your plugin exposes and creates the correct diff --git a/src/context.rs b/src/context.rs index a67368de..b16425b8 100644 --- a/src/context.rs +++ b/src/context.rs @@ -53,6 +53,11 @@ pub trait ProcessContext { /// ``` fn next_event(&mut self) -> Option; + /// 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); diff --git a/src/plugin.rs b/src/plugin.rs index 1778b2a2..9eab0adc 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -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 diff --git a/src/wrapper/clap/context.rs b/src/wrapper/clap/context.rs index 0f9527e1..f128cb39 100644 --- a/src/wrapper/clap/context.rs +++ b/src/wrapper/clap/context.rs @@ -23,6 +23,7 @@ pub(crate) struct WrapperGuiContext { pub(crate) struct WrapperProcessContext<'a, P: ClapPlugin> { pub(super) wrapper: &'a Wrapper

, pub(super) input_events_guard: AtomicRefMut<'a, VecDeque>, + pub(super) output_events_guard: AtomicRefMut<'a, VecDeque>, pub(super) transport: Transport, } @@ -99,6 +100,10 @@ impl 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 diff --git a/src/wrapper/clap/wrapper.rs b/src/wrapper/clap/wrapper.rs index 12be0a58..6ecb6152 100644 --- a/src/wrapper/clap/wrapper.rs +++ b/src/wrapper/clap/wrapper.rs @@ -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 { /// TODO: Maybe load these lazily at some point instead of needing to spool them all to this /// queue first input_events: AtomicRefCell>, + /// Stores any events the plugin has output during the current processing cycle, analogous to + /// `input_events`. + output_events: AtomicRefCell>, /// The last process status returned by the plugin. This is used for tail handling. last_process_status: AtomicCell, /// The current latency in samples, as set by the plugin through the [`ProcessContext`]. uses @@ -478,6 +482,7 @@ impl Wrapper

{ }), 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 Wrapper

{ 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 Wrapper

{ 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::() 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::() 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::() 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::() 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::() 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::() 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::() 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::() 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::() 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::() 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::() 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::() 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 Wrapper

{ 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 Wrapper

{ // 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 Wrapper

{ 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, } } diff --git a/src/wrapper/vst3/context.rs b/src/wrapper/vst3/context.rs index 1ea594c5..560c8937 100644 --- a/src/wrapper/vst3/context.rs +++ b/src/wrapper/vst3/context.rs @@ -25,6 +25,7 @@ pub(crate) struct WrapperGuiContext { pub(crate) struct WrapperProcessContext<'a, P: Vst3Plugin> { pub(super) inner: &'a WrapperInner

, pub(super) input_events_guard: AtomicRefMut<'a, VecDeque>, + pub(super) output_events_guard: AtomicRefMut<'a, VecDeque>, pub(super) transport: Transport, } @@ -108,6 +109,10 @@ impl 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); diff --git a/src/wrapper/vst3/inner.rs b/src/wrapper/vst3/inner.rs index e4deff30..c2e112a5 100644 --- a/src/wrapper/vst3/inner.rs +++ b/src/wrapper/vst3/inner.rs @@ -82,6 +82,9 @@ pub(crate) struct WrapperInner { /// interleave parameter changes and note events, this queue has to be sorted when /// creating the process context pub input_events: AtomicRefCell>, + /// Stores any events the plugin has output during the current processing cycle, analogous to + /// `input_events`. + pub output_events: AtomicRefCell>, /// 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 WrapperInner

{ 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 WrapperInner

{ WrapperProcessContext { inner: self, input_events_guard: self.input_events.borrow_mut(), + output_events_guard: self.output_events.borrow_mut(), transport, } } diff --git a/src/wrapper/vst3/note_expressions.rs b/src/wrapper/vst3/note_expressions.rs index 63d14ecf..911f1aaf 100644 --- a/src/wrapper/vst3/note_expressions.rs +++ b/src/wrapper/vst3/note_expressions.rs @@ -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 { + 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, + } + } } diff --git a/src/wrapper/vst3/wrapper.rs b/src/wrapper/vst3/wrapper.rs index d44dedd5..6247e6c0 100644 --- a/src/wrapper/vst3/wrapper.rs +++ b/src/wrapper/vst3/wrapper.rs @@ -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 IComponent for Wrapper

{ { 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 IComponent for Wrapper

{ 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 IComponent for Wrapper

{ 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 IComponent for Wrapper

{ { 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 IAudioProcessor for Wrapper

{ } } - 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);