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     deprecated("Use statusType instead") alias status = statusType;
59     int statusType() const
60     {
61         return _statusByte >> 4;
62     }
63 
64     // Status type distinction properties
65 
66     bool isChannelAftertouch() const
67     {
68         return statusType() == MidiStatus.channelAftertouch;
69     }
70 
71     bool isControlChange() const
72     {
73         return statusType() == MidiStatus.controlChange;
74     }
75 
76     /// Checks whether the status type of the message is 'Note On'
77     /// _and the velocity value is actually greater than zero_.
78     ///
79     /// IMPORTANT: As per MIDI 1.0, a 'Note On' event with a velocity
80     ///            of zero should be treated like a 'Note Off' event.
81     ///            Which is why this function checks velocity > 0.
82     ///
83     /// See_Also:
84     ///     isNoteOff()
85     bool isNoteOn() const
86     {
87         return (statusType() == MidiStatus.noteOn) && (noteVelocity() > 0);
88     }
89 
90     /// Checks whether the status type of the message is 'Note Off'
91     /// or 'Note On' with a velocity of 0.
92     ///
93     /// IMPORTANT: As per MIDI 1.0, a 'Note On' event with a velocity
94     ///            of zero should be treated like a 'Note Off' event.
95     ///            Which is why this function checks velocity == 0.
96     ///
97     ///            Some devices send a 'Note On' message with a velocity
98     ///            value of zero instead of a real 'Note Off' message.
99     ///            Many DAWs will automatically convert such ones to
100     ///            explicit ones, but one cannot rely on this.
101     ///
102     /// See_Also:
103     ///     isNoteOn()
104     bool isNoteOff() const
105     {
106         return (statusType() == MidiStatus.noteOff)
107                ||
108                ( (statusType() == MidiStatus.noteOn) && (noteVelocity() == 0) );
109     }
110 
111     bool isPitchBend() const
112     {
113         return statusType() == MidiStatus.pitchWheel;
114     }
115 
116     alias isPitchWheel = isPitchBend;
117 
118     bool isPolyAftertouch() const
119     {
120         return statusType() == MidiStatus.polyAftertouch;
121     }
122 
123     bool isProgramChange() const
124     {
125         return statusType() == MidiStatus.programChange;
126     }
127 
128     // Data value properties
129 
130     /// Channel pressure
131     int channelAftertouch() const
132     {
133         assert(isChannelAftertouch());
134         return _data1;
135     }
136 
137     /// Number of the changed controller
138     MidiControlChange controlChangeControl() const
139     {
140         assert(isControlChange());
141         return cast(MidiControlChange)(_data1);
142     }
143 
144     /// Controller's new value
145     ///
146     /// Returns: [1 .. 127]
147     int controlChangeValue() const
148     {
149         assert(isControlChange());
150         return _data2;
151     }
152 
153     /// Controller's new value
154     ///
155     /// Returns: [0.0 .. 1.0]
156     float controlChangeValue0to1() const
157     {
158         return cast(float)(controlChangeValue()) / 127.0f;
159     }
160 
161     /// Returns: true = on
162     bool controlChangeOnOff() const
163     {
164         return controlChangeValue() >= 64;
165     }
166 
167     int noteNumber() const
168     {
169         assert(isNoteOn() || isNoteOff() || isPolyAftertouch());
170         return _data1;
171     }
172 
173     int noteVelocity() const
174     {
175         return _data2;
176     }
177 
178     /// Returns: [-1.0 .. 1.0[
179     float pitchBend() const
180     {
181         assert(isPitchBend());
182         immutable int iVal = (_data2 << 7) + _data1;
183         return cast(float) (iVal - 8192) / 8192.0f;
184     }
185 
186     alias pitchWheel = pitchBend;
187 
188     /// Amount of pressure
189     int polyAftertouch() const
190     {
191         assert(isPolyAftertouch());
192         return _data2;
193     }
194 
195     /// Program number
196     int program() const
197     {
198         assert(isProgramChange());
199         return _data1;
200     }
201 
202 private:
203     int _offset = 0;
204 
205     ubyte _statusByte = 0;
206 
207     ubyte _data1 = 0;
208 
209     ubyte _data2 = 0;
210 }
211 
212 
213 enum MidiStatus : ubyte
214 {
215     none = 0,
216     noteOff = 8,
217     noteOn = 9,
218     polyAftertouch = 10,
219     controlChange = 11,
220     programChange = 12,
221     channelAftertouch = 13,
222     pitchBend = 14,
223     pitchWheel = pitchBend
224 };
225 
226 enum MidiControlChange : ubyte
227 {
228     modWheel = 1,
229     breathController = 2,
230     undefined003 = 3,
231     footController = 4,
232     portamentoTime = 5,
233     channelVolume = 7,
234     balance = 8,
235     undefined009 = 9,
236     pan = 10,
237     expressionController = 11,
238     effectControl1 = 12,
239     effectControl2 = 13,
240     generalPurposeController1 = 16,
241     generalPurposeController2 = 17,
242     generalPurposeController3 = 18,
243     generalPurposeController4 = 19,
244     sustainOnOff = 64,
245     portamentoOnOff = 65,
246     sustenutoOnOff = 66,
247     softPedalOnOff = 67,
248     legatoOnOff = 68,
249     hold2OnOff = 69,
250     soundVariation = 70,
251     resonance = 71,
252     releaseTime = 72,
253     attackTime = 73,
254     cutoffFrequency = 74,
255     decayTime = 75,
256     vibratoRate = 76,
257     vibratoDepth = 77,
258     vibratoDelay = 78,
259     soundControllerUndefined = 79,
260     tremoloDepth = 92,
261     chorusDepth = 93,
262     phaserDepth = 95,
263     allNotesOff = 123
264 }
265 
266 nothrow @nogc:
267 
268 MidiMessage makeMidiMessage(int offset, int channel, MidiStatus statusType, int data1, int data2)
269 {
270     assert(channel >= 0 && channel <= 15);
271     assert(statusType >= 0 && statusType <= 15);
272     assert(data1 >= 0 && data2 <= 255);
273     assert(data1 >= 0 && data2 <= 255);
274     MidiMessage msg;
275     msg._offset = offset;
276     msg._statusByte = cast(ubyte)( channel | (statusType << 4) );
277     msg._data1 = cast(ubyte)data1;
278     msg._data2 = cast(ubyte)data2;
279     return msg;
280 }
281 
282 MidiMessage makeMidiMessageNoteOn(int offset, int channel, int noteNumber, int velocity)
283 {
284     return makeMidiMessage(offset, channel, MidiStatus.noteOn, noteNumber, velocity);
285 }
286 
287 MidiMessage makeMidiMessageNoteOff(int offset, int channel, int noteNumber)
288 {
289     return makeMidiMessage(offset, channel, MidiStatus.noteOff, noteNumber, 0);
290 }
291 
292 MidiMessage makeMidiMessagePitchWheel(int offset, int channel, float value)
293 {
294     int ivalue = 8192 + cast(int)(value * 8192.0);
295     if (ivalue < 0)
296         ivalue = 0;
297     if (ivalue > 16383)
298         ivalue = 16383;
299     return makeMidiMessage(offset, channel, MidiStatus.pitchWheel, ivalue & 0x7F, ivalue >> 7);
300 }
301 
302 MidiMessage makeMidiMessageControlChange(int offset, int channel, MidiControlChange index, float value)
303 {
304     // MAYDO: mapping is a bit strange here, not sure it can make +127 except exactly for 1.0f
305     return makeMidiMessage(offset, channel, MidiStatus.controlChange, index, cast(int)(value * 127.0f) );
306 }
307 
308 
309 MidiQueue makeMidiQueue()
310 {
311     return MidiQueue(42);
312 }
313 
314 /// Queue for MIDI messages
315 /// TODO: use a priority queue
316 struct MidiQueue
317 {
318 nothrow:
319 @nogc:
320 
321     enum QueueCapacity = 511;
322 
323     private this(int dummy)
324     {
325         _outMessages = makeVec!MidiMessage(QueueCapacity);
326     }
327 
328     @disable this(this);
329 
330     ~this()
331     {
332     }
333 
334     void initialize()
335     {
336         // Clears all pending MIDI messages
337         _framesElapsed = 0;
338         _numElements = 0;
339     }
340 
341     /// Enqueue a message in the priority queue.
342     void enqueue(MidiMessage message)
343     {
344         // Tweak message to mark it with current stamp
345         // This allows to have an absolute offset for MIDI messages.
346         assert(message._offset >= 0);
347         message._offset += _framesElapsed;
348         insertElement(message);
349     }
350 
351     /// Gets all the MIDI messages for the next `frames` samples.
352     /// It is guaranteed to be in order relative to time.
353     /// These messages are valid until the next call to `getNextMidiMessages`.
354     const(MidiMessage)[] getNextMidiMessages(int frames)
355     {
356         _outMessages.clearContents();
357 
358         int framesLimit = _framesElapsed;
359 
360         if (!empty())
361         {
362             MidiMessage m = minElement();
363 
364             while(m._offset < (_framesElapsed + frames))
365             {
366                 // Subtract the timestamp again
367                 m._offset -= _framesElapsed;
368                 assert(m._offset >= 0);
369                 assert(m._offset < frames);
370                 _outMessages.pushBack(m);
371                 popMinElement();
372 
373                 if (empty())
374                     break;
375 
376                 m = minElement();
377             }
378         }
379         _framesElapsed += frames;
380         return _outMessages[];
381     }
382 
383 private:
384 
385     // Frames elapsed since the beginning
386     int _framesElapsed = 0;
387 
388     // Scratch buffer to return slices of messages on demand (messages are copied there).
389     Vec!MidiMessage _outMessages;
390 
391     //
392     // Min heap implementation below.
393     //
394 
395     int _numElements = 0;
396 
397     // Useful slots are in 1..QueueCapacity+1
398     // means it can contain QueueCapacity items at most
399     MidiMessage[QueueCapacity+1] _heap;
400 
401     void insertElement(MidiMessage message)
402     {
403         // Insert the element at the next available bottom level slot
404         int slot = _numElements + 1;
405 
406         _numElements++;
407 
408         if (slot >= _heap.length)
409         {
410             // TODO: limitation here, we can't accept more than QueueCapacity MIDI messages in the queue
411             debug
412             {
413                 // MIDI messages heap is on full capacity.
414                 // That can happen because you have forgotten to call `getNextMidiMessages`
415                 // with the necessary number of frames.
416                 assert(false);
417             }
418             else
419             {
420                 return; // dropping excessive message
421             }
422         }
423 
424         _heap[slot] = message;
425 
426         // Bubble up
427         while (slot > 1 && _heap[parentOf(slot)]._offset > _heap[slot]._offset)
428         {
429             // swap with parent if larger
430             swap(_heap[slot], _heap[parentOf(slot)]);
431             slot = parentOf(slot);
432         }
433     }
434 
435     bool empty()
436     {
437         assert(_numElements >= 0);
438         return _numElements == 0;
439     }
440 
441     MidiMessage minElement()
442     {
443         assert(!empty);
444         return _heap[1];
445     }
446 
447     void popMinElement()
448     {
449         assert(!empty);
450 
451         // Put the last element into root
452         _heap[1] = _heap[_numElements];
453 
454         _numElements = _numElements-1;
455 
456         int slot = 1;
457 
458         if (slot >= _heap.length)
459         {
460             debug assert(false); // dropping excessive message
461         }
462 
463         while (1)
464         {
465             // Looking for the minimum of self and children
466 
467             int left = leftChildOf(slot);
468             int right = rightChildOf(slot);
469             int best = slot;
470             if (left <= _numElements && _heap[left]._offset < _heap[best]._offset)
471                 best = left;
472             if (right <= _numElements && _heap[right]._offset < _heap[best]._offset)
473                 best = right;
474 
475             if (best == slot) // no swap, final position
476             {
477                 // both children (if any) are larger, everything good
478                 break;
479             }
480             else
481             {
482                 // swap with smallest of children
483                 swap(_heap[slot], _heap[best]);
484 
485                 // continue to bubble down
486                 slot = best;
487             }
488         }
489     }
490 
491     static int parentOf(int index)
492     {
493         return index >> 1;
494     }
495 
496     static int leftChildOf(int index)
497     {
498         return 2*index;
499     }
500 
501     static int rightChildOf(int index)
502     {
503         return 2*index+1;
504     }
505 }
506 
507 unittest
508 {
509     MidiQueue queue = makeMidiQueue();
510 
511     foreach (k; 0..2)
512     {
513         // Enqueue QueueCapacity messages with decreasing timestamps
514         foreach(i; 0..MidiQueue.QueueCapacity)
515         {
516             queue.enqueue( makeMidiMessageNoteOn(MidiQueue.QueueCapacity-1-i, 0, 60, 100) );
517             assert(queue._numElements == i+1);
518         }
519 
520         const(MidiMessage)[] messages = queue.getNextMidiMessages(1024);
521         foreach(int i, m; messages)
522         {
523             import core.stdc.stdio;
524             // each should be in order
525             assert(m.offset == i);
526             assert(m.isNoteOn);
527         }
528     }
529 }