1 /*
2 Cockos WDL License
3 
4 Copyright (C) 2005 - 2015 Cockos Incorporated
5 Copyright (C) 2015 - 2017 Auburn Sounds
6 
7 Portions copyright other contributors, see each source file for more information
8 
9 This software is provided 'as-is', without any express or implied warranty.  In no event will the authors be held liable for any damages arising from the use of this software.
10 
11 Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
12 
13 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
14 1. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
15 1. This notice may not be removed or altered from any source distribution.
16 */
17 /**
18     MIDI messages definitions.
19 */
20 module dplug.client.midi;
21 
22 import dplug.core.vec;
23 
24 /// This abstraction is similar to the one in IPlug.
25 /// For VST raw MIDI messages are passed.
26 /// For AU MIDI messages gets synthesized.
27 struct MidiMessage
28 {
29 pure:
30 nothrow:
31 @nogc:
32 
33     this( int offset, ubyte statusByte, ubyte data1, ubyte data2)
34     {
35         _offset = offset;
36         _statusByte = statusByte;
37         _data1 = data1;
38         _data2 = data2;
39     }
40 
41     int offset() const
42     {
43         return _offset;
44     }
45 
46     /// Midi channels 1 .. 16
47     ///
48     /// Returns: [0 .. 15]
49     int channel() const
50     {
51         return _statusByte & 0x0F;
52     }
53 
54     /// Status Type
55     ///
56     /// See_Also: dplug.client.midi : MidiStatus
57     int statusType() const
58     {
59         return _statusByte >> 4;
60     }
61 
62     // Status type distinction properties
63 
64     bool isChannelAftertouch() const
65     {
66         return statusType() == MidiStatus.channelAftertouch;
67     }
68 
69     bool isControlChange() const
70     {
71         return statusType() == MidiStatus.controlChange;
72     }
73 
74     /// Checks whether the status type of the message is 'Note On'
75     /// _and the velocity value is actually greater than zero_.
76     ///
77     /// IMPORTANT: As per MIDI 1.0, a 'Note On' event with a velocity
78     ///            of zero should be treated like a 'Note Off' event.
79     ///            Which is why this function checks velocity > 0.
80     ///
81     /// See_Also:
82     ///     isNoteOff()
83     bool isNoteOn() const
84     {
85         return (statusType() == MidiStatus.noteOn) && (noteVelocity() > 0);
86     }
87 
88     /// Checks whether the status type of the message is 'Note Off'
89     /// or 'Note On' with a velocity of 0.
90     ///
91     /// IMPORTANT: As per MIDI 1.0, a 'Note On' event with a velocity
92     ///            of zero should be treated like a 'Note Off' event.
93     ///            Which is why this function checks velocity == 0.
94     ///
95     ///            Some devices send a 'Note On' message with a velocity
96     ///            value of zero instead of a real 'Note Off' message.
97     ///            Many DAWs will automatically convert such ones to
98     ///            explicit ones, but one cannot rely on this.
99     ///
100     /// See_Also:
101     ///     isNoteOn()
102     bool isNoteOff() const
103     {
104         return (statusType() == MidiStatus.noteOff)
105                ||
106                ( (statusType() == MidiStatus.noteOn) && (noteVelocity() == 0) );
107     }
108 
109     /// DO HANDLE THIS. From MIDI Spec:
110     ///
111     /// "Mutes all sounding notes that were turned on by received Note On messages, and which haven't yet been 
112     /// turned off by respective Note Off messages. [...]
113     ///
114     /// "Note: The difference between this message and All Notes Off is that this message immediately mutes all sound 
115     /// on the device regardless of whether the Hold Pedal is on, and mutes the sound quickly regardless of any lengthy 
116     /// VCA release times. It's often used by sequencers to quickly mute all sound when the musician presses "Stop" in 
117     /// the middle of a song."
118     bool isAllSoundsOff() const
119     {
120         return isControlChange() && (controlChangeControl() == MidiControlChange.allSoundsOff);
121     }
122 
123     /// DO HANDLE THIS. From MIDI Spec:
124     ///
125     /// "Turns off all notes that were turned on by received Note On messages, and which haven't yet been turned off 
126     /// by respective Note Off messages. [...]
127     bool isAllNotesOff() const
128     {
129         return isControlChange() && (controlChangeControl() == MidiControlChange.allNotesOff);
130     }
131 
132     bool isPitchBend() const
133     {
134         return statusType() == MidiStatus.pitchWheel;
135     }
136 
137     alias isPitchWheel = isPitchBend;
138 
139     bool isPolyAftertouch() const
140     {
141         return statusType() == MidiStatus.polyAftertouch;
142     }
143 
144     bool isProgramChange() const
145     {
146         return statusType() == MidiStatus.programChange;
147     }
148 
149     // Data value properties
150 
151     /// Channel pressure
152     int channelAftertouch() const
153     {
154         assert(isChannelAftertouch());
155         return _data1;
156     }
157 
158     /// Number of the changed controller
159     MidiControlChange controlChangeControl() const
160     {
161         assert(isControlChange());
162         return cast(MidiControlChange)(_data1);
163     }
164 
165     /// Controller's new value
166     ///
167     /// Returns: [1 .. 127]
168     int controlChangeValue() const
169     {
170         assert(isControlChange());
171         return _data2;
172     }
173 
174     /// Controller's new value
175     ///
176     /// Returns: [0.0 .. 1.0]
177     float controlChangeValue0to1() const
178     {
179         return cast(float)(controlChangeValue()) / 127.0f;
180     }
181 
182     /// Returns: true = on
183     bool controlChangeOnOff() const
184     {
185         return controlChangeValue() >= 64;
186     }
187 
188     int noteNumber() const
189     {
190         assert(isNoteOn() || isNoteOff() || isPolyAftertouch());
191         return _data1;
192     }
193 
194     int noteVelocity() const
195     {
196         return _data2;
197     }
198 
199     /// Returns: [-1.0 .. 1.0[
200     float pitchBend() const
201     {
202         assert(isPitchBend());
203         immutable int iVal = (_data2 << 7) + _data1;
204         return cast(float) (iVal - 8192) / 8192.0f;
205     }
206 
207     alias pitchWheel = pitchBend;
208 
209     /// Amount of pressure
210     int polyAftertouch() const
211     {
212         assert(isPolyAftertouch());
213         return _data2;
214     }
215 
216     /// Program number
217     int program() const
218     {
219         assert(isProgramChange());
220         return _data1;
221     }
222 
223     /// Return: size in bytes of the MIDI message, if it were serialized without offset.
224     ///         -1 if unknown size (either the status byte doesn't start with 1xxxxxx, or
225     ///         starts with 1111xxxx (can be many different things).
226     /// Reference: https://www.midi.org/specifications-old/item/table-2-expanded-messages-list-status-bytes
227     int lengthInBytes() const
228     {
229         switch (statusType())
230         {
231             case MidiStatus.noteOff:
232             case MidiStatus.noteOn:
233             case MidiStatus.polyAftertouch:
234             case MidiStatus.controlChange:
235                 return 3;
236 
237             case MidiStatus.programChange:
238             case MidiStatus.channelAftertouch:
239                 return 2;
240 
241             case MidiStatus.pitchBend:
242                 return 3;
243 
244             default:
245                 return -1; // unknown. A midi message with unknown size will be dropped by Dplug in general.
246         }
247     }
248 
249     /// Write the raw MIDI data in a buffer `data` of capacity `len`.
250     ///
251     /// If given < 0 len, return the number of bytes needed to return the whole message.
252     /// Note: Channel Pressure event, who are 2 bytes, are transmitted as 3 in VST3 (no issue) 
253     /// but also in LV2 (BUG).
254     ///
255     /// Params:
256     ///     data   Pointer to at least 3 bytes of buffer to write MIDI message to (advice = 256 bytes).
257     ///     buflen Length of buffer pointed to by `data`.
258     ///            If buflen < 0, this functions instead return the number of bytes needed (or -1 for unknown length).
259     ///
260     /// Returns: Number of written bytes.
261     ///          If the MIDI message has an unknown (aka unsupported) length then nothing gets written,
262     ///          zero is returned.
263     int toBytes(ubyte* data, int buflen) const
264     {
265         int msglen = lengthInBytes();
266         if (buflen < 0) 
267             return msglen;
268 
269         if (msglen == -1) 
270             return 0; // unknown length, do not serialize
271 
272         // Write min of buffer length, and message length.
273         int len = buflen < msglen ? buflen : msglen;
274 
275         if (len > 0) data[0] = _statusByte;
276         if (len > 1) data[1] = _data1;
277         if (len > 2) data[2] = _data2;
278 
279         return msglen;
280     }
281 
282     /// Simply returns internal representation.
283     ubyte data1() pure const
284     {
285         return _data1;
286     }
287     ///ditto
288     ubyte data2() pure const
289     {
290         return _data2;
291     }
292 
293 private:
294     int _offset = 0;
295 
296     ubyte _statusByte = 0;
297 
298     ubyte _data1 = 0;
299 
300     ubyte _data2 = 0;
301 }
302 
303 
304 enum MidiStatus : ubyte
305 {
306     none              = 0,
307     noteOff           = 8,
308     noteOn            = 9,
309     polyAftertouch    = 10,
310     controlChange     = 11,
311     programChange     = 12,
312     channelAftertouch = 13,
313     pitchBend         = 14,
314     pitchWheel = pitchBend
315 };
316 
317 enum MidiControlChange : ubyte
318 {
319     modWheel = 1,
320     breathController = 2,
321     undefined003 = 3,
322     footController = 4,
323     portamentoTime = 5,
324     channelVolume = 7,
325     balance = 8,
326     undefined009 = 9,
327     pan = 10,
328     expressionController = 11,
329     effectControl1 = 12,
330     effectControl2 = 13,
331     generalPurposeController1 = 16,
332     generalPurposeController2 = 17,
333     generalPurposeController3 = 18,
334     generalPurposeController4 = 19,
335     sustainOnOff = 64,
336     portamentoOnOff = 65,
337     sustenutoOnOff = 66,
338     softPedalOnOff = 67,
339     legatoOnOff = 68,
340     hold2OnOff = 69,
341     soundVariation = 70,
342     resonance = 71,
343     releaseTime = 72,
344     attackTime = 73,
345     cutoffFrequency = 74,
346     decayTime = 75,
347     vibratoRate = 76,
348     vibratoDepth = 77,
349     vibratoDelay = 78,
350     soundControllerUndefined = 79,
351     tremoloDepth = 92,
352     chorusDepth = 93,
353     phaserDepth = 95,
354     allSoundsOff = 120,
355     allNotesOff = 123
356 }
357 
358 nothrow @nogc:
359 
360 MidiMessage makeMidiMessage(int offset, int channel, MidiStatus statusType, int data1, int data2)
361 {
362     assert(channel >= 0 && channel <= 15);
363     assert(statusType >= 0 && statusType <= 15);
364     assert(data1 >= 0 && data2 <= 255);
365     assert(data1 >= 0 && data2 <= 255);
366     MidiMessage msg;
367     msg._offset = offset;
368     msg._statusByte = cast(ubyte)( channel | (statusType << 4) );
369     msg._data1 = cast(ubyte)data1;
370     msg._data2 = cast(ubyte)data2;
371     return msg;
372 }
373 
374 MidiMessage makeMidiMessageNoteOn(int offset, int channel, int noteNumber, int velocity)
375 {
376     return makeMidiMessage(offset, channel, MidiStatus.noteOn, noteNumber, velocity);
377 }
378 
379 MidiMessage makeMidiMessageNoteOff(int offset, int channel, int noteNumber)
380 {
381     return makeMidiMessage(offset, channel, MidiStatus.noteOff, noteNumber, 0);
382 }
383 
384 /// Make a Pitch Wheel (aka Pitch Bend) MIDI message.
385 /// Params:
386 ///     value Amount of pitch, -1 to 1. Not sure what unit! FUTUURE understand how much semitones MIDI says it should be.
387 MidiMessage makeMidiMessagePitchWheel(int offset, int channel, float value)
388 {
389     int ivalue = 8192 + cast(int)(value * 8192.0);
390     if (ivalue < 0)
391         ivalue = 0;
392     if (ivalue > 16383)
393         ivalue = 16383;
394     return makeMidiMessage(offset, channel, MidiStatus.pitchWheel, ivalue & 0x7F, ivalue >> 7);
395 }
396 
397 /// Make a Channel Aftertouch MIDI message. It has no note number, and acts for the whole instrument.
398 /// Also called: Channel Pressure.
399 MidiMessage makeMidiMessageChannelPressure(int offset, int channel, float value)
400 {
401     int ivalue = cast(int)(value * 128.0);
402     if (ivalue < 0)
403         ivalue = 0;
404     if (ivalue > 127)
405         ivalue = 127;
406     return makeMidiMessage(offset, channel, MidiStatus.channelAftertouch, ivalue, 0); // no note number
407 }
408 
409 MidiMessage makeMidiMessageControlChange(int offset, int channel, MidiControlChange index, float value)
410 {
411     int ivalue = cast(int)(value * 128.0f);
412     if (ivalue < 0)
413         ivalue = 0;
414     if (ivalue > 127)
415         ivalue = 127;
416     return makeMidiMessage(offset, channel, MidiStatus.controlChange, index, ivalue);
417 }
418 
419 
420 MidiQueue makeMidiQueue()
421 {
422     return MidiQueue(42);
423 }
424 
425 /// Priority queue for MIDI messages
426 struct MidiQueue
427 {
428 nothrow:
429 @nogc:
430 
431     private this(int dummy)
432     {
433         _outMessages = makeVec!MidiMessage();
434         initialize();
435     }
436 
437     @disable this(this);
438 
439     ~this()
440     {
441     }
442 
443     void initialize()
444     {
445         // Clears all pending MIDI messages
446         _framesElapsed = 0;
447         _insertOrder = 0;
448         _heap.clearContents();
449 
450         MidiMessageWithOrder dummy;
451         _heap.pushBack(dummy); // empty should have element
452     }
453 
454     /// Enqueue a message in the priority queue.
455     void enqueue(MidiMessage message)
456     {
457         // Tweak message to mark it with current stamp
458         // This allows to have an absolute offset for MIDI messages.
459         assert(message._offset >= 0);
460         message._offset += _framesElapsed;
461         insertElement(message);
462     }
463 
464     /// Gets all the MIDI messages for the next `frames` samples.
465     /// It is guaranteed to be in order relative to time.
466     /// These messages are valid until the next call to `getNextMidiMessages`.
467     /// The time reference is afterwards advanced by `frames`.
468     const(MidiMessage)[] getNextMidiMessages(int frames)
469     {
470         _outMessages.clearContents();
471         accumNextMidiMessages(_outMessages, frames);
472         return _outMessages[];
473     }
474 
475     /// Another way to get MIDI messages is to pushBack them into an external buffer,
476     /// in order to accumulate them for different sub-buffers.
477     /// When you use this API, you need to provide a Vec yourself.
478     void accumNextMidiMessages(ref Vec!MidiMessage output, int frames)
479     {
480         int framesLimit = _framesElapsed;
481 
482         if (!empty())
483         {
484             MidiMessage m = minElement();
485 
486             while(m._offset < (_framesElapsed + frames))
487             {
488                 // Subtract the timestamp again
489                 m._offset -= _framesElapsed;
490                 assert(m._offset >= 0);
491                 assert(m._offset < frames);
492                 output.pushBack(m);
493                 popMinElement();
494 
495                 if (empty())
496                     break;
497 
498                 m = minElement();
499             }
500         }
501         _framesElapsed += frames;
502     }
503 
504 private:
505 
506     // Frames elapsed since the beginning
507     int _framesElapsed = 0;
508 
509     // Scratch buffer to return slices of messages on demand (messages are copied there).
510     Vec!MidiMessage _outMessages;
511 
512     //
513     // Min heap implementation below.
514     //
515 
516     int numElements() pure const
517     {
518         return cast(int)(_heap.length) - 1; // item 0 is unused
519     }
520 
521     /// Rolling counter used to disambiguate from messages pushed with the same timestamp.
522     uint _insertOrder = 0;
523 
524     // Useful slots are in 1..QueueCapacity+1
525     // means it can contain QueueCapacity items at most
526     Vec!MidiMessageWithOrder _heap; // important that this is grow-only
527 
528     static struct MidiMessageWithOrder
529     {
530         MidiMessage message;
531         uint order;
532     }
533 
534     void insertElement(MidiMessage message)
535     {
536         // Insert the element at the next available bottom level slot
537         int slot = numElements() + 1;
538 
539         // Insert at end of heap, then bubble up
540         _heap.pushBack(MidiMessageWithOrder(message, _insertOrder++));
541 
542         // Bubble up
543         while (slot > 1 && compareLargerThan(_heap[parentOf(slot)], _heap[slot]))
544         {
545             int parentIndex = parentOf(slot);
546 
547             // swap with parent if should be popped later
548             MidiMessageWithOrder tmp = _heap[slot];
549             _heap[slot] = _heap[parentIndex];
550             _heap[parentIndex] = tmp;
551 
552             slot = parentIndex;
553         }
554     }
555 
556     bool empty()
557     {
558         assert(numElements() >= 0);
559         return numElements() == 0;
560     }
561 
562     MidiMessage minElement()
563     {
564         assert(!empty);
565         return _heap[1].message;
566     }
567 
568     void popMinElement()
569     {
570         assert(!empty);
571 
572         // Put the last element into root
573         _heap[1] = _heap.popBack();
574 
575         int slot = 1;
576 
577         while (1)
578         {
579             // Looking for the minimum of self and children
580 
581             int left = leftChildOf(slot);
582             int right = rightChildOf(slot);
583             int best = slot;
584 
585             if ((left <= numElements()) && compareLargerThan(_heap[best], _heap[left]))
586             {
587                 best = left;
588             }
589             if (right <= numElements() && compareLargerThan(_heap[best], _heap[right]))
590             {
591                 best = right;
592             }
593 
594             if (best == slot) // no swap, final position
595             {
596                 // both children (if any) are larger, everything good
597                 break;
598             }
599             else
600             {
601                 // swap with smallest of children
602                 MidiMessageWithOrder tmp = _heap[slot];
603                 _heap[slot] = _heap[best];
604                 _heap[best] = tmp;
605 
606                 // continue to bubble down
607                 slot = best;
608             }
609         }
610     }
611 
612     static int parentOf(int index)
613     {
614         return index >> 1;
615     }
616 
617     static int leftChildOf(int index)
618     {
619         return 2*index;
620     }
621 
622     static int rightChildOf(int index)
623     {
624         return 2*index+1;
625     }
626 
627     // Larger means "should be popped later"
628     // Priorities can't ever be equal.
629     private static bool compareLargerThan(MidiMessageWithOrder a, MidiMessageWithOrder b)
630     {
631         if (a.message._offset != b.message._offset) 
632         {
633             return a.message._offset > b.message._offset;
634         }
635         if (a.order != b.order)
636         {
637             int diff = cast(int)(a.order - b.order);
638             return (diff > 0);
639         }
640         else
641         {
642             // Impossible, unless 2^32 messages have been pushed with the same timestamp
643             // which is perhaps too experimental as far as music goes
644             assert(false);
645         }
646     }
647 }
648 
649 unittest
650 {
651     MidiQueue queue = makeMidiQueue();
652     foreach (k; 0..2)
653     {
654         int capacity = 511;
655         // Enqueue QueueCapacity messages with decreasing timestamps
656         foreach(i; 0..capacity)
657         {
658             queue.enqueue( makeMidiMessageNoteOn(capacity-1-i, 0, 60, 100) );
659             assert(queue.numElements() == i+1);
660         }
661 
662         const(MidiMessage)[] messages = queue.getNextMidiMessages(1024);
663 
664         foreach(size_t i, m; messages)
665         {
666             // each should be in order
667             assert(m.offset == cast(int)i);
668 
669             assert(m.isNoteOn);
670         }
671     }
672 }
673 
674 // Issue #575: MidiQueue should NOT reorder messages that come with same timestamp.
675 unittest
676 {
677     MidiQueue queue = makeMidiQueue();
678     int note = 102;
679     int vel = 64;
680     int chan = 1;
681     int offset = 0;
682 
683     queue.enqueue( makeMidiMessageNoteOn(offset, chan, note, vel) );
684     queue.enqueue( makeMidiMessageNoteOn(offset, chan, note, vel) );
685     queue.enqueue( makeMidiMessageNoteOff(offset, chan, note) );
686     queue.enqueue( makeMidiMessageNoteOff(offset, chan, note) );
687 
688     const(MidiMessage)[] messages = queue.getNextMidiMessages(1024);
689     assert(messages.length == 4);
690     assert(messages[0].isNoteOn());
691     assert(messages[1].isNoteOn());
692     assert(messages[2].isNoteOff());
693     assert(messages[3].isNoteOff());
694 }
695 
696 unittest
697 {
698     ubyte[3] buf;
699     MidiMessage msg = makeMidiMessageNoteOn(0, 1, 102, 64);
700     assert(3 == msg.toBytes(null, -1));
701     assert(3 == msg.toBytes(buf.ptr, 3));
702     assert(buf == [0x91, 102, 64 ]);
703 
704     msg = makeMidiMessageChannelPressure(0, 4, 1.0f);
705     assert(2 == msg.toBytes(buf.ptr, -1));
706     assert(2 == msg.toBytes(buf.ptr, 3));
707     assert(buf == [0xD4, 127, 64 ]);
708 }