1
0
Fork 0

Handle predefined VST3 note expressions

This commit is contained in:
Robbert van der Helm 2022-04-08 17:49:13 +02:00
parent 25dd0d9bef
commit 059c733b78
4 changed files with 132 additions and 8 deletions

View file

@ -4,6 +4,7 @@ mod util;
mod context; mod context;
mod factory; mod factory;
mod inner; mod inner;
mod note_expressions;
mod param_units; mod param_units;
mod view; mod view;
mod wrapper; mod wrapper;

View file

@ -12,6 +12,7 @@ use vst3_sys::base::{kInvalidArgument, kResultOk, tresult};
use vst3_sys::vst::{IComponentHandler, RestartFlags}; use vst3_sys::vst::{IComponentHandler, RestartFlags};
use super::context::{WrapperGuiContext, WrapperProcessContext}; use super::context::{WrapperGuiContext, WrapperProcessContext};
use super::note_expressions::NoteExpressionController;
use super::param_units::ParamUnits; use super::param_units::ParamUnits;
use super::util::{ObjectPtr, VstPtr}; use super::util::{ObjectPtr, VstPtr};
use super::view::WrapperView; use super::view::WrapperView;
@ -82,6 +83,11 @@ 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>>,
/// 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
/// channels as needed.
pub note_expression_controller: AtomicRefCell<NoteExpressionController>,
/// Unprocessed parameter changes sent by the host as pairs of `(sample_idx_in_buffer, change)`. /// Unprocessed parameter changes sent by the host as pairs of `(sample_idx_in_buffer, change)`.
/// Needed because VST3 does not have a single queue containing all parameter changes. If /// Needed because VST3 does not have a single queue containing all parameter changes. If
/// `P::SAMPLE_ACCURATE_AUTOMATION` is set, then all parameter changes will be read into this /// `P::SAMPLE_ACCURATE_AUTOMATION` is set, then all parameter changes will be read into this
@ -248,6 +254,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)),
note_expression_controller: AtomicRefCell::new(NoteExpressionController::default()),
input_param_changes: AtomicRefCell::new(BinaryHeap::with_capacity( input_param_changes: AtomicRefCell::new(BinaryHeap::with_capacity(
if P::SAMPLE_ACCURATE_AUTOMATION { if P::SAMPLE_ACCURATE_AUTOMATION {
4096 4096

View file

@ -0,0 +1,101 @@
//! Special handling for note expressions, because VST3 makes this a lot more complicated than it
//! needs to be. We only support the predefined expressions.
use vst3_sys::vst::{NoteExpressionValueEvent, NoteOnEvent};
use crate::midi::NoteEvent;
type MidiNote = u8;
type MidiChannel = u8;
type NoteId = i32;
/// The number of notes we'll keep track of for mapping note IDs to channel+note combinations.
const NOTE_IDS_LEN: usize = 32;
/// VST3 has predefined note expressions just like CLAP, but unlike the other note events these
/// expressions are identified only with a note ID. To account for that, we'll keep track of the
/// most recent note IDs we've encountered so we can later map those IDs back to a note and channel
/// combination.
#[derive(Debug, Default)]
pub struct NoteExpressionController {
/// The last 32 note IDs we've seen. We'll do a linear search every time we receive a note
/// expression value event to find the matching note and channel.
note_ids: [(NoteId, MidiNote, MidiChannel); NOTE_IDS_LEN],
/// The index in the `note_ids` ring buffer the next event should be inserted at, wraps back
/// around to 0 when reaching the end.
note_ids_idx: usize,
}
impl NoteExpressionController {
/// Register the note ID from a note on event so it can later be retrieved when handling a note
/// expression value event.
pub fn register_note(&mut self, event: &NoteOnEvent) {
self.note_ids[self.note_ids_idx] = (event.note_id, event.pitch as u8, event.channel as u8);
self.note_ids_idx = (self.note_ids_idx + 1) % NOTE_IDS_LEN;
}
/// Translate the note expression value event into an internal NIH-plug event, if we handle the
/// expression type from the note expression value event. The timing is provided here because we
/// may be splitting buffers on inter-buffer parameter changes.
pub fn translate_event(
&self,
timing: u32,
event: &NoteExpressionValueEvent,
) -> Option<NoteEvent> {
let (_, note, channel) = *self
.note_ids
.iter()
.find(|(note_id, _, _)| *note_id == event.note_id)?;
match event.type_id {
// kVolumeTypeID
0 => Some(NoteEvent::Volume {
timing,
channel,
note,
// Because expression values in VST3 are always in the `[0, 1]` range, they added a
// 4x scaling factor here to allow the values to go from -infinity to +12 dB
gain: event.value as f32 * 4.0,
}),
// kPanTypeId
1 => Some(NoteEvent::Pan {
timing,
channel,
note,
// Our panning expressions are symmetrical around 0
pan: (event.value as f32 * 2.0) - 1.0,
}),
// kTuningTypeID
2 => Some(NoteEvent::Tuning {
timing,
channel,
note,
// This denormalized to the same [-120, 120] range used by CLAP and our expression
// events
tuning: 240.0 * (event.value as f32 - 0.5),
}),
// kVibratoTypeID
3 => Some(NoteEvent::Vibrato {
timing,
channel,
note,
vibrato: event.value as f32,
}),
// kExpressionTypeID
4 => Some(NoteEvent::Brightness {
timing,
channel,
note,
brightness: event.value as f32,
}),
// kBrightnessTypeID
5 => Some(NoteEvent::Expression {
timing,
channel,
note,
expression: event.value as f32,
}),
_ => None,
}
}
}

View file

@ -8,8 +8,8 @@ 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, IAudioProcessor, IComponent, IEditController, IEventList, kNoProgramListId, kRootUnitId, EventTypes, IAudioProcessor, IComponent, IEditController,
IParamValueQueue, IParameterChanges, IUnitInfo, ProgramListInfo, TChar, UnitInfo, IEventList, IParamValueQueue, IParameterChanges, IUnitInfo, ProgramListInfo, TChar, UnitInfo,
}; };
use vst3_sys::VST3; use vst3_sys::VST3;
use widestring::U16CStr; use widestring::U16CStr;
@ -746,6 +746,8 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
if P::MIDI_INPUT >= MidiConfig::Basic { if P::MIDI_INPUT >= MidiConfig::Basic {
let mut input_events = self.inner.input_events.borrow_mut(); let mut input_events = self.inner.input_events.borrow_mut();
let mut note_expression_controller =
self.inner.note_expression_controller.borrow_mut();
if let Some(events) = data.input_events.upgrade() { if let Some(events) = data.input_events.upgrade() {
let num_events = events.get_event_count(); let num_events = events.get_event_count();
@ -765,16 +767,20 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
} }
let timing = event.sample_offset as u32 - block_start as u32; let timing = event.sample_offset as u32 - block_start as u32;
if event.type_ == vst3_sys::vst::EventTypes::kNoteOnEvent as u16 { if event.type_ == EventTypes::kNoteOnEvent as u16 {
let event = event.event.note_on; let event = event.event.note_on;
// We need to keep track of note IDs to be able to handle not
// expression value events
note_expression_controller.register_note(&event);
input_events.push_back(NoteEvent::NoteOn { input_events.push_back(NoteEvent::NoteOn {
timing, timing,
channel: event.channel as u8, channel: event.channel as u8,
note: event.pitch as u8, note: event.pitch as u8,
velocity: event.velocity, velocity: event.velocity,
}); });
} else if event.type_ == vst3_sys::vst::EventTypes::kNoteOffEvent as u16 } else if event.type_ == EventTypes::kNoteOffEvent as u16 {
{
let event = event.event.note_off; let event = event.event.note_off;
input_events.push_back(NoteEvent::NoteOff { input_events.push_back(NoteEvent::NoteOff {
timing, timing,
@ -782,9 +788,7 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
note: event.pitch as u8, note: event.pitch as u8,
velocity: event.velocity, velocity: event.velocity,
}); });
} else if event.type_ } else if event.type_ == EventTypes::kPolyPressureEvent as u16 {
== vst3_sys::vst::EventTypes::kPolyPressureEvent as u16
{
let event = event.event.poly_pressure; let event = event.event.poly_pressure;
input_events.push_back(NoteEvent::PolyPressure { input_events.push_back(NoteEvent::PolyPressure {
timing, timing,
@ -792,6 +796,17 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
note: event.pitch as u8, note: event.pitch as u8,
pressure: event.pressure, pressure: event.pressure,
}); });
} else if event.type_ == EventTypes::kNoteExpressionValueEvent as u16 {
let event = event.event.note_expression_value;
match note_expression_controller.translate_event(timing, &event) {
Some(translated_event) => {
input_events.push_back(translated_event)
}
None => nih_debug_assert_failure!(
"Unhandled note expression type: {}",
event.type_id
),
}
} }
// TODO: Add note event controllers to support the same expression types // TODO: Add note event controllers to support the same expression types