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}