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