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 ///     offset  = Offset in current buffer, in frames.
387 ///     channel = MIDI Channel.
388 ///     value = Amount of pitch, -1 to 1. Not sure what unit! FUTUURE understand how much semitones MIDI says it should be.
389 MidiMessage makeMidiMessagePitchWheel(int offset, int channel, float value)
390 {
391     int ivalue = 8192 + cast(int)(value * 8192.0);
392     if (ivalue < 0)
393         ivalue = 0;
394     if (ivalue > 16383)
395         ivalue = 16383;
396     return makeMidiMessage(offset, channel, MidiStatus.pitchWheel, ivalue & 0x7F, ivalue >> 7);
397 }
398 
399 /// Make a Channel Aftertouch MIDI message. It has no note number, and acts for the whole instrument.
400 /// Also called: Channel Pressure.
401 MidiMessage makeMidiMessageChannelPressure(int offset, int channel, float value)
402 {
403     int ivalue = cast(int)(value * 128.0);
404     if (ivalue < 0)
405         ivalue = 0;
406     if (ivalue > 127)
407         ivalue = 127;
408     return makeMidiMessage(offset, channel, MidiStatus.channelAftertouch, ivalue, 0); // no note number
409 }
410 
411 MidiMessage makeMidiMessageControlChange(int offset, int channel, MidiControlChange index, float value)
412 {
413     int ivalue = cast(int)(value * 128.0f);
414     if (ivalue < 0)
415         ivalue = 0;
416     if (ivalue > 127)
417         ivalue = 127;
418     return makeMidiMessage(offset, channel, MidiStatus.controlChange, index, ivalue);
419 }
420 
421 
422 MidiQueue makeMidiQueue()
423 {
424     return MidiQueue(42);
425 }
426 
427 /// Priority queue for MIDI messages
428 struct MidiQueue
429 {
430 nothrow:
431 @nogc:
432 
433     private this(int dummy)
434     {
435         _outMessages = makeVec!MidiMessage();
436         initialize();
437     }
438 
439     @disable this(this);
440 
441     ~this()
442     {
443     }
444 
445     void initialize()
446     {
447         // Clears all pending MIDI messages
448         _framesElapsed = 0;
449         _insertOrder = 0;
450         _heap.clearContents();
451 
452         MidiMessageWithOrder dummy;
453         _heap.pushBack(dummy); // empty should have element
454     }
455 
456     /// Enqueue a message in the priority queue.
457     void enqueue(MidiMessage message)
458     {
459         // Tweak message to mark it with current stamp
460         // This allows to have an absolute offset for MIDI messages.
461         assert(message._offset >= 0);
462         message._offset += _framesElapsed;
463         insertElement(message);
464     }
465 
466     /// Gets all the MIDI messages for the next `frames` samples.
467     /// It is guaranteed to be in order relative to time.
468     /// These messages are valid until the next call to `getNextMidiMessages`.
469     /// The time reference is afterwards advanced by `frames`.
470     const(MidiMessage)[] getNextMidiMessages(int frames)
471     {
472         _outMessages.clearContents();
473         accumNextMidiMessages(_outMessages, frames);
474         return _outMessages[];
475     }
476 
477     /// Another way to get MIDI messages is to pushBack them into an external buffer,
478     /// in order to accumulate them for different sub-buffers.
479     /// When you use this API, you need to provide a Vec yourself.
480     void accumNextMidiMessages(ref Vec!MidiMessage output, int frames)
481     {
482         int framesLimit = _framesElapsed;
483 
484         if (!empty())
485         {
486             MidiMessage m = minElement();
487 
488             while(m._offset < (_framesElapsed + frames))
489             {
490                 // Subtract the timestamp again
491                 m._offset -= _framesElapsed;
492                 assert(m._offset >= 0);
493                 assert(m._offset < frames);
494                 output.pushBack(m);
495                 popMinElement();
496 
497                 if (empty())
498                     break;
499 
500                 m = minElement();
501             }
502         }
503         _framesElapsed += frames;
504     }
505 
506 private:
507 
508     // Frames elapsed since the beginning
509     int _framesElapsed = 0;
510 
511     // Scratch buffer to return slices of messages on demand (messages are copied there).
512     Vec!MidiMessage _outMessages;
513 
514     //
515     // Min heap implementation below.
516     //
517 
518     int numElements() pure const
519     {
520         return cast(int)(_heap.length) - 1; // item 0 is unused
521     }
522 
523     /// Rolling counter used to disambiguate from messages pushed with the same timestamp.
524     uint _insertOrder = 0;
525 
526     // Useful slots are in 1..QueueCapacity+1
527     // means it can contain QueueCapacity items at most
528     Vec!MidiMessageWithOrder _heap; // important that this is grow-only
529 
530     static struct MidiMessageWithOrder
531     {
532         MidiMessage message;
533         uint order;
534     }
535 
536     void insertElement(MidiMessage message)
537     {
538         // Insert the element at the next available bottom level slot
539         int slot = numElements() + 1;
540 
541         // Insert at end of heap, then bubble up
542         _heap.pushBack(MidiMessageWithOrder(message, _insertOrder++));
543 
544         // Bubble up
545         while (slot > 1 && compareLargerThan(_heap[parentOf(slot)], _heap[slot]))
546         {
547             int parentIndex = parentOf(slot);
548 
549             // swap with parent if should be popped later
550             MidiMessageWithOrder tmp = _heap[slot];
551             _heap[slot] = _heap[parentIndex];
552             _heap[parentIndex] = tmp;
553 
554             slot = parentIndex;
555         }
556     }
557 
558     bool empty()
559     {
560         assert(numElements() >= 0);
561         return numElements() == 0;
562     }
563 
564     MidiMessage minElement()
565     {
566         assert(!empty);
567         return _heap[1].message;
568     }
569 
570     void popMinElement()
571     {
572         assert(!empty);
573 
574         // Put the last element into root
575         _heap[1] = _heap.popBack();
576 
577         int slot = 1;
578 
579         while (1)
580         {
581             // Looking for the minimum of self and children
582 
583             int left = leftChildOf(slot);
584             int right = rightChildOf(slot);
585             int best = slot;
586 
587             if ((left <= numElements()) && compareLargerThan(_heap[best], _heap[left]))
588             {
589                 best = left;
590             }
591             if (right <= numElements() && compareLargerThan(_heap[best], _heap[right]))
592             {
593                 best = right;
594             }
595 
596             if (best == slot) // no swap, final position
597             {
598                 // both children (if any) are larger, everything good
599                 break;
600             }
601             else
602             {
603                 // swap with smallest of children
604                 MidiMessageWithOrder tmp = _heap[slot];
605                 _heap[slot] = _heap[best];
606                 _heap[best] = tmp;
607 
608                 // continue to bubble down
609                 slot = best;
610             }
611         }
612     }
613 
614     static int parentOf(int index)
615     {
616         return index >> 1;
617     }
618 
619     static int leftChildOf(int index)
620     {
621         return 2*index;
622     }
623 
624     static int rightChildOf(int index)
625     {
626         return 2*index+1;
627     }
628 
629     // Larger means "should be popped later"
630     // Priorities can't ever be equal.
631     private static bool compareLargerThan(MidiMessageWithOrder a, MidiMessageWithOrder b)
632     {
633         if (a.message._offset != b.message._offset) 
634         {
635             return a.message._offset > b.message._offset;
636         }
637         if (a.order != b.order)
638         {
639             int diff = cast(int)(a.order - b.order);
640             return (diff > 0);
641         }
642         else
643         {
644             // Impossible, unless 2^32 messages have been pushed with the same timestamp
645             // which is perhaps too experimental as far as music goes
646             assert(false);
647         }
648     }
649 }
650 
651 unittest
652 {
653     MidiQueue queue = makeMidiQueue();
654     foreach (k; 0..2)
655     {
656         int capacity = 511;
657         // Enqueue QueueCapacity messages with decreasing timestamps
658         foreach(i; 0..capacity)
659         {
660             queue.enqueue( makeMidiMessageNoteOn(capacity-1-i, 0, 60, 100) );
661             assert(queue.numElements() == i+1);
662         }
663 
664         const(MidiMessage)[] messages = queue.getNextMidiMessages(1024);
665 
666         foreach(size_t i, m; messages)
667         {
668             // each should be in order
669             assert(m.offset == cast(int)i);
670 
671             assert(m.isNoteOn);
672         }
673     }
674 }
675 
676 // Issue #575: MidiQueue should NOT reorder messages that come with same timestamp.
677 unittest
678 {
679     MidiQueue queue = makeMidiQueue();
680     int note = 102;
681     int vel = 64;
682     int chan = 1;
683     int offset = 0;
684 
685     queue.enqueue( makeMidiMessageNoteOn(offset, chan, note, vel) );
686     queue.enqueue( makeMidiMessageNoteOn(offset, chan, note, vel) );
687     queue.enqueue( makeMidiMessageNoteOff(offset, chan, note) );
688     queue.enqueue( makeMidiMessageNoteOff(offset, chan, note) );
689 
690     const(MidiMessage)[] messages = queue.getNextMidiMessages(1024);
691     assert(messages.length == 4);
692     assert(messages[0].isNoteOn());
693     assert(messages[1].isNoteOn());
694     assert(messages[2].isNoteOff());
695     assert(messages[3].isNoteOff());
696 }
697 
698 unittest
699 {
700     ubyte[3] buf;
701     MidiMessage msg = makeMidiMessageNoteOn(0, 1, 102, 64);
702     assert(3 == msg.toBytes(null, -1));
703     assert(3 == msg.toBytes(buf.ptr, 3));
704     assert(buf == [0x91, 102, 64 ]);
705 
706     msg = makeMidiMessageChannelPressure(0, 4, 1.0f);
707     assert(2 == msg.toBytes(buf.ptr, -1));
708     assert(2 == msg.toBytes(buf.ptr, 3));
709     assert(buf == [0xD4, 127, 64 ]);
710 }