From a2f8a9bebf61cf888d687dba2676fd6ee53a4115 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 4 Jul 2022 18:31:59 +0200 Subject: [PATCH] Add voice ID fields for all non-MIDI note events This will be useful when adding polyphonic modulation. --- BREAKING_CHANGES.md | 1 + plugins/examples/midi_inverter/src/lib.rs | 18 ++++++ src/midi.rs | 54 ++++++++++++++++ src/wrapper/clap/wrapper.rs | 72 ++++++++++++++++++--- src/wrapper/vst3/note_expressions.rs | 9 ++- src/wrapper/vst3/wrapper.rs | 77 +++++++++++++++++++---- 6 files changed, 209 insertions(+), 22 deletions(-) diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index 9926f38a..bb0e4253 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -11,6 +11,7 @@ code then it will not be listed here. - The `CLAP_DESCRIPTION`, `CLAP_MANUAL_URL`, and `CLAP_SUPPORT_URL` associated constants from the `ClapPlugin` are now optional and have the type `Option<&'static str>` instead of `&'static str`. +- Most `NoteEvent` variants now have an additional `voice_id` field. ## [2022-07-02] diff --git a/plugins/examples/midi_inverter/src/lib.rs b/plugins/examples/midi_inverter/src/lib.rs index cfbddce3..49d70873 100644 --- a/plugins/examples/midi_inverter/src/lib.rs +++ b/plugins/examples/midi_inverter/src/lib.rs @@ -49,99 +49,117 @@ impl Plugin for MidiInverter { match event { NoteEvent::NoteOn { timing, + voice_id, channel, note, velocity, } => context.send_event(NoteEvent::NoteOn { timing, + voice_id, channel: 15 - channel, note: 127 - note, velocity: 1.0 - velocity, }), NoteEvent::NoteOff { timing, + voice_id, channel, note, velocity, } => context.send_event(NoteEvent::NoteOff { timing, + voice_id, channel: 15 - channel, note: 127 - note, velocity: 1.0 - velocity, }), NoteEvent::PolyPressure { timing, + voice_id, channel, note, pressure, } => context.send_event(NoteEvent::PolyPressure { timing, + voice_id, channel: 15 - channel, note: 127 - note, pressure: 1.0 - pressure, }), NoteEvent::PolyVolume { timing, + voice_id, channel, note, gain, } => context.send_event(NoteEvent::PolyVolume { timing, + voice_id, channel: 15 - channel, note: 127 - note, gain: 1.0 - gain, }), NoteEvent::PolyPan { timing, + voice_id, channel, note, pan, } => context.send_event(NoteEvent::PolyPan { timing, + voice_id, channel: 15 - channel, note: 127 - note, pan: 1.0 - pan, }), NoteEvent::PolyTuning { timing, + voice_id, channel, note, tuning, } => context.send_event(NoteEvent::PolyTuning { timing, + voice_id, channel: 15 - channel, note: 127 - note, tuning: 1.0 - tuning, }), NoteEvent::PolyVibrato { timing, + voice_id, channel, note, vibrato, } => context.send_event(NoteEvent::PolyVibrato { timing, + voice_id, channel: 15 - channel, note: 127 - note, vibrato: 1.0 - vibrato, }), NoteEvent::PolyExpression { timing, + voice_id, channel, note, expression, } => context.send_event(NoteEvent::PolyExpression { timing, + voice_id, channel: 15 - channel, note: 127 - note, expression: 1.0 - expression, }), NoteEvent::PolyBrightness { timing, + voice_id, channel, note, brightness, } => context.send_event(NoteEvent::PolyBrightness { timing, + voice_id, channel: 15 - channel, note: 127 - note, brightness: 1.0 - brightness, diff --git a/src/midi.rs b/src/midi.rs index 288824e3..b8024dce 100644 --- a/src/midi.rs +++ b/src/midi.rs @@ -30,6 +30,9 @@ pub enum NoteEvent { /// A note on event, available on [`MidiConfig::Basic`] and up. NoteOn { timing: u32, + /// A unique identifier for this note, if available. Using this to refer to a note is + /// required when allowing overlapping voices for CLAP plugins. + voice_id: Option, /// The note's channel, from 0 to 16. channel: u8, /// The note's MIDI key number, from 0 to 127. @@ -41,6 +44,9 @@ pub enum NoteEvent { /// A note off event, available on [`MidiConfig::Basic`] and up. NoteOff { timing: u32, + /// A unique identifier for this note, if available. Using this to refer to a note is + /// required when allowing overlapping voices for CLAP plugins. + voice_id: Option, /// The note's channel, from 0 to 16. channel: u8, /// The note's MIDI key number, from 0 to 127. @@ -59,6 +65,9 @@ pub enum NoteEvent { /// you may manually combine the polyphonic key pressure and MPE channel pressure. PolyPressure { timing: u32, + /// A unique identifier for this note, if available. Using this to refer to a note is + /// required when allowing overlapping voices for CLAP plugins. + voice_id: Option, /// The note's channel, from 0 to 16. channel: u8, /// The note's MIDI key number, from 0 to 127. @@ -70,6 +79,9 @@ pub enum NoteEvent { /// support these expressions. PolyVolume { timing: u32, + /// A unique identifier for this note, if available. Using this to refer to a note is + /// required when allowing overlapping voices for CLAP plugins. + voice_id: Option, /// The note's channel, from 0 to 16. channel: u8, /// The note's MIDI key number, from 0 to 127. @@ -81,6 +93,9 @@ pub enum NoteEvent { /// support these expressions. PolyPan { timing: u32, + /// A unique identifier for this note, if available. Using this to refer to a note is + /// required when allowing overlapping voices for CLAP plugins. + voice_id: Option, /// The note's channel, from 0 to 16. channel: u8, /// The note's MIDI key number, from 0 to 127. @@ -93,6 +108,9 @@ pub enum NoteEvent { /// these expressions. PolyTuning { timing: u32, + /// A unique identifier for this note, if available. Using this to refer to a note is + /// required when allowing overlapping voices for CLAP plugins. + voice_id: Option, /// The note's channel, from 0 to 16. channel: u8, /// The note's MIDI key number, from 0 to 127. @@ -104,6 +122,9 @@ pub enum NoteEvent { /// these expressions. PolyVibrato { timing: u32, + /// A unique identifier for this note, if available. Using this to refer to a note is + /// required when allowing overlapping voices for CLAP plugins. + voice_id: Option, /// The note's channel, from 0 to 16. channel: u8, /// The note's MIDI key number, from 0 to 127. @@ -115,6 +136,9 @@ pub enum NoteEvent { /// [`MidiConfig::Basic`] and up. Not all hosts may support these expressions. PolyExpression { timing: u32, + /// A unique identifier for this note, if available. Using this to refer to a note is + /// required when allowing overlapping voices for CLAP plugins. + voice_id: Option, /// The note's channel, from 0 to 16. channel: u8, /// The note's MIDI key number, from 0 to 127. @@ -126,6 +150,9 @@ pub enum NoteEvent { /// these expressions. PolyBrightness { timing: u32, + /// A unique identifier for this note, if available. Using this to refer to a note is + /// required when allowing overlapping voices for CLAP plugins. + voice_id: Option, /// The note's channel, from 0 to 16. channel: u8, /// The note's MIDI key number, from 0 to 127. @@ -186,6 +213,24 @@ impl NoteEvent { } } + /// Returns the event's voice ID, if it has any. + pub fn voice_id(&self) -> Option { + match &self { + NoteEvent::NoteOn { voice_id, .. } => *voice_id, + NoteEvent::NoteOff { voice_id, .. } => *voice_id, + NoteEvent::PolyPressure { voice_id, .. } => *voice_id, + NoteEvent::PolyVolume { voice_id, .. } => *voice_id, + NoteEvent::PolyPan { voice_id, .. } => *voice_id, + NoteEvent::PolyTuning { voice_id, .. } => *voice_id, + NoteEvent::PolyVibrato { voice_id, .. } => *voice_id, + NoteEvent::PolyExpression { voice_id, .. } => *voice_id, + NoteEvent::PolyBrightness { voice_id, .. } => *voice_id, + NoteEvent::MidiChannelPressure { .. } => None, + NoteEvent::MidiPitchBend { .. } => None, + NoteEvent::MidiCC { .. } => None, + } + } + /// Parse MIDI into a [`NoteEvent`]. Will return `Err(event_type)` if the parsing failed. pub fn from_midi(timing: u32, midi_data: [u8; 3]) -> Result { // TODO: Maybe add special handling for 14-bit CCs and RPN messages at some @@ -195,18 +240,21 @@ impl NoteEvent { match event_type { midi::NOTE_ON => Ok(NoteEvent::NoteOn { timing, + voice_id: None, channel, note: midi_data[1], velocity: midi_data[2] as f32 / 127.0, }), midi::NOTE_OFF => Ok(NoteEvent::NoteOff { timing, + voice_id: None, channel, note: midi_data[1], velocity: midi_data[2] as f32 / 127.0, }), midi::POLYPHONIC_KEY_PRESSURE => Ok(NoteEvent::PolyPressure { timing, + voice_id: None, channel, note: midi_data[1], pressure: midi_data[2] as f32 / 127.0, @@ -239,6 +287,7 @@ impl NoteEvent { match self { NoteEvent::NoteOn { timing: _, + voice_id: _, channel, note, velocity, @@ -249,6 +298,7 @@ impl NoteEvent { ]), NoteEvent::NoteOff { timing: _, + voice_id: _, channel, note, velocity, @@ -259,6 +309,7 @@ impl NoteEvent { ]), NoteEvent::PolyPressure { timing: _, + voice_id: _, channel, note, pressure, @@ -341,6 +392,7 @@ mod tests { fn test_note_on_midi_conversion() { let event = NoteEvent::NoteOn { timing: TIMING, + voice_id: None, channel: 1, note: 2, // The value will be rounded in the conversion to MIDI, hence this overly specific value @@ -357,6 +409,7 @@ mod tests { fn test_note_off_midi_conversion() { let event = NoteEvent::NoteOff { timing: TIMING, + voice_id: None, channel: 1, note: 2, velocity: 0.6929134, @@ -372,6 +425,7 @@ mod tests { fn test_poly_pressure_midi_conversion() { let event = NoteEvent::PolyPressure { timing: TIMING, + voice_id: None, channel: 1, note: 2, pressure: 0.6929134, diff --git a/src/wrapper/clap/wrapper.rs b/src/wrapper/clap/wrapper.rs index 63c45990..7b745b18 100644 --- a/src/wrapper/clap/wrapper.rs +++ b/src/wrapper/clap/wrapper.rs @@ -909,6 +909,7 @@ impl Wrapper

{ let push_successful = match event { NoteEvent::NoteOn { timing: _, + voice_id, channel, note, velocity, @@ -922,7 +923,7 @@ impl Wrapper

{ // We don't have a way to denote live events flags: 0, }, - note_id: -1, + note_id: voice_id.unwrap_or(-1), port_index: 0, channel: channel as i16, key: note as i16, @@ -933,6 +934,7 @@ impl Wrapper

{ } NoteEvent::NoteOff { timing: _, + voice_id, channel, note, velocity, @@ -945,7 +947,7 @@ impl Wrapper

{ type_: CLAP_EVENT_NOTE_OFF, flags: 0, }, - note_id: -1, + note_id: voice_id.unwrap_or(-1), port_index: 0, channel: channel as i16, key: note as i16, @@ -956,6 +958,7 @@ impl Wrapper

{ } NoteEvent::PolyPressure { timing: _, + voice_id, channel, note, pressure, @@ -969,7 +972,7 @@ impl Wrapper

{ flags: 0, }, expression_id: CLAP_NOTE_EXPRESSION_PRESSURE, - note_id: -1, + note_id: voice_id.unwrap_or(-1), port_index: 0, channel: channel as i16, key: note as i16, @@ -980,6 +983,7 @@ impl Wrapper

{ } NoteEvent::PolyVolume { timing: _, + voice_id, channel, note, gain, @@ -993,7 +997,7 @@ impl Wrapper

{ flags: 0, }, expression_id: CLAP_NOTE_EXPRESSION_VOLUME, - note_id: -1, + note_id: voice_id.unwrap_or(-1), port_index: 0, channel: channel as i16, key: note as i16, @@ -1004,6 +1008,7 @@ impl Wrapper

{ } NoteEvent::PolyPan { timing: _, + voice_id, channel, note, pan, @@ -1017,7 +1022,7 @@ impl Wrapper

{ flags: 0, }, expression_id: CLAP_NOTE_EXPRESSION_PAN, - note_id: -1, + note_id: voice_id.unwrap_or(-1), port_index: 0, channel: channel as i16, key: note as i16, @@ -1028,6 +1033,7 @@ impl Wrapper

{ } NoteEvent::PolyTuning { timing: _, + voice_id, channel, note, tuning, @@ -1041,7 +1047,7 @@ impl Wrapper

{ flags: 0, }, expression_id: CLAP_NOTE_EXPRESSION_TUNING, - note_id: -1, + note_id: voice_id.unwrap_or(-1), port_index: 0, channel: channel as i16, key: note as i16, @@ -1052,6 +1058,7 @@ impl Wrapper

{ } NoteEvent::PolyVibrato { timing: _, + voice_id, channel, note, vibrato, @@ -1065,7 +1072,7 @@ impl Wrapper

{ flags: 0, }, expression_id: CLAP_NOTE_EXPRESSION_VIBRATO, - note_id: -1, + note_id: voice_id.unwrap_or(-1), port_index: 0, channel: channel as i16, key: note as i16, @@ -1076,6 +1083,7 @@ impl Wrapper

{ } NoteEvent::PolyExpression { timing: _, + voice_id, channel, note, expression, @@ -1089,7 +1097,7 @@ impl Wrapper

{ flags: 0, }, expression_id: CLAP_NOTE_EXPRESSION_EXPRESSION, - note_id: -1, + note_id: voice_id.unwrap_or(-1), port_index: 0, channel: channel as i16, key: note as i16, @@ -1100,6 +1108,7 @@ impl Wrapper

{ } NoteEvent::PolyBrightness { timing: _, + voice_id, channel, note, brightness, @@ -1113,7 +1122,7 @@ impl Wrapper

{ flags: 0, }, expression_id: CLAP_NOTE_EXPRESSION_BRIGHTNESS, - note_id: -1, + note_id: voice_id.unwrap_or(-1), port_index: 0, channel: channel as i16, key: note as i16, @@ -1264,6 +1273,11 @@ impl Wrapper

{ // When splitting up the buffer for sample accurate automation all events // should be relative to the block timing: raw_event.time - current_sample_idx as u32, + voice_id: if event.note_id != -1 { + Some(event.note_id) + } else { + None + }, channel: event.channel as u8, note: event.key as u8, velocity: event.velocity as f32, @@ -1277,6 +1291,11 @@ impl Wrapper

{ let event = &*(event as *const clap_event_note); input_events.push_back(NoteEvent::NoteOff { timing: raw_event.time - current_sample_idx as u32, + voice_id: if event.note_id != -1 { + Some(event.note_id) + } else { + None + }, channel: event.channel as u8, note: event.key as u8, velocity: event.velocity as f32, @@ -1293,6 +1312,11 @@ impl Wrapper

{ CLAP_NOTE_EXPRESSION_PRESSURE => { input_events.push_back(NoteEvent::PolyPressure { timing: raw_event.time - current_sample_idx as u32, + voice_id: if event.note_id != -1 { + Some(event.note_id) + } else { + None + }, channel: event.channel as u8, note: event.key as u8, pressure: event.value as f32, @@ -1301,6 +1325,11 @@ impl Wrapper

{ CLAP_NOTE_EXPRESSION_VOLUME => { input_events.push_back(NoteEvent::PolyVolume { timing: raw_event.time - current_sample_idx as u32, + voice_id: if event.note_id != -1 { + Some(event.note_id) + } else { + None + }, channel: event.channel as u8, note: event.key as u8, gain: event.value as f32, @@ -1309,6 +1338,11 @@ impl Wrapper

{ CLAP_NOTE_EXPRESSION_PAN => { input_events.push_back(NoteEvent::PolyPan { timing: raw_event.time - current_sample_idx as u32, + voice_id: if event.note_id != -1 { + Some(event.note_id) + } else { + None + }, channel: event.channel as u8, note: event.key as u8, // In CLAP this value goes from [0, 1] instead of [-1, 1] @@ -1318,6 +1352,11 @@ impl Wrapper

{ CLAP_NOTE_EXPRESSION_TUNING => { input_events.push_back(NoteEvent::PolyTuning { timing: raw_event.time - current_sample_idx as u32, + voice_id: if event.note_id != -1 { + Some(event.note_id) + } else { + None + }, channel: event.channel as u8, note: event.key as u8, tuning: event.value as f32, @@ -1326,6 +1365,11 @@ impl Wrapper

{ CLAP_NOTE_EXPRESSION_VIBRATO => { input_events.push_back(NoteEvent::PolyVibrato { timing: raw_event.time - current_sample_idx as u32, + voice_id: if event.note_id != -1 { + Some(event.note_id) + } else { + None + }, channel: event.channel as u8, note: event.key as u8, vibrato: event.value as f32, @@ -1334,6 +1378,11 @@ impl Wrapper

{ CLAP_NOTE_EXPRESSION_EXPRESSION => { input_events.push_back(NoteEvent::PolyExpression { timing: raw_event.time - current_sample_idx as u32, + voice_id: if event.note_id != -1 { + Some(event.note_id) + } else { + None + }, channel: event.channel as u8, note: event.key as u8, expression: event.value as f32, @@ -1342,6 +1391,11 @@ impl Wrapper

{ CLAP_NOTE_EXPRESSION_BRIGHTNESS => { input_events.push_back(NoteEvent::PolyBrightness { timing: raw_event.time - current_sample_idx as u32, + voice_id: if event.note_id != -1 { + Some(event.note_id) + } else { + None + }, channel: event.channel as u8, note: event.key as u8, brightness: event.value as f32, diff --git a/src/wrapper/vst3/note_expressions.rs b/src/wrapper/vst3/note_expressions.rs index 714367e5..4d0f6200 100644 --- a/src/wrapper/vst3/note_expressions.rs +++ b/src/wrapper/vst3/note_expressions.rs @@ -105,7 +105,8 @@ impl NoteExpressionController { timing: u32, event: &NoteExpressionValueEvent, ) -> Option { - let (_, note, channel) = *self + // 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)?; @@ -113,6 +114,7 @@ impl NoteExpressionController { 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 @@ -121,6 +123,7 @@ impl NoteExpressionController { }), PAN_EXPRESSION_ID => Some(NoteEvent::PolyPan { timing, + voice_id: Some(note_id), channel, note, // Our panning expressions are symmetrical around 0 @@ -128,6 +131,7 @@ impl NoteExpressionController { }), 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 @@ -136,18 +140,21 @@ impl NoteExpressionController { }), 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, diff --git a/src/wrapper/vst3/wrapper.rs b/src/wrapper/vst3/wrapper.rs index df52e11f..5219c319 100644 --- a/src/wrapper/vst3/wrapper.rs +++ b/src/wrapper/vst3/wrapper.rs @@ -1162,6 +1162,11 @@ impl IAudioProcessor for Wrapper

{ timing, event: NoteEvent::NoteOn { timing, + voice_id: if event.note_id != -1 { + Some(event.note_id) + } else { + None + }, channel: event.channel as u8, note: event.pitch as u8, velocity: event.velocity, @@ -1173,6 +1178,11 @@ impl IAudioProcessor for Wrapper

{ timing, event: NoteEvent::NoteOff { timing, + voice_id: if event.note_id != -1 { + Some(event.note_id) + } else { + None + }, channel: event.channel as u8, note: event.pitch as u8, velocity: event.velocity, @@ -1184,6 +1194,11 @@ impl IAudioProcessor for Wrapper

{ timing, event: NoteEvent::PolyPressure { timing, + voice_id: if event.note_id != -1 { + Some(event.note_id) + } else { + None + }, channel: event.channel as u8, note: event.pitch as u8, pressure: event.pressure, @@ -1531,9 +1546,13 @@ impl IAudioProcessor for Wrapper

{ // There's also a ppqPos field, but uh how about no vst3_event.sample_offset = event.timing() as i32 + block_start as i32; + // `voice_id.onwrap_or(|| ...)` triggers + // https://github.com/rust-lang/rust-clippy/issues/8522 + #[allow(clippy::unnecessary_lazy_evaluations)] match event { NoteEvent::NoteOn { timing: _, + voice_id, channel, note, velocity, @@ -1547,11 +1566,13 @@ impl IAudioProcessor for Wrapper

{ 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, + note_id: voice_id + .unwrap_or_else(|| ((channel as i32) << 8) | note as i32), }; } NoteEvent::NoteOff { timing: _, + voice_id, channel, note, velocity, @@ -1561,12 +1582,14 @@ impl IAudioProcessor for Wrapper

{ channel: channel as i16, pitch: note as i16, velocity, - note_id: ((channel as i32) << 8) | note as i32, + note_id: voice_id + .unwrap_or_else(|| ((channel as i32) << 8) | note as i32), tuning: 0.0, }; } NoteEvent::PolyPressure { timing: _, + voice_id, channel, note, pressure, @@ -1575,20 +1598,50 @@ impl IAudioProcessor for Wrapper

{ vst3_event.event.poly_pressure = PolyPressureEvent { channel: channel as i16, pitch: note as i16, - note_id: ((channel as i32) << 8) | note as i32, + note_id: voice_id + .unwrap_or_else(|| ((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 => - { + event @ (NoteEvent::PolyVolume { + voice_id, + channel, + note, + .. + } + | NoteEvent::PolyPan { + voice_id, + channel, + note, + .. + } + | NoteEvent::PolyTuning { + voice_id, + channel, + note, + .. + } + | NoteEvent::PolyVibrato { + voice_id, + channel, + note, + .. + } + | NoteEvent::PolyExpression { + voice_id, + channel, + note, + .. + } + | NoteEvent::PolyBrightness { + voice_id, + channel, + note, + .. + }) if P::MIDI_OUTPUT >= MidiConfig::Basic => { match NoteExpressionController::translate_event_reverse( - ((channel as i32) << 8) | note as i32, + voice_id + .unwrap_or_else(|| ((channel as i32) << 8) | note as i32), &event, ) { Some(translated_event) => {