Handle predefined VST3 note expressions
This commit is contained in:
parent
25dd0d9bef
commit
059c733b78
4 changed files with 132 additions and 8 deletions
|
@ -4,6 +4,7 @@ mod util;
|
|||
mod context;
|
||||
mod factory;
|
||||
mod inner;
|
||||
mod note_expressions;
|
||||
mod param_units;
|
||||
mod view;
|
||||
mod wrapper;
|
||||
|
|
|
@ -12,6 +12,7 @@ use vst3_sys::base::{kInvalidArgument, kResultOk, tresult};
|
|||
use vst3_sys::vst::{IComponentHandler, RestartFlags};
|
||||
|
||||
use super::context::{WrapperGuiContext, WrapperProcessContext};
|
||||
use super::note_expressions::NoteExpressionController;
|
||||
use super::param_units::ParamUnits;
|
||||
use super::util::{ObjectPtr, VstPtr};
|
||||
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
|
||||
/// creating the process context
|
||||
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)`.
|
||||
/// 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
|
||||
|
@ -248,6 +254,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)),
|
||||
note_expression_controller: AtomicRefCell::new(NoteExpressionController::default()),
|
||||
input_param_changes: AtomicRefCell::new(BinaryHeap::with_capacity(
|
||||
if P::SAMPLE_ACCURATE_AUTOMATION {
|
||||
4096
|
||||
|
|
101
src/wrapper/vst3/note_expressions.rs
Normal file
101
src/wrapper/vst3/note_expressions.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,8 +8,8 @@ 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, IAudioProcessor, IComponent, IEditController, IEventList,
|
||||
IParamValueQueue, IParameterChanges, IUnitInfo, ProgramListInfo, TChar, UnitInfo,
|
||||
kNoProgramListId, kRootUnitId, EventTypes, IAudioProcessor, IComponent, IEditController,
|
||||
IEventList, IParamValueQueue, IParameterChanges, IUnitInfo, ProgramListInfo, TChar, UnitInfo,
|
||||
};
|
||||
use vst3_sys::VST3;
|
||||
use widestring::U16CStr;
|
||||
|
@ -746,6 +746,8 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
|||
|
||||
if P::MIDI_INPUT >= MidiConfig::Basic {
|
||||
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() {
|
||||
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;
|
||||
if event.type_ == vst3_sys::vst::EventTypes::kNoteOnEvent as u16 {
|
||||
if event.type_ == EventTypes::kNoteOnEvent as u16 {
|
||||
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 {
|
||||
timing,
|
||||
channel: event.channel as u8,
|
||||
note: event.pitch as u8,
|
||||
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;
|
||||
input_events.push_back(NoteEvent::NoteOff {
|
||||
timing,
|
||||
|
@ -782,9 +788,7 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
|||
note: event.pitch as u8,
|
||||
velocity: event.velocity,
|
||||
});
|
||||
} else if event.type_
|
||||
== vst3_sys::vst::EventTypes::kPolyPressureEvent as u16
|
||||
{
|
||||
} else if event.type_ == EventTypes::kPolyPressureEvent as u16 {
|
||||
let event = event.event.poly_pressure;
|
||||
input_events.push_back(NoteEvent::PolyPressure {
|
||||
timing,
|
||||
|
@ -792,6 +796,17 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
|||
note: event.pitch as u8,
|
||||
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
|
||||
|
|
Loading…
Add table
Reference in a new issue