Skip to main content

nice_plug_core/
midi.rs

1//! Constants and definitions surrounding MIDI support.
2
3use midi_consts::channel_event as midi;
4
5use self::sysex::SysExMessage;
6use crate::{nice_trace, plugin::Plugin};
7
8pub mod sysex;
9
10pub use midi_consts::channel_event::control_change;
11
12/// A plugin-specific note event type.
13///
14/// The reason why this is defined like this instead of parameterizing `NoteEvent` with `P` is
15/// because deriving trait bounds requires all of the plugin's generic parameters to implement those
16/// traits. And we can't require `P` to implement things like `Clone`.
17///
18/// <https://github.com/rust-lang/rust/issues/26925>
19pub type PluginNoteEvent<P> = NoteEvent<<P as Plugin>::SysExMessage>;
20
21/// Determines which note events a plugin can send and receive.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
23pub enum MidiConfig {
24    /// The plugin will not have a note input or output port and will thus not receive any not
25    /// events.
26    None,
27    /// The plugin receives note on/off/choke events, pressure, and potentially a couple
28    /// standardized expression types depending on the plugin standard and host. If the plugin sets
29    /// up configuration for polyphonic modulation and assigns polyphonic modulation IDs to some of
30    /// its parameters, then it will also receive polyphonic modulation events. This level is also
31    /// needed to be able to send SysEx events.
32    Basic,
33    /// The plugin receives full MIDI CCs as well as pitch bend information. For VST3 plugins this
34    /// involves adding 130*16 parameters to bind to the the 128 MIDI CCs, pitch bend, and channel
35    /// pressure.
36    MidiCCs,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
40pub enum VoiceID {
41    /// The note can belong to any voice.
42    Wildcard,
43    /// The note belongs to the voice with the given ID.
44    ID(i32),
45}
46
47impl VoiceID {
48    pub const fn id(&self) -> Option<i32> {
49        if let Self::ID(id) = self {
50            Some(*id)
51        } else {
52            None
53        }
54    }
55
56    pub const fn is_wildcard(&self) -> bool {
57        matches!(self, Self::Wildcard)
58    }
59
60    /// Compute a voice ID if the host didn't provide one. Polyphonic modulation will not work in
61    /// this case, but playing notes will.
62    pub fn id_or_fallback(&self, key: Key, channel: Channel) -> i32 {
63        key.number().unwrap_or(0) as i32 | ((channel.number().unwrap_or(0) as i32) << 16)
64    }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
68pub enum Channel {
69    /// The note can belong to any channel.
70    Wildcard,
71    /// The note's channel number, in `0..16`.
72    Number(u8),
73}
74
75impl Channel {
76    pub const fn number(&self) -> Option<u8> {
77        if let Self::Number(number) = self {
78            Some(*number)
79        } else {
80            None
81        }
82    }
83
84    pub const fn is_wildcard(&self) -> bool {
85        matches!(self, Self::Wildcard)
86    }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
90pub enum Key {
91    /// The note can belong to any key.
92    Wildcard,
93    /// The note's key number, in `0..127`.
94    /// Same as MIDI1 Key Number (60 == Middle C)
95    Number(u8),
96}
97
98impl Key {
99    pub const fn number(&self) -> Option<u8> {
100        if let Self::Number(number) = self {
101            Some(*number)
102        } else {
103            None
104        }
105    }
106
107    pub const fn is_wildcard(&self) -> bool {
108        matches!(self, Self::Wildcard)
109    }
110}
111
112/// Event for (incoming) notes. The set of supported note events depends on the value of
113/// [`Plugin::MIDI_INPUT`. Also check out the [`util`][crate::util] module for convenient conversion
114/// functions.
115///
116/// `S` is a MIDI SysEx message type that needs to implement [`SysExMessage`] to allow converting
117/// this `NoteEvent` to and from raw MIDI data. `()` is provided as a default implementing for
118/// plugins that don't use SysEx.
119///
120/// All of the timings are sample offsets within the current buffer. Out of bound timings are
121/// clamped to the current buffer's length. All sample, channel and note numbers are zero-indexed.
122#[derive(Debug, Clone, Copy, PartialEq)]
123#[non_exhaustive]
124pub enum NoteEvent<S> {
125    /// A note on event, available on [`MidiConfig::Basic`] and up.
126    NoteOn {
127        timing: u32,
128        /// A unique identifier for this note, if available. Using this to refer to a note is
129        /// required when allowing overlapping voices for CLAP plugins.
130        voice_id: VoiceID,
131        /// The note's channel number.
132        channel: Channel,
133        /// The note's MIDI key number.
134        key: Key,
135        /// The note's velocity, in `[0, 1]`. Some plugin APIs may allow higher precision than the
136        /// 128 levels available in MIDI.
137        velocity: f32,
138    },
139    /// A note off event, available on [`MidiConfig::Basic`] and up. Bitwig Studio does not provide
140    /// a voice ID for this event.
141    NoteOff {
142        timing: u32,
143        /// A unique identifier for this note, if available. Using this to refer to a note is
144        /// required when allowing overlapping voices for CLAP plugins.
145        voice_id: VoiceID,
146        /// The note's channel number.
147        channel: Channel,
148        /// The note's MIDI key number.
149        key: Key,
150        /// The note's velocity, in `[0, 1]`. Some plugin APIs may allow higher precision than the
151        /// 128 levels available in MIDI.
152        velocity: f32,
153    },
154    /// A note choke event, available on [`MidiConfig::Basic`] and up. When the host sends this to
155    /// the plugin, it indicates that a voice or all sound associated with a note should immediately
156    /// stop playing.
157    Choke {
158        timing: u32,
159        /// A unique identifier for this note, if available. Using this to refer to a note is
160        /// required when allowing overlapping voices for CLAP plugins.
161        voice_id: VoiceID,
162        /// The note's channel number.
163        channel: Channel,
164        /// The note's MIDI key number.
165        key: Key,
166    },
167
168    /// Sent by the plugin to the host to indicate that a voice has ended. This **needs** to be sent
169    /// when a voice terminates when using polyphonic modulation. Otherwise you can ignore this
170    /// event.
171    VoiceTerminated {
172        timing: u32,
173        /// The voice's unique identifier. Setting this allows a single voice to be terminated if
174        /// the plugin allows multiple overlapping voices for a single key.
175        voice_id: VoiceID,
176        /// The note's channel number.
177        channel: Channel,
178        /// The note's MIDI key number.
179        key: Key,
180    },
181    /// A polyphonic modulation event, available on [`MidiConfig::Basic`] and up. This will only be
182    /// sent for parameters that were decorated with the `.with_poly_modulation_id()` modifier, and
183    /// only by supported hosts. This event contains a _normalized offset value_ for the parameter's
184    /// current, **unmodulated** value. That is, an offset for the current value before monophonic
185    /// modulation is applied, as polyphonic modulation overrides monophonic modulation. There are
186    /// multiple ways to incorporate this polyphonic modulation into a synthesizer, but a simple way
187    /// to incorporate this would work as follows:
188    ///
189    /// - By default, a voice uses the parameter's global value, which may or may not include
190    ///   monophonic modulation. This is `parameter.value` for unsmoothed parameters, and smoothed
191    ///   parameters should use block smoothing so the smoothed values can be reused by multiple
192    ///   voices.
193    /// - If a `PolyModulation` event is emitted for the voice, that voice should use the the
194    ///   _normalized offset_ contained within the event to compute the voice's modulated value and
195    ///   use that in place of the global value.
196    ///   - This value can be obtained by calling `param.preview_plain(param.normalized_value() +
197    ///     event.normalized_offset)`. These functions automatically clamp the values as necessary.
198    ///   - If the parameter uses smoothing, then the parameter's smoother can be copied to the
199    ///     voice. [`Smoother::set_target()`][crate::params::smoothing::Smoother::set_target()] can
200    ///     then be used to have the smoother use the modulated value.
201    ///   - One caveat with smoothing is that copying the smoother like this only works correctly if
202    ///     it last produced a value during the sample before the `PolyModulation` event. Otherwise
203    ///     there may still be an audible jump in parameter values. A solution for this would be to
204    ///     first call the [`Smoother::reset()`][crate::params::smoothing::Smoother::reset()] with
205    ///     the current sample's global value before calling `set_target()`.
206    ///   - Finally, if the polyphonic modulation happens on the same sample as the `NoteOn` event,
207    ///     then the smoothing should not start at the current global value. In this case, `reset()`
208    ///     should be called with the voice's modulated value.
209    /// - If a `MonoAutomation` event is emitted for a parameter, then the values or target values
210    ///   (if the parameter uses smoothing) for all voices must be updated. The normalized value
211    ///   from the `MonoAutomation` and the voice's normalized modulation offset must be added and
212    ///   converted back to a plain value. This value can be used directly for unsmoothed
213    ///   parameters, or passed to `set_target()` for smoothed parameters. The global value will
214    ///   have already been updated, so this event only serves as a notification to update
215    ///   polyphonic modulation.
216    /// - When a voice ends, either because the amplitude envelope has hit zero or because the voice
217    ///   was stolen, the plugin must send a `VoiceTerminated` to the host to let it know that it
218    ///   can reuse the resources it used to modulate the value.
219    PolyModulation {
220        timing: u32,
221        /// The identifier of the voice this polyphonic modulation event should affect. This voice
222        /// should use the values from this and subsequent polyphonic modulation events instead of
223        /// the global value.
224        voice_id: i32,
225        /// The ID that was set for the modulated parameter using the `.with_poly_modulation_id()`
226        /// method.
227        poly_modulation_id: u32,
228        /// The normalized offset value. See the event's docstring for more information.
229        normalized_offset: f32,
230    },
231    /// A notification to inform the plugin that a polyphonically modulated parameter has received a
232    /// new automation value. This is used in conjunction with the `PolyModulation` event. See that
233    /// event's documentation for more details. The parameter's global value has already been
234    /// updated when this event is emitted.
235    MonoAutomation {
236        timing: u32,
237        /// The ID that was set for the modulated parameter using the `.with_poly_modulation_id()`
238        /// method.
239        poly_modulation_id: u32,
240        /// The parameter's new normalized value. This needs to be added to a voice's normalized
241        /// offset to get that voice's modulated normalized value. See the `PolyModulation` event's
242        /// docstring for more information.
243        normalized_value: f32,
244    },
245
246    /// A polyphonic note pressure/aftertouch event, available on [`MidiConfig::Basic`] and up. Not
247    /// all hosts may support polyphonic aftertouch.
248    ///
249    /// # Note
250    ///
251    /// When implementing MPE support you should use MIDI channel pressure instead as polyphonic key
252    /// pressure + MPE is undefined as per the MPE specification. Or as a more generic catch all,
253    /// you may manually combine the polyphonic key pressure and MPE channel pressure.
254    PolyPressure {
255        timing: u32,
256        /// A unique identifier for this note, if available. Using this to refer to a note is
257        /// required when allowing overlapping voices for CLAP plugins.
258        voice_id: VoiceID,
259        /// The note's channel number.
260        channel: Channel,
261        /// The note's MIDI key number.
262        key: Key,
263        /// The note's pressure, in `[0, 1]`.
264        pressure: f32,
265    },
266    /// A volume expression event, available on [`MidiConfig::Basic`] and up. Not all hosts may
267    /// support these expressions.
268    PolyVolume {
269        timing: u32,
270        /// A unique identifier for this note, if available. Using this to refer to a note is
271        /// required when allowing overlapping voices for CLAP plugins.
272        voice_id: VoiceID,
273        /// The note's channel number.
274        channel: Channel,
275        /// The note's MIDI key number.
276        key: Key,
277        /// The note's voltage gain ratio, where 1.0 is unity gain.
278        gain: f32,
279    },
280    /// A panning expression event, available on [`MidiConfig::Basic`] and up. Not all hosts may
281    /// support these expressions.
282    PolyPan {
283        timing: u32,
284        /// A unique identifier for this note, if available. Using this to refer to a note is
285        /// required when allowing overlapping voices for CLAP plugins.
286        voice_id: VoiceID,
287        /// The note's channel number.
288        channel: Channel,
289        /// The note's MIDI key number.
290        key: Key,
291        /// The note's panning from, in `[-1, 1]`, with -1 being panned hard left, and 1
292        /// being panned hard right.
293        pan: f32,
294    },
295    /// A tuning expression event, available on [`MidiConfig::Basic`] and up. Not all hosts may support
296    /// these expressions.
297    PolyTuning {
298        timing: u32,
299        /// A unique identifier for this note, if available. Using this to refer to a note is
300        /// required when allowing overlapping voices for CLAP plugins.
301        voice_id: VoiceID,
302        /// The note's channel number.
303        channel: Channel,
304        /// The note's MIDI key number.
305        key: Key,
306        /// The note's tuning in semitones, in `[-128, 128]`.
307        tuning: f32,
308    },
309    /// A vibrato expression event, available on [`MidiConfig::Basic`] and up. Not all hosts may support
310    /// these expressions.
311    PolyVibrato {
312        timing: u32,
313        /// A unique identifier for this note, if available. Using this to refer to a note is
314        /// required when allowing overlapping voices for CLAP plugins.
315        voice_id: VoiceID,
316        /// The note's channel number.
317        channel: Channel,
318        /// The note's MIDI key number.
319        key: Key,
320        /// The note's vibrato amount, in `[0, 1]`.
321        vibrato: f32,
322    },
323    /// A expression expression (yes, expression expression) event, available on
324    /// [`MidiConfig::Basic`] and up. Not all hosts may support these expressions.
325    PolyExpression {
326        timing: u32,
327        /// A unique identifier for this note, if available. Using this to refer to a note is
328        /// required when allowing overlapping voices for CLAP plugins.
329        voice_id: VoiceID,
330        /// The note's channel number.
331        channel: Channel,
332        /// The note's MIDI key number.
333        key: Key,
334        /// The note's expression amount, in `[0, 1]`.
335        expression: f32,
336    },
337    /// A brightness expression event, available on [`MidiConfig::Basic`] and up. Not all hosts may support
338    /// these expressions.
339    PolyBrightness {
340        timing: u32,
341        /// A unique identifier for this note, if available. Using this to refer to a note is
342        /// required when allowing overlapping voices for CLAP plugins.
343        voice_id: VoiceID,
344        /// The note's channel number.
345        channel: Channel,
346        /// The note's MIDI key number.
347        key: Key,
348        /// The note's brightness amount, in `[0, 1]`.
349        brightness: f32,
350    },
351    /// A MIDI channel pressure event, available on [`MidiConfig::MidiCCs`] and up.
352    MidiChannelPressure {
353        timing: u32,
354        /// The affected channel, in `0..16`.
355        channel: u8,
356        /// The pressure, normalized to `[0, 1]` to match the poly pressure event.
357        pressure: f32,
358    },
359    /// A MIDI pitch bend, available on [`MidiConfig::MidiCCs`] and up.
360    MidiPitchBend {
361        timing: u32,
362        /// The affected channel, in `0..16`.
363        channel: u8,
364        /// The pressure, normalized to `[0, 1]`. `0.5` means no pitch bend.
365        value: f32,
366    },
367    /// A MIDI control change event, available on [`MidiConfig::MidiCCs`] and up.
368    ///
369    /// # Note
370    ///
371    /// The wrapper does not perform any special handling for two message 14-bit CCs (where the CC
372    /// number is in `0..32`, and the next CC is that number plus 32) or for four message RPN
373    /// messages. For now you will need to handle these CCs yourself.
374    MidiCC {
375        timing: u32,
376        /// The affected channel, in `0..16`.
377        channel: u8,
378        /// The control change number. See [`control_change`] for a list of CC numbers.
379        cc: u8,
380        /// The CC's value, normalized to `[0, 1]`. Multiply by 127 to get the original raw value.
381        value: f32,
382    },
383    /// A MIDI program change event, available on [`MidiConfig::MidiCCs`] and up. VST3 plugins
384    /// cannot receive these events.
385    MidiProgramChange {
386        timing: u32,
387        /// The affected channel, in `0..16`.
388        channel: u8,
389        /// The program number, in `0..128`.
390        program: u8,
391    },
392    /// A MIDI SysEx message supported by the plugin's `SysExMessage` type, available on
393    /// [`MidiConfig::Basic`] and up. If the conversion from the raw byte array fails (e.g. the
394    /// plugin doesn't support this kind of message), then this will be logged during debug builds
395    /// of the plugin, and no event is emitted.
396    MidiSysEx { timing: u32, message: S },
397}
398
399/// The result of converting a `NoteEvent<S>` to MIDI. This is a bit weirder than it would have to
400/// be because it's not possible to use associated constants in type definitions.
401#[derive(Debug, Clone)]
402pub enum MidiResult<S: SysExMessage> {
403    /// A basic three byte MIDI event.
404    Basic([u8; 3]),
405    /// A SysEx event. The message was written to the `S::Buffer` and may include padding at the
406    /// end. The `usize` value indicates the message's actual length, including headers and end of
407    /// SysEx byte.
408    SysEx(S::Buffer, usize),
409}
410
411impl<S> NoteEvent<S> {
412    /// Returns the sample within the current buffer this event belongs to.
413    pub fn timing(&self) -> u32 {
414        match self {
415            NoteEvent::NoteOn { timing, .. } => *timing,
416            NoteEvent::NoteOff { timing, .. } => *timing,
417            NoteEvent::Choke { timing, .. } => *timing,
418            NoteEvent::VoiceTerminated { timing, .. } => *timing,
419            NoteEvent::PolyModulation { timing, .. } => *timing,
420            NoteEvent::MonoAutomation { timing, .. } => *timing,
421            NoteEvent::PolyPressure { timing, .. } => *timing,
422            NoteEvent::PolyVolume { timing, .. } => *timing,
423            NoteEvent::PolyPan { timing, .. } => *timing,
424            NoteEvent::PolyTuning { timing, .. } => *timing,
425            NoteEvent::PolyVibrato { timing, .. } => *timing,
426            NoteEvent::PolyExpression { timing, .. } => *timing,
427            NoteEvent::PolyBrightness { timing, .. } => *timing,
428            NoteEvent::MidiChannelPressure { timing, .. } => *timing,
429            NoteEvent::MidiPitchBend { timing, .. } => *timing,
430            NoteEvent::MidiCC { timing, .. } => *timing,
431            NoteEvent::MidiProgramChange { timing, .. } => *timing,
432            NoteEvent::MidiSysEx { timing, .. } => *timing,
433        }
434    }
435
436    /// Returns the event's voice ID, if it has any.
437    pub fn voice_id(&self) -> Option<VoiceID> {
438        match self {
439            NoteEvent::NoteOn { voice_id, .. } => Some(*voice_id),
440            NoteEvent::NoteOff { voice_id, .. } => Some(*voice_id),
441            NoteEvent::Choke { voice_id, .. } => Some(*voice_id),
442            NoteEvent::VoiceTerminated { voice_id, .. } => Some(*voice_id),
443            NoteEvent::PolyModulation { voice_id, .. } => Some(VoiceID::ID(*voice_id)),
444            NoteEvent::MonoAutomation { .. } => None,
445            NoteEvent::PolyPressure { voice_id, .. } => Some(*voice_id),
446            NoteEvent::PolyVolume { voice_id, .. } => Some(*voice_id),
447            NoteEvent::PolyPan { voice_id, .. } => Some(*voice_id),
448            NoteEvent::PolyTuning { voice_id, .. } => Some(*voice_id),
449            NoteEvent::PolyVibrato { voice_id, .. } => Some(*voice_id),
450            NoteEvent::PolyExpression { voice_id, .. } => Some(*voice_id),
451            NoteEvent::PolyBrightness { voice_id, .. } => Some(*voice_id),
452            NoteEvent::MidiChannelPressure { .. } => None,
453            NoteEvent::MidiPitchBend { .. } => None,
454            NoteEvent::MidiCC { .. } => None,
455            NoteEvent::MidiProgramChange { .. } => None,
456            NoteEvent::MidiSysEx { .. } => None,
457        }
458    }
459
460    /// Returns the event's channel, if it has any.
461    pub fn channel(&self) -> Option<Channel> {
462        match self {
463            NoteEvent::NoteOn { channel, .. } => Some(*channel),
464            NoteEvent::NoteOff { channel, .. } => Some(*channel),
465            NoteEvent::Choke { channel, .. } => Some(*channel),
466            NoteEvent::VoiceTerminated { channel, .. } => Some(*channel),
467            NoteEvent::PolyModulation { .. } => None,
468            NoteEvent::MonoAutomation { .. } => None,
469            NoteEvent::PolyPressure { channel, .. } => Some(*channel),
470            NoteEvent::PolyVolume { channel, .. } => Some(*channel),
471            NoteEvent::PolyPan { channel, .. } => Some(*channel),
472            NoteEvent::PolyTuning { channel, .. } => Some(*channel),
473            NoteEvent::PolyVibrato { channel, .. } => Some(*channel),
474            NoteEvent::PolyExpression { channel, .. } => Some(*channel),
475            NoteEvent::PolyBrightness { channel, .. } => Some(*channel),
476            NoteEvent::MidiChannelPressure { channel, .. } => Some(Channel::Number(*channel)),
477            NoteEvent::MidiPitchBend { channel, .. } => Some(Channel::Number(*channel)),
478            NoteEvent::MidiCC { channel, .. } => Some(Channel::Number(*channel)),
479            NoteEvent::MidiProgramChange { channel, .. } => Some(Channel::Number(*channel)),
480            NoteEvent::MidiSysEx { .. } => None,
481        }
482    }
483}
484
485impl<S: SysExMessage> NoteEvent<S> {
486    /// Parse MIDI into a [`NoteEvent`]. Supports both basic three bytes messages as well as SysEx.
487    /// Will return `Err(event_type)` if the parsing failed.
488    pub fn from_midi(timing: u32, midi_data: &[u8]) -> Result<Self, u8> {
489        let status_byte = midi_data.first().copied().unwrap_or_default();
490        let event_type = status_byte & midi::EVENT_TYPE_MASK;
491        let channel = status_byte & midi::MIDI_CHANNEL_MASK;
492
493        if midi_data.len() >= 3 {
494            // TODO: Maybe add special handling for 14-bit CCs and RPN messages at some
495            //       point, right now the plugin has to figure it out for itself
496            match event_type {
497                // You thought this was a note on? Think again! This is a cleverly disguised note off
498                // event straight from the 80s when Baud rate was still a limiting factor!
499                midi::NOTE_ON if midi_data[2] == 0 => {
500                    return Ok(NoteEvent::NoteOff {
501                        timing,
502                        voice_id: VoiceID::Wildcard,
503                        channel: Channel::Number(channel),
504                        key: Key::Number(midi_data[1]),
505                        // Few things use release velocity. Just having this be zero here is fine, right?
506                        velocity: 0.0,
507                    });
508                }
509                midi::NOTE_ON => {
510                    return Ok(NoteEvent::NoteOn {
511                        timing,
512                        voice_id: VoiceID::Wildcard,
513                        channel: Channel::Number(channel),
514                        key: Key::Number(midi_data[1]),
515                        velocity: midi_data[2] as f32 / 127.0,
516                    });
517                }
518                midi::NOTE_OFF => {
519                    return Ok(NoteEvent::NoteOff {
520                        timing,
521                        voice_id: VoiceID::Wildcard,
522                        channel: Channel::Number(channel),
523                        key: Key::Number(midi_data[1]),
524                        velocity: midi_data[2] as f32 / 127.0,
525                    });
526                }
527                midi::POLYPHONIC_KEY_PRESSURE => {
528                    return Ok(NoteEvent::PolyPressure {
529                        timing,
530                        voice_id: VoiceID::Wildcard,
531                        channel: Channel::Number(channel),
532                        key: Key::Number(midi_data[1]),
533                        pressure: midi_data[2] as f32 / 127.0,
534                    });
535                }
536                midi::PITCH_BEND_CHANGE => {
537                    return Ok(NoteEvent::MidiPitchBend {
538                        timing,
539                        channel,
540                        value: (midi_data[1] as u16 + ((midi_data[2] as u16) << 7)) as f32
541                            / ((1 << 14) - 1) as f32,
542                    });
543                }
544                midi::CONTROL_CHANGE => {
545                    return Ok(NoteEvent::MidiCC {
546                        timing,
547                        channel,
548                        cc: midi_data[1],
549                        value: midi_data[2] as f32 / 127.0,
550                    });
551                }
552                _ => (),
553            }
554        }
555        if midi_data.len() >= 2 {
556            match event_type {
557                midi::CHANNEL_KEY_PRESSURE => {
558                    return Ok(NoteEvent::MidiChannelPressure {
559                        timing,
560                        channel,
561                        pressure: midi_data[1] as f32 / 127.0,
562                    });
563                }
564                midi::PROGRAM_CHANGE => {
565                    return Ok(NoteEvent::MidiProgramChange {
566                        timing,
567                        channel,
568                        program: midi_data[1],
569                    });
570                }
571                _ => (),
572            }
573        }
574
575        // Every other message is parsed as SysEx, even if they don't have the `0xf0` status byte.
576        // This allows the `SysExMessage` trait to have a bit more flexibility if needed. Regular
577        // note event parsing however still has higher priority.
578        match S::from_buffer(midi_data) {
579            Some(message) => Ok(NoteEvent::MidiSysEx { timing, message }),
580            None => {
581                if event_type == 0xf0 {
582                    if midi_data.len() <= 32 {
583                        nice_trace!("Unhandled MIDI system message: {midi_data:02x?}");
584                    } else {
585                        nice_trace!("Unhandled MIDI system message of {} bytes", midi_data.len());
586                    }
587                } else {
588                    nice_trace!("Unhandled MIDI status byte {status_byte:#x}");
589                }
590
591                Err(event_type)
592            }
593        }
594    }
595
596    /// Create a MIDI message from this note event. Returns `None` if this even does not have a
597    /// direct MIDI equivalent. `PolyPressure` will be converted to polyphonic key pressure, but the
598    /// other polyphonic note expression types will not be converted to MIDI CC messages.
599    pub fn as_midi(&self) -> Option<MidiResult<S>> {
600        match self {
601            NoteEvent::NoteOn {
602                timing: _,
603                voice_id: _,
604                channel,
605                key,
606                velocity,
607            } => Some(MidiResult::Basic([
608                midi::NOTE_ON | channel.number().unwrap_or(0),
609                key.number().unwrap_or(0),
610                // MIDI treats note ons with zero velocity as note offs, because reasons
611                (velocity * 127.0).round().clamp(1.0, 127.0) as u8,
612            ])),
613            NoteEvent::NoteOff {
614                timing: _,
615                voice_id: _,
616                channel,
617                key,
618                velocity,
619            } => Some(MidiResult::Basic([
620                midi::NOTE_OFF | channel.number().unwrap_or(0),
621                key.number().unwrap_or(0),
622                (velocity * 127.0).round().clamp(0.0, 127.0) as u8,
623            ])),
624            NoteEvent::PolyPressure {
625                timing: _,
626                voice_id: _,
627                channel,
628                key,
629                pressure,
630            } => Some(MidiResult::Basic([
631                midi::POLYPHONIC_KEY_PRESSURE | channel.number().unwrap_or(0),
632                key.number().unwrap_or(0),
633                (pressure * 127.0).round().clamp(0.0, 127.0) as u8,
634            ])),
635            NoteEvent::MidiChannelPressure {
636                timing: _,
637                channel,
638                pressure,
639            } => Some(MidiResult::Basic([
640                midi::CHANNEL_KEY_PRESSURE | channel,
641                (pressure * 127.0).round().clamp(0.0, 127.0) as u8,
642                0,
643            ])),
644            NoteEvent::MidiPitchBend {
645                timing: _,
646                channel,
647                value,
648            } => {
649                const PITCH_BEND_RANGE: f32 = ((1 << 14) - 1) as f32;
650                let midi_value = (value * PITCH_BEND_RANGE)
651                    .round()
652                    .clamp(0.0, PITCH_BEND_RANGE) as u16;
653
654                Some(MidiResult::Basic([
655                    midi::PITCH_BEND_CHANGE | channel,
656                    (midi_value & ((1 << 7) - 1)) as u8,
657                    (midi_value >> 7) as u8,
658                ]))
659            }
660            NoteEvent::MidiCC {
661                timing: _,
662                channel,
663                cc,
664                value,
665            } => Some(MidiResult::Basic([
666                midi::CONTROL_CHANGE | channel,
667                *cc,
668                (value * 127.0).round().clamp(0.0, 127.0) as u8,
669            ])),
670            NoteEvent::MidiProgramChange {
671                timing: _,
672                channel,
673                program,
674            } => Some(MidiResult::Basic([
675                midi::PROGRAM_CHANGE | channel,
676                *program,
677                0,
678            ])),
679            // `message` is serialized and written to `sysex_buffer`, and the result contains the
680            // message's actual length
681            NoteEvent::MidiSysEx { timing: _, message } => {
682                let (padded_sysex_buffer, length) = message.as_buffer();
683                Some(MidiResult::SysEx(padded_sysex_buffer, length))
684            }
685            NoteEvent::Choke { .. }
686            | NoteEvent::VoiceTerminated { .. }
687            | NoteEvent::PolyModulation { .. }
688            | NoteEvent::MonoAutomation { .. }
689            | NoteEvent::PolyVolume { .. }
690            | NoteEvent::PolyPan { .. }
691            | NoteEvent::PolyTuning { .. }
692            | NoteEvent::PolyVibrato { .. }
693            | NoteEvent::PolyExpression { .. }
694            | NoteEvent::PolyBrightness { .. } => None,
695        }
696    }
697
698    /// Subtract a sample offset from this event's timing, needed to compensate for the block
699    /// splitting in the VST3 wrapper implementation because all events have to be read upfront.
700    pub fn subtract_timing(&mut self, samples: u32) {
701        match self {
702            NoteEvent::NoteOn { timing, .. } => *timing -= samples,
703            NoteEvent::NoteOff { timing, .. } => *timing -= samples,
704            NoteEvent::Choke { timing, .. } => *timing -= samples,
705            NoteEvent::VoiceTerminated { timing, .. } => *timing -= samples,
706            NoteEvent::PolyModulation { timing, .. } => *timing -= samples,
707            NoteEvent::MonoAutomation { timing, .. } => *timing -= samples,
708            NoteEvent::PolyPressure { timing, .. } => *timing -= samples,
709            NoteEvent::PolyVolume { timing, .. } => *timing -= samples,
710            NoteEvent::PolyPan { timing, .. } => *timing -= samples,
711            NoteEvent::PolyTuning { timing, .. } => *timing -= samples,
712            NoteEvent::PolyVibrato { timing, .. } => *timing -= samples,
713            NoteEvent::PolyExpression { timing, .. } => *timing -= samples,
714            NoteEvent::PolyBrightness { timing, .. } => *timing -= samples,
715            NoteEvent::MidiChannelPressure { timing, .. } => *timing -= samples,
716            NoteEvent::MidiPitchBend { timing, .. } => *timing -= samples,
717            NoteEvent::MidiCC { timing, .. } => *timing -= samples,
718            NoteEvent::MidiProgramChange { timing, .. } => *timing -= samples,
719            NoteEvent::MidiSysEx { timing, .. } => *timing -= samples,
720        }
721    }
722}
723
724#[cfg(test)]
725mod tests {
726    pub use super::*;
727
728    pub const TIMING: u32 = 5;
729
730    /// Converts an event to and from MIDI. Panics if any part of the conversion fails.
731    fn roundtrip_basic_event(event: NoteEvent<()>) -> NoteEvent<()> {
732        let midi_data = match event.as_midi().unwrap() {
733            MidiResult::Basic(midi_data) => midi_data,
734            MidiResult::SysEx(_, _) => panic!("Unexpected SysEx result"),
735        };
736
737        NoteEvent::from_midi(TIMING, &midi_data).unwrap()
738    }
739
740    #[test]
741    fn test_note_on_midi_conversion() {
742        let event = NoteEvent::<()>::NoteOn {
743            timing: TIMING,
744            voice_id: VoiceID::Wildcard,
745            channel: Channel::Number(1),
746            key: Key::Number(2),
747            // The value will be rounded in the conversion to MIDI, hence this overly specific value
748            velocity: 0.6929134,
749        };
750
751        assert_eq!(roundtrip_basic_event(event), event);
752    }
753
754    #[test]
755    fn test_note_off_midi_conversion() {
756        let event = NoteEvent::<()>::NoteOff {
757            timing: TIMING,
758            voice_id: VoiceID::Wildcard,
759            channel: Channel::Number(1),
760            key: Key::Number(2),
761            velocity: 0.6929134,
762        };
763
764        assert_eq!(roundtrip_basic_event(event), event);
765    }
766
767    #[test]
768    fn test_poly_pressure_midi_conversion() {
769        let event = NoteEvent::<()>::PolyPressure {
770            timing: TIMING,
771            voice_id: VoiceID::Wildcard,
772            channel: Channel::Number(1),
773            key: Key::Number(2),
774            pressure: 0.6929134,
775        };
776
777        assert_eq!(roundtrip_basic_event(event), event);
778    }
779
780    #[test]
781    fn test_channel_pressure_midi_conversion() {
782        let event = NoteEvent::<()>::MidiChannelPressure {
783            timing: TIMING,
784            channel: 1,
785            pressure: 0.6929134,
786        };
787
788        assert_eq!(roundtrip_basic_event(event), event);
789    }
790
791    #[test]
792    fn test_pitch_bend_midi_conversion() {
793        let event = NoteEvent::<()>::MidiPitchBend {
794            timing: TIMING,
795            channel: 1,
796            value: 0.6929134,
797        };
798
799        assert_eq!(roundtrip_basic_event(event), event);
800    }
801
802    #[test]
803    fn test_cc_midi_conversion() {
804        let event = NoteEvent::<()>::MidiCC {
805            timing: TIMING,
806            channel: 1,
807            cc: 2,
808            value: 0.6929134,
809        };
810
811        assert_eq!(roundtrip_basic_event(event), event);
812    }
813
814    #[test]
815    fn test_program_change_midi_conversion() {
816        let event = NoteEvent::<()>::MidiProgramChange {
817            timing: TIMING,
818            channel: 1,
819            program: 42,
820        };
821
822        assert_eq!(roundtrip_basic_event(event), event);
823    }
824
825    mod sysex {
826        use super::*;
827
828        #[derive(Clone, Debug, PartialEq)]
829        enum MessageType {
830            Foo(f32),
831        }
832
833        impl SysExMessage for MessageType {
834            type Buffer = [u8; 4];
835
836            fn from_buffer(buffer: &[u8]) -> Option<Self> {
837                match buffer {
838                    [0xf0, 0x69, n, 0xf7] => Some(MessageType::Foo(*n as f32 / 127.0)),
839                    _ => None,
840                }
841            }
842
843            fn as_buffer(&self) -> (Self::Buffer, usize) {
844                match self {
845                    MessageType::Foo(x) => ([0xf0, 0x69, (x * 127.0).round() as u8, 0xf7], 4),
846                }
847            }
848        }
849
850        #[test]
851        fn test_parse_from_buffer() {
852            let midi_data = [0xf0, 0x69, 127, 0xf7];
853            let parsed = NoteEvent::from_midi(TIMING, &midi_data).unwrap();
854
855            assert_eq!(
856                parsed,
857                NoteEvent::MidiSysEx {
858                    timing: TIMING,
859                    message: MessageType::Foo(1.0)
860                }
861            );
862        }
863
864        #[test]
865        fn test_convert_to_buffer() {
866            let message = MessageType::Foo(1.0);
867            let event = NoteEvent::MidiSysEx {
868                timing: TIMING,
869                message,
870            };
871
872            match event.as_midi() {
873                Some(MidiResult::SysEx(padded_sysex_buffer, length)) => {
874                    assert_eq!(padded_sysex_buffer[..length], [0xf0, 0x69, 127, 0xf7])
875                }
876                result => panic!("Unexpected result: {result:?}"),
877            }
878        }
879
880        #[test]
881        fn test_invalid_parse() {
882            let midi_data = [0xf0, 0x0, 127, 0xf7];
883            let parsed = NoteEvent::<MessageType>::from_midi(TIMING, &midi_data);
884
885            assert!(parsed.is_err());
886        }
887    }
888}