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}