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