diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index bb0e4253..b757f7d0 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -8,10 +8,13 @@ code then it will not be listed here. ## [2022-07-04] +- There is a new `NoteEvent::VoiceTerminated` event to let the host know a voice + has been terminated. This needs to be output by CLAP plugins that support + polyphonic modulation. +- Most `NoteEvent` variants now have an additional `voice_id` field. - 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/src/midi.rs b/src/midi.rs index b8024dce..f3e493e8 100644 --- a/src/midi.rs +++ b/src/midi.rs @@ -10,7 +10,10 @@ pub enum MidiConfig { /// The plugin will not have a note input port and will thus not receive any not events. None, /// The plugin receives note on/off events, pressure, and potentially a couple standardized - /// expression types depending on the plugin standard and host. + /// expression types depending on the plugin standard and host. If the plugin sets up + /// configuration for polyphonic modulation (see [`ClapPlugin`][crate::prelude::ClapPlugin]) and + /// assigns polyphonic modulation IDs to some of its parameters, then it will also receive + /// polyphonic modulation events. Basic, /// The plugin receives full MIDI CCs as well as pitch bend information. For VST3 plugins this /// involves adding 130*16 parameters to bind to the the 128 MIDI CCs, pitch bend, and channel @@ -55,6 +58,19 @@ pub enum NoteEvent { /// 127 levels available in MIDI. velocity: f32, }, + /// Sent by the plugin to the host to indicate that a voice has ended. This **needs** to be sent + /// when a voice terminates when using polyphonic modulation. Otherwise you can ignore this + /// event. + VoiceTerminated { + timing: u32, + /// The voice's unique identifier. Setting this allows a single voice to be terminated if + /// the plugin allows multiple overlapping voices for a single key. + voice_id: Option, + /// The note's channel, from 0 to 16, and the note's MIDI key number, from 0 to 127. + channel: u8, + /// The note's MIDI key number + note: u8, + }, /// A polyphonic note pressure/aftertouch event, available on [`MidiConfig::Basic`] and up. Not /// all hosts may support polyphonic aftertouch. /// @@ -200,6 +216,7 @@ impl NoteEvent { match &self { NoteEvent::NoteOn { timing, .. } => *timing, NoteEvent::NoteOff { timing, .. } => *timing, + NoteEvent::VoiceTerminated { timing, .. } => *timing, NoteEvent::PolyPressure { timing, .. } => *timing, NoteEvent::PolyVolume { timing, .. } => *timing, NoteEvent::PolyPan { timing, .. } => *timing, @@ -218,6 +235,7 @@ impl NoteEvent { match &self { NoteEvent::NoteOn { voice_id, .. } => *voice_id, NoteEvent::NoteOff { voice_id, .. } => *voice_id, + NoteEvent::VoiceTerminated { voice_id, .. } => *voice_id, NoteEvent::PolyPressure { voice_id, .. } => *voice_id, NoteEvent::PolyVolume { voice_id, .. } => *voice_id, NoteEvent::PolyPan { voice_id, .. } => *voice_id, @@ -353,7 +371,8 @@ impl NoteEvent { cc, (value * 127.0).round().clamp(0.0, 127.0) as u8, ]), - NoteEvent::PolyVolume { .. } + NoteEvent::VoiceTerminated { .. } + | NoteEvent::PolyVolume { .. } | NoteEvent::PolyPan { .. } | NoteEvent::PolyTuning { .. } | NoteEvent::PolyVibrato { .. } @@ -368,6 +387,7 @@ impl NoteEvent { match self { NoteEvent::NoteOn { timing, .. } => *timing -= samples, NoteEvent::NoteOff { timing, .. } => *timing -= samples, + NoteEvent::VoiceTerminated { timing, .. } => *timing -= samples, NoteEvent::PolyPressure { timing, .. } => *timing -= samples, NoteEvent::PolyVolume { timing, .. } => *timing -= samples, NoteEvent::PolyPan { timing, .. } => *timing -= samples, diff --git a/src/wrapper/clap/wrapper.rs b/src/wrapper/clap/wrapper.rs index 7b745b18..9f782087 100644 --- a/src/wrapper/clap/wrapper.rs +++ b/src/wrapper/clap/wrapper.rs @@ -4,14 +4,15 @@ use clap_sys::events::{ clap_event_header, clap_event_midi, clap_event_note, clap_event_note_expression, clap_event_param_gesture, clap_event_param_mod, clap_event_param_value, clap_event_transport, clap_input_events, clap_output_events, CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_IS_LIVE, - CLAP_EVENT_MIDI, CLAP_EVENT_NOTE_EXPRESSION, CLAP_EVENT_NOTE_OFF, CLAP_EVENT_NOTE_ON, - CLAP_EVENT_PARAM_GESTURE_BEGIN, CLAP_EVENT_PARAM_GESTURE_END, CLAP_EVENT_PARAM_MOD, - CLAP_EVENT_PARAM_VALUE, CLAP_EVENT_TRANSPORT, CLAP_NOTE_EXPRESSION_BRIGHTNESS, - CLAP_NOTE_EXPRESSION_EXPRESSION, CLAP_NOTE_EXPRESSION_PAN, CLAP_NOTE_EXPRESSION_PRESSURE, - CLAP_NOTE_EXPRESSION_TUNING, CLAP_NOTE_EXPRESSION_VIBRATO, CLAP_NOTE_EXPRESSION_VOLUME, - CLAP_TRANSPORT_HAS_BEATS_TIMELINE, CLAP_TRANSPORT_HAS_SECONDS_TIMELINE, - CLAP_TRANSPORT_HAS_TEMPO, CLAP_TRANSPORT_HAS_TIME_SIGNATURE, CLAP_TRANSPORT_IS_LOOP_ACTIVE, - CLAP_TRANSPORT_IS_PLAYING, CLAP_TRANSPORT_IS_RECORDING, CLAP_TRANSPORT_IS_WITHIN_PRE_ROLL, + CLAP_EVENT_MIDI, CLAP_EVENT_NOTE_END, CLAP_EVENT_NOTE_EXPRESSION, CLAP_EVENT_NOTE_OFF, + CLAP_EVENT_NOTE_ON, CLAP_EVENT_PARAM_GESTURE_BEGIN, CLAP_EVENT_PARAM_GESTURE_END, + CLAP_EVENT_PARAM_MOD, CLAP_EVENT_PARAM_VALUE, CLAP_EVENT_TRANSPORT, + CLAP_NOTE_EXPRESSION_BRIGHTNESS, CLAP_NOTE_EXPRESSION_EXPRESSION, CLAP_NOTE_EXPRESSION_PAN, + CLAP_NOTE_EXPRESSION_PRESSURE, CLAP_NOTE_EXPRESSION_TUNING, CLAP_NOTE_EXPRESSION_VIBRATO, + CLAP_NOTE_EXPRESSION_VOLUME, CLAP_TRANSPORT_HAS_BEATS_TIMELINE, + CLAP_TRANSPORT_HAS_SECONDS_TIMELINE, CLAP_TRANSPORT_HAS_TEMPO, + CLAP_TRANSPORT_HAS_TIME_SIGNATURE, CLAP_TRANSPORT_IS_LOOP_ACTIVE, CLAP_TRANSPORT_IS_PLAYING, + CLAP_TRANSPORT_IS_RECORDING, CLAP_TRANSPORT_IS_WITHIN_PRE_ROLL, }; use clap_sys::ext::audio_ports::{ clap_audio_port_info, clap_plugin_audio_ports, CLAP_AUDIO_PORT_IS_MAIN, CLAP_EXT_AUDIO_PORTS, @@ -956,6 +957,29 @@ impl Wrapper

{ clap_call! { out=>try_push(out, &event.header) } } + NoteEvent::VoiceTerminated { + timing: _, + voice_id, + channel, + note, + } if P::MIDI_OUTPUT >= MidiConfig::Basic => { + let event = clap_event_note { + header: clap_event_header { + size: mem::size_of::() as u32, + time, + space_id: CLAP_CORE_EVENT_SPACE_ID, + type_: CLAP_EVENT_NOTE_END, + flags: 0, + }, + note_id: voice_id.unwrap_or(-1), + port_index: 0, + channel: channel as i16, + key: note as i16, + velocity: 0.0, + }; + + clap_call! { out=>try_push(out, &event.header) } + } NoteEvent::PolyPressure { timing: _, voice_id, diff --git a/src/wrapper/vst3/wrapper.rs b/src/wrapper/vst3/wrapper.rs index 5219c319..cd83af86 100644 --- a/src/wrapper/vst3/wrapper.rs +++ b/src/wrapper/vst3/wrapper.rs @@ -1587,6 +1587,13 @@ impl IAudioProcessor for Wrapper

{ tuning: 0.0, }; } + // VST3 does not support or need these events, but they should also not + // trigger a debug assertion failure in NIH-plug + NoteEvent::VoiceTerminated { .. } + if P::MIDI_OUTPUT >= MidiConfig::Basic => + { + continue; + } NoteEvent::PolyPressure { timing: _, voice_id,