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}