1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
//! 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::prelude::{NoteEvent, SysExMessage};
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;
/// `kVolumeTypeID`
pub const VOLUME_EXPRESSION_ID: u32 = 0;
/// `kPanTypeId`
pub const PAN_EXPRESSION_ID: u32 = 1;
/// `kTuningTypeID`
pub const TUNING_EXPRESSION_ID: u32 = 2;
/// `kVibratoTypeID`
pub const VIBRATO_EXPRESSION_ID: u32 = 3;
/// `kExpressionTypeID`
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 expressions 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
/// 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,
}
/// 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) {
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<S: SysExMessage>(
&self,
timing: u32,
event: &NoteExpressionValueEvent,
) -> Option<NoteEvent<S>> {
// We're calling it a voice ID, VST3 (and CLAP) calls it a note ID
let (note_id, note, channel) = *self
.note_ids
.iter()
.find(|(note_id, _, _)| *note_id == event.note_id)?;
match event.type_id {
VOLUME_EXPRESSION_ID => Some(NoteEvent::PolyVolume {
timing,
voice_id: Some(note_id),
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,
}),
PAN_EXPRESSION_ID => Some(NoteEvent::PolyPan {
timing,
voice_id: Some(note_id),
channel,
note,
// Our panning expressions are symmetrical around 0
pan: (event.value as f32 * 2.0) - 1.0,
}),
TUNING_EXPRESSION_ID => Some(NoteEvent::PolyTuning {
timing,
voice_id: Some(note_id),
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),
}),
VIBRATO_EXPRESSION_ID => Some(NoteEvent::PolyVibrato {
timing,
voice_id: Some(note_id),
channel,
note,
vibrato: event.value as f32,
}),
EXPRESSION_EXPRESSION_ID => Some(NoteEvent::PolyBrightness {
timing,
voice_id: Some(note_id),
channel,
note,
brightness: event.value as f32,
}),
BRIGHTNESS_EXPRESSION_ID => Some(NoteEvent::PolyExpression {
timing,
voice_id: Some(note_id),
channel,
note,
expression: event.value as f32,
}),
_ => 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<impl SysExMessage>,
) -> Option<NoteExpressionValueEvent> {
match &event {
NoteEvent::PolyVolume { gain, .. } => Some(NoteExpressionValueEvent {
type_id: VOLUME_EXPRESSION_ID,
note_id,
value: *gain as f64 / 4.0,
}),
NoteEvent::PolyPan { pan, .. } => Some(NoteExpressionValueEvent {
type_id: PAN_EXPRESSION_ID,
note_id,
value: (*pan as f64 + 1.0) / 2.0,
}),
NoteEvent::PolyTuning { tuning, .. } => Some(NoteExpressionValueEvent {
type_id: TUNING_EXPRESSION_ID,
note_id,
value: (*tuning as f64 / 240.0) + 0.5,
}),
NoteEvent::PolyVibrato { vibrato, .. } => Some(NoteExpressionValueEvent {
type_id: VIBRATO_EXPRESSION_ID,
note_id,
value: *vibrato as f64,
}),
NoteEvent::PolyExpression { expression, .. } => Some(NoteExpressionValueEvent {
type_id: EXPRESSION_EXPRESSION_ID,
note_id,
value: *expression as f64,
}),
NoteEvent::PolyBrightness { brightness, .. } => Some(NoteExpressionValueEvent {
type_id: BRIGHTNESS_EXPRESSION_ID,
note_id,
value: *brightness as f64,
}),
_ => None,
}
}
}