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