1
0
Fork 0

Properly define all predefined note expressions

This is apparently how you're supposed to do it in VST3. How would you
know? You'd ask Steinberg.
This commit is contained in:
Robbert van der Helm 2022-05-05 15:35:14 +02:00
parent 06c5e4b04a
commit 7cb671319e
2 changed files with 85 additions and 36 deletions

View file

@ -25,6 +25,42 @@ pub const EXPRESSION_EXPRESSION_ID: u32 = 4;
/// `kBrightnessTypeID`
pub const BRIGHTNESS_EXPRESSION_ID: u32 = 5;
/// The note expressions we support. It's completely undocumented, but apparently VST3 plugins need
/// to specifically define a custom note expression for the predefined note expressiosn for them to
/// work.
pub const KNOWN_NOTE_EXPRESSIONS: [NoteExpressionInfo; 6] = [
NoteExpressionInfo {
type_id: VOLUME_EXPRESSION_ID,
title: "Volume",
unit: "dB",
},
NoteExpressionInfo {
type_id: PAN_EXPRESSION_ID,
title: "Pan",
unit: "",
},
NoteExpressionInfo {
type_id: TUNING_EXPRESSION_ID,
title: "Tuning",
unit: "semitones",
},
NoteExpressionInfo {
type_id: VIBRATO_EXPRESSION_ID,
title: "Vibrato",
unit: "",
},
NoteExpressionInfo {
type_id: EXPRESSION_EXPRESSION_ID,
title: "Expression",
unit: "",
},
NoteExpressionInfo {
type_id: BRIGHTNESS_EXPRESSION_ID,
title: "Brightness",
unit: "",
},
];
/// 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
@ -39,24 +75,21 @@ pub struct NoteExpressionController {
note_ids_idx: usize,
}
impl NoteExpressionController {
/// Returns `true` if the VST3 expression type ID is one of the predefiend types we support.
/// This is only needed as a workaround for a Bitwig bug where it only sends us note expressions
/// if we explicitly return `kResultOk` on
/// `INoteExpressionController::get_note_expression_info()` with malformed expression index
/// arguments.
pub const fn known_expression_type_id(type_id: u32) -> bool {
matches!(
type_id,
VOLUME_EXPRESSION_ID
| PAN_EXPRESSION_ID
| TUNING_EXPRESSION_ID
| VIBRATO_EXPRESSION_ID
| EXPRESSION_EXPRESSION_ID
| BRIGHTNESS_EXPRESSION_ID
)
}
/// This is used to register a (predefined) note expression in the `INoteExpressionController`. The
/// data is kept in this module to keep everything related to VST3 note expressions in one place.
///
/// This does not contain value descriptions because those are also predefined as normalized `[0,
/// 1]` values.
pub struct NoteExpressionInfo {
/// The predefined VST3 note expression type ID for this note expression.
pub type_id: u32,
/// The title for the note expression. Also used for the short title because why not.
pub title: &'static str,
/// The unit for the note expression.
pub unit: &'static str,
}
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) {

View file

@ -8,10 +8,11 @@ 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, Event, EventTypes, IAudioProcessor, IComponent, IEditController,
IEventList, IMidiMapping, INoteExpressionController, IParamValueQueue, IParameterChanges,
IUnitInfo, LegacyMidiCCOutEvent, NoteExpressionTypeInfo, NoteOffEvent, NoteOnEvent,
ParameterFlags, PolyPressureEvent, ProgramListInfo, TChar, UnitInfo,
kNoParamId, kNoParentUnitId, kNoProgramListId, kRootUnitId, Event, EventTypes, IAudioProcessor,
IComponent, IEditController, IEventList, IMidiMapping, INoteExpressionController,
IParamValueQueue, IParameterChanges, IUnitInfo, LegacyMidiCCOutEvent, NoteExpressionTypeInfo,
NoteExpressionValueDescription, NoteOffEvent, NoteOnEvent, ParameterFlags, PolyPressureEvent,
ProgramListInfo, TChar, UnitInfo,
};
use vst3_sys::VST3;
use widestring::U16CStr;
@ -29,7 +30,7 @@ use crate::util::permit_alloc;
use crate::wrapper::state;
use crate::wrapper::util::process_wrapper;
use crate::wrapper::vst3::inner::ProcessEvent;
use crate::wrapper::vst3::note_expressions::NoteExpressionController;
use crate::wrapper::vst3::note_expressions::{self, NoteExpressionController};
use crate::wrapper::vst3::util::{VST3_MIDI_CHANNELS, VST3_MIDI_PARAMS_END};
// Alias needed for the VST3 attribute macro
@ -1435,10 +1436,9 @@ impl<P: Vst3Plugin> IUnitInfo for Wrapper<P> {
impl<P: Vst3Plugin> INoteExpressionController for Wrapper<P> {
unsafe fn get_note_expression_count(&self, bus_idx: i32, _channel: i16) -> i32 {
// NOTE: We don't have any custom note expressions. But Bitwig won't send us the predefined
// note expressions unless we pretend we do.
// Apparently you need to define the predefined note expressions. Thanks VST3.
if P::MIDI_INPUT >= MidiConfig::Basic && bus_idx == 0 {
1
note_expressions::KNOWN_NOTE_EXPRESSIONS.len() as i32
} else {
0
}
@ -1451,22 +1451,38 @@ impl<P: Vst3Plugin> INoteExpressionController for Wrapper<P> {
note_expression_idx: i32,
info: *mut NoteExpressionTypeInfo,
) -> tresult {
if P::MIDI_INPUT < MidiConfig::Basic || bus_idx != 0 {
if P::MIDI_INPUT < MidiConfig::Basic
|| bus_idx != 0
|| !(0..note_expressions::KNOWN_NOTE_EXPRESSIONS.len() as i32)
.contains(&note_expression_idx)
{
return kInvalidArgument;
}
check_null_ptr!(info);
// NOTE: As mentioned above, this is only a workaround for a Bitwig bug. We don't have any
// custom note expressions, and the IDs passed to this function are the type IDs which
// is incorrect. We won't even bother filling in the information. This argument is of
// course also incorrect, as these expression indices are supposed to be linear
// indices starting at 0, while Bitwig queries this with 0, 1, 2, and 5.
if NoteExpressionController::known_expression_type_id(note_expression_idx as u32) {
kResultOk
} else {
kResultFalse
}
*info = mem::zeroed();
let info = &mut *info;
let note_expression_info =
&note_expressions::KNOWN_NOTE_EXPRESSIONS[note_expression_idx as usize];
info.type_id = note_expression_info.type_id;
u16strlcpy(&mut info.title, note_expression_info.title);
u16strlcpy(&mut info.short_title, note_expression_info.title);
u16strlcpy(&mut info.units, note_expression_info.unit);
info.unit_id = kNoParentUnitId;
// This should not be needed since they're predefined, but then again you'd think you also
// wouldn't need to define predefined note expressions now do you?
info.value_desc = NoteExpressionValueDescription {
default_value: 0.5,
min: 0.0,
max: 1.0,
step_count: 0,
};
info.id = kNoParamId;
info.flags = 1 << 2; // kIsAbsolute
kResultOk
}
unsafe fn get_note_expression_string_by_value(