1
0
Fork 0

Add voice ID fields for all non-MIDI note events

This will be useful when adding polyphonic modulation.
This commit is contained in:
Robbert van der Helm 2022-07-04 18:31:59 +02:00
parent f11b3c1a03
commit a2f8a9bebf
6 changed files with 209 additions and 22 deletions

View file

@ -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]

View file

@ -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,

View file

@ -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<i32>,
/// 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<i32>,
/// 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<i32>,
/// 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<i32>,
/// 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<i32>,
/// 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<i32>,
/// 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<i32>,
/// 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<i32>,
/// 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<i32>,
/// 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<i32> {
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<Self, u8> {
// 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,

View file

@ -909,6 +909,7 @@ impl<P: ClapPlugin> Wrapper<P> {
let push_successful = match event {
NoteEvent::NoteOn {
timing: _,
voice_id,
channel,
note,
velocity,
@ -922,7 +923,7 @@ impl<P: ClapPlugin> Wrapper<P> {
// 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<P: ClapPlugin> Wrapper<P> {
}
NoteEvent::NoteOff {
timing: _,
voice_id,
channel,
note,
velocity,
@ -945,7 +947,7 @@ impl<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
}
NoteEvent::PolyPressure {
timing: _,
voice_id,
channel,
note,
pressure,
@ -969,7 +972,7 @@ impl<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
}
NoteEvent::PolyVolume {
timing: _,
voice_id,
channel,
note,
gain,
@ -993,7 +997,7 @@ impl<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
}
NoteEvent::PolyPan {
timing: _,
voice_id,
channel,
note,
pan,
@ -1017,7 +1022,7 @@ impl<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
}
NoteEvent::PolyTuning {
timing: _,
voice_id,
channel,
note,
tuning,
@ -1041,7 +1047,7 @@ impl<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
}
NoteEvent::PolyVibrato {
timing: _,
voice_id,
channel,
note,
vibrato,
@ -1065,7 +1072,7 @@ impl<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
}
NoteEvent::PolyExpression {
timing: _,
voice_id,
channel,
note,
expression,
@ -1089,7 +1097,7 @@ impl<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
}
NoteEvent::PolyBrightness {
timing: _,
voice_id,
channel,
note,
brightness,
@ -1113,7 +1122,7 @@ impl<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
// 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<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
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,

View file

@ -105,7 +105,8 @@ impl NoteExpressionController {
timing: u32,
event: &NoteExpressionValueEvent,
) -> Option<NoteEvent> {
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,

View file

@ -1162,6 +1162,11 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
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<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
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<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
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<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
// 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<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
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<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
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<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
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) => {