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 - 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 provided by the framework if you want to do your own internal preset
management. management.
- Full support for both modern polyphonic note expressions as well as MIDI CCs, - Full support for receiving and outputting both modern polyphonic note
channel pressure, and pitch bend for CLAP and VST3. expression events as well as MIDI CCs, channel pressure, and pitch bend for
CLAP and VST3.
- A plugin bundler accessible through the - A plugin bundler accessible through the
`cargo xtask bundle <package> <build_arguments>` command that automatically `cargo xtask bundle <package> <build_arguments>` command that automatically
detects which plugin targets your plugin exposes and creates the correct 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>; 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 /// Update the current latency of the plugin. If the plugin is currently processing audio, then
/// this may cause audio playback to be restarted. /// this may cause audio playback to be restarted.
fn set_latency_samples(&self, samples: u32); 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. /// instead of setting up the busses properly.
const DEFAULT_NUM_OUTPUTS: u32 = 2; const DEFAULT_NUM_OUTPUTS: u32 = 2;
/// Whether the plugin accepts note events, and which level of . If this is set to /// Whether the plugin accepts note events, and what which events it wants to receive. If this
/// [`MidiConfig::None`], then the plugin won't receive any note events. /// is set to [`MidiConfig::None`], then the plugin won't receive any note events.
const MIDI_INPUT: MidiConfig = MidiConfig::None; 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 /// 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 /// 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 /// 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(crate) struct WrapperProcessContext<'a, P: ClapPlugin> {
pub(super) wrapper: &'a Wrapper<P>, pub(super) wrapper: &'a Wrapper<P>,
pub(super) input_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>, pub(super) input_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>,
pub(super) output_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>,
pub(super) transport: Transport, pub(super) transport: Transport,
} }
@ -99,6 +100,10 @@ impl<P: ClapPlugin> ProcessContext for WrapperProcessContext<'_, P> {
self.input_events_guard.pop_front() 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) { fn set_latency_samples(&self, samples: u32) {
// Only make a callback if it's actually needed // 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 // 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::latency::{clap_host_latency, clap_plugin_latency, CLAP_EXT_LATENCY};
use clap_sys::ext::note_ports::{ use clap_sys::ext::note_ports::{
clap_note_port_info, clap_plugin_note_ports, CLAP_EXT_NOTE_PORTS, CLAP_NOTE_DIALECT_CLAP, 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::{ use clap_sys::ext::params::{
clap_host_params, clap_param_info, clap_plugin_params, CLAP_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 /// TODO: Maybe load these lazily at some point instead of needing to spool them all to this
/// queue first /// queue first
input_events: AtomicRefCell<VecDeque<NoteEvent>>, 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. /// The last process status returned by the plugin. This is used for tail handling.
last_process_status: AtomicCell<ProcessStatus>, last_process_status: AtomicCell<ProcessStatus>,
/// The current latency in samples, as set by the plugin through the [`ProcessContext`]. uses /// 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), current_buffer_config: AtomicCell::new(None),
input_events: AtomicRefCell::new(VecDeque::with_capacity(512)), input_events: AtomicRefCell::new(VecDeque::with_capacity(512)),
output_events: AtomicRefCell::new(VecDeque::with_capacity(512)),
last_process_status: AtomicCell::new(ProcessStatus::Normal), last_process_status: AtomicCell::new(ProcessStatus::Normal),
current_latency: AtomicU32::new(0), current_latency: AtomicU32::new(0),
output_buffer: AtomicRefCell::new(Buffer::default()), output_buffer: AtomicRefCell::new(Buffer::default()),
@ -580,6 +585,7 @@ impl<P: ClapPlugin> Wrapper<P> {
WrapperProcessContext { WrapperProcessContext {
wrapper: self, wrapper: self,
input_events_guard: self.input_events.borrow_mut(), input_events_guard: self.input_events.borrow_mut(),
output_events_guard: self.output_events.borrow_mut(),
transport, transport,
} }
} }
@ -831,6 +837,301 @@ impl<P: ClapPlugin> Wrapper<P> {
if parameter_values_changed { if parameter_values_changed {
self.notify_param_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 /// 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, 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() { if !process.out_events.is_null() {
wrapper.handle_out_events(&*process.out_events, block_start); wrapper.handle_out_events(&*process.out_events, block_start);
} }
@ -2031,6 +2333,7 @@ impl<P: ClapPlugin> Wrapper<P> {
// TODO: Outputting notes // TODO: Outputting notes
match is_input { match is_input {
true if P::MIDI_INPUT >= MidiConfig::Basic => 1, true if P::MIDI_INPUT >= MidiConfig::Basic => 1,
false if P::MIDI_OUTPUT >= MidiConfig::Basic => 1,
_ => 0, _ => 0,
} }
} }
@ -2047,14 +2350,30 @@ impl<P: ClapPlugin> Wrapper<P> {
let info = &mut *info; let info = &mut *info;
info.id = 0; info.id = 0;
// TODO: Implement MIDI CC handling // TODO: Implement MPE (would just be a toggle for the plugin to expose it) and MIDI2
// TODO: Implement MPE and MIDI2
info.supported_dialects = CLAP_NOTE_DIALECT_CLAP; 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; info.preferred_dialect = CLAP_NOTE_DIALECT_CLAP;
strlcpy(&mut info.name, "Note Input"); strlcpy(&mut info.name, "Note Input");
true 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, _ => false,
} }
} }

View file

@ -25,6 +25,7 @@ pub(crate) struct WrapperGuiContext<P: Vst3Plugin> {
pub(crate) struct WrapperProcessContext<'a, P: Vst3Plugin> { pub(crate) struct WrapperProcessContext<'a, P: Vst3Plugin> {
pub(super) inner: &'a WrapperInner<P>, pub(super) inner: &'a WrapperInner<P>,
pub(super) input_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>, pub(super) input_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>,
pub(super) output_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>,
pub(super) transport: Transport, pub(super) transport: Transport,
} }
@ -108,6 +109,10 @@ impl<P: Vst3Plugin> ProcessContext for WrapperProcessContext<'_, P> {
self.input_events_guard.pop_front() 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) { fn set_latency_samples(&self, samples: u32) {
// Only trigger a restart if it's actually needed // Only trigger a restart if it's actually needed
let old_latency = self.inner.current_latency.swap(samples, Ordering::SeqCst); 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 /// interleave parameter changes and note events, this queue has to be sorted when
/// creating the process context /// creating the process context
pub input_events: AtomicRefCell<VecDeque<NoteEvent>>, 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 /// 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 /// 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 /// 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), current_latency: AtomicU32::new(0),
output_buffer: AtomicRefCell::new(Buffer::default()), output_buffer: AtomicRefCell::new(Buffer::default()),
input_events: AtomicRefCell::new(VecDeque::with_capacity(1024)), input_events: AtomicRefCell::new(VecDeque::with_capacity(1024)),
output_events: AtomicRefCell::new(VecDeque::with_capacity(1024)),
note_expression_controller: AtomicRefCell::new(NoteExpressionController::default()), note_expression_controller: AtomicRefCell::new(NoteExpressionController::default()),
process_events: AtomicRefCell::new(Vec::with_capacity(4096)), process_events: AtomicRefCell::new(Vec::with_capacity(4096)),
updated_state_sender, updated_state_sender,
@ -303,6 +307,7 @@ impl<P: Vst3Plugin> WrapperInner<P> {
WrapperProcessContext { WrapperProcessContext {
inner: self, inner: self,
input_events_guard: self.input_events.borrow_mut(), input_events_guard: self.input_events.borrow_mut(),
output_events_guard: self.output_events.borrow_mut(),
transport, transport,
} }
} }

View file

@ -98,4 +98,46 @@ impl NoteExpressionController {
_ => None, _ => 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::base::{IBStream, IPluginBase};
use vst3_sys::utils::SharedVstPtr; use vst3_sys::utils::SharedVstPtr;
use vst3_sys::vst::{ use vst3_sys::vst::{
kNoProgramListId, kRootUnitId, EventTypes, IAudioProcessor, IComponent, IEditController, kNoProgramListId, kRootUnitId, Event, EventTypes, IAudioProcessor, IComponent, IEditController,
IEventList, IMidiMapping, IParamValueQueue, IParameterChanges, IUnitInfo, ParameterFlags, IEventList, IMidiMapping, IParamValueQueue, IParameterChanges, IUnitInfo, LegacyMidiCCOutEvent,
ProgramListInfo, TChar, UnitInfo, NoteOffEvent, NoteOnEvent, ParameterFlags, PolyPressureEvent, ProgramListInfo, TChar, UnitInfo,
}; };
use vst3_sys::VST3; use vst3_sys::VST3;
use widestring::U16CStr; use widestring::U16CStr;
@ -26,6 +26,7 @@ use crate::util::permit_alloc;
use crate::wrapper::state; use crate::wrapper::state;
use crate::wrapper::util::{process_wrapper, u16strlcpy}; use crate::wrapper::util::{process_wrapper, u16strlcpy};
use crate::wrapper::vst3::inner::ProcessEvent; 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}; use crate::wrapper::vst3::util::{VST3_MIDI_CHANNELS, VST3_MIDI_PARAMS_END};
// Alias needed for the VST3 attribute macro // Alias needed for the VST3 attribute macro
@ -78,6 +79,12 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
{ {
1 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, _ => 0,
} }
} }
@ -130,7 +137,23 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
info.media_type = vst3_sys::vst::MediaTypes::kEvent as i32; info.media_type = vst3_sys::vst::MediaTypes::kEvent as i32;
info.direction = vst3_sys::vst::BusDirections::kInput as i32; info.direction = vst3_sys::vst::BusDirections::kInput as i32;
info.channel_count = 16; 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.bus_type = vst3_sys::vst::BusTypes::kMain as i32;
info.flags = vst3_sys::vst::BusFlags::kDefaultActive as u32; info.flags = vst3_sys::vst::BusFlags::kDefaultActive as u32;
kResultOk kResultOk
@ -158,6 +181,17 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
kResultOk 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, _ => kInvalidArgument,
} }
} }
@ -179,6 +213,13 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
{ {
kResultOk 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, _ => kInvalidArgument,
} }
} }
@ -1029,11 +1070,152 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
} }
} }
let mut plugin = self.inner.plugin.write(); let result = {
let mut context = self.inner.make_process_context(transport); 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); plugin.process(&mut output_buffer, &mut context)
};
self.inner.last_process_status.store(result); 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 { match result {
ProcessStatus::Error(err) => { ProcessStatus::Error(err) => {
nih_debug_assert_failure!("Process error: {}", err); nih_debug_assert_failure!("Process error: {}", err);