1 /*
2 Cockos WDL License
3 
4 Copyright (C) 2005 - 2015 Cockos Incorporated
5 Copyright (C) 2015 and later 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 module dplug.client.midi;
18 
19 import std.algorithm.mutation;
20 import dplug.core.alignedbuffer;
21 
22 /// It's the same abstraction that in IPlug.
23 /// For VST raw Midi messages are passed.
24 /// For AU Midi messages gets synthesized.
25 struct MidiMessage
26 {
27 pure:
28 nothrow:
29 @nogc:
30 
31     this( int offset, ubyte status, ubyte data1, ubyte data2)
32     {
33         _offset = offset;
34         _status = status;
35         _data1 = data1;
36         _data2 = data2;
37     }
38 
39     int offset() const
40     {
41         return _offset;
42     } 
43 
44     int channel() const
45     {
46         return status & 0x0F;
47     }
48 
49     int status() const
50     {
51         return _status >> 4;
52     }
53 
54     bool isNoteOn() const
55     {
56         return status() == MidiStatus.noteOn;
57     }
58 
59     bool isNoteOff() const
60     {
61         return status() == MidiStatus.noteOff;
62     }
63 
64     int noteNumber() const
65     {
66         assert(isNoteOn() || isNoteOff());
67         return _data1;
68     }
69 
70     int noteVelocity() const
71     {
72         assert(isNoteOn() || isNoteOff());
73         return _data2;
74     }
75 
76 private:
77     int _offset = 0;
78 
79     ubyte _status = 0;
80 
81     ubyte _data1 = 0;
82 
83     ubyte _data2 = 0; 
84 }
85 
86 
87 enum MidiStatus : ubyte
88 {
89     none = 0,
90     noteOff = 8,
91     noteOn = 9,
92     polyAftertouch = 10,
93     controlChange = 11,
94     programChange = 12,
95     channelAftertouch = 13,
96     pitchWheel = 14
97 };
98 
99 enum MidiControlChange : ubyte
100 {
101     modWheel = 1,
102     breathController = 2,
103     undefined003 = 3,
104     footController = 4,
105     portamentoTime = 5,
106     channelVolume = 7,
107     balance = 8,
108     undefined009 = 9,
109     pan = 10,
110     expressionController = 11,
111     effectControl1 = 12,
112     effectControl2 = 13,
113     generalPurposeController1 = 16,
114     generalPurposeController2 = 17,
115     generalPurposeController3 = 18,
116     generalPurposeController4 = 19,
117     sustainOnOff = 64,
118     portamentoOnOff = 65,
119     sustenutoOnOff = 66,
120     softPedalOnOff = 67,
121     legatoOnOff = 68,
122     hold2OnOff = 69,
123     soundVariation = 70,
124     resonance = 71,
125     releaseTime = 72,
126     attackTime = 73,
127     cutoffFrequency = 74,
128     decayTime = 75,
129     vibratoRate = 76,
130     vibratoDepth = 77,
131     vibratoDelay = 78,
132     soundControllerUndefined = 79,
133     tremoloDepth = 92,
134     chorusDepth = 93,
135     phaserDepth = 95,
136     allNotesOff = 123
137 }
138 
139 nothrow @nogc:
140 
141 MidiMessage makeMidiMessage(int offset, int channel, MidiStatus status, int data1, int data2)
142 {
143     assert(channel >= 0 && channel <= 15);
144     assert(status >= 0 && status <= 15);
145     assert(data1 >= 0 && data2 <= 255);
146     assert(data1 >= 0 && data2 <= 255);
147     MidiMessage msg;
148     msg._offset = offset;
149     msg._status = cast(ubyte)( channel | (status << 4) );
150     msg._data1 = cast(ubyte)data1;
151     msg._data2 = cast(ubyte)data2;
152     return msg;
153 }
154 
155 MidiMessage makeMidiMessageNoteOn(int offset, int channel, int noteNumber, int velocity)
156 {    
157     return makeMidiMessage(offset, channel, MidiStatus.noteOn, noteNumber, velocity);
158 }
159 
160 MidiMessage makeMidiMessageNoteOff(int offset, int channel, int noteNumber)
161 {    
162     return makeMidiMessage(offset, channel, MidiStatus.noteOff, noteNumber, 0);
163 }
164 
165 MidiMessage makeMidiMessagePitchWheel(int offset, int channel, float value)
166 {
167     int ivalue = 8192 + cast(int)(value * 8192.0);
168     if (ivalue < 0) 
169         ivalue = 0;
170     if (ivalue > 16383) 
171         ivalue = 16383;
172     return makeMidiMessage(offset, channel, MidiStatus.pitchWheel, ivalue & 0x7F, ivalue >> 7);
173 }
174 
175 MidiMessage makeMidiMessageControlChange(int offset, int channel, MidiControlChange index, float value)
176 {
177     // MAYDO: mapping is a bit strange here, not sure it can make +127 except exactly for 1.0f
178     return makeMidiMessage(offset, channel, MidiStatus.controlChange, index, cast(int)(value * 127.0f) );    
179 }
180 
181 
182 MidiQueue makeMidiQueue()
183 {
184     return MidiQueue(42);
185 }
186 
187 /// Queue for MIDI messages
188 /// TODO: use a priority queue
189 struct MidiQueue
190 {
191 nothrow:
192 @nogc:
193 
194     enum QueueCapacity = 511;
195 
196     private this(int dummy)
197     {
198         _outMessages = makeAlignedBuffer!MidiMessage(QueueCapacity);
199     }
200 
201     @disable this(this);
202 
203     ~this()
204     {
205     }
206 
207     void initialize()
208     {
209         // Clears all pending MIDI messages
210         _framesElapsed = 0;
211         _numElements = 0;
212     }
213 
214     /// Enqueue a message in the priority queue.
215     void enqueue(MidiMessage message)
216     {
217         // Tweak message to mark it with current stamp
218         // This allows to have an absolute offset for MIDI messages.
219         assert(message._offset >= 0);
220         message._offset += _framesElapsed;
221         insertElement(message);
222     }
223 
224     /// Gets all the MIDI messages for the next `frames` samples.
225     /// It is guaranteed to be in order relative to time.
226     /// These messages are valid until the next call to `getNextMidiMessages`.
227     const(MidiMessage)[] getNextMidiMessages(int frames)
228     {
229         _outMessages.clearContents();
230 
231         int framesLimit = _framesElapsed;
232 
233         if (!empty())
234         {
235             MidiMessage m = minElement();
236 
237             while(m._offset < (_framesElapsed + frames))
238             {
239                 // Subtract the timestamp again
240                 m._offset -= _framesElapsed;
241                 assert(m._offset >= 0);
242                 assert(m._offset < frames);
243                 _outMessages.pushBack(m);
244                 popMinElement();
245 
246                 if (empty())
247                     break;
248 
249                 m = minElement();
250             }
251         }
252         _framesElapsed += frames;
253         return _outMessages[];
254     }
255 
256 private:
257 
258     // Frames elapsed since the beginning
259     int _framesElapsed = 0;
260 
261     // Scratch buffer to return slices of messages on demand (messages are copied there).
262     AlignedBuffer!MidiMessage _outMessages;
263 
264     //
265     // Min heap implementation below.
266     //
267 
268     int _numElements = 0;
269 
270     // Useful slots are in 1..QueueCapacity+1
271     // means it can contain QueueCapacity items at most
272     MidiMessage[QueueCapacity+1] _heap;    
273 
274     void insertElement(MidiMessage message)
275     {
276         // Insert the element at the next available bottom level slot
277         int slot = _numElements + 1;
278 
279         _numElements++;
280 
281         if (slot >= _heap.length)
282         {
283             // TODO: limitation here, we can't accept more than QueueCapacity MIDI messages in the queue
284             debug
285             {
286                 // MIDI messages heap is on full capacity.
287                 // That can happen because you have forgotten to call `getNextMidiMessages` 
288                 // with the necessary number of frames.
289                 assert(false); 
290             }
291             else
292             {
293                 return; // dropping excessive message
294             }
295         }
296 
297         _heap[slot] = message;
298 
299         // Bubble up
300         while (slot > 1 && _heap[parentOf(slot)]._offset > _heap[slot]._offset)
301         {
302             // swap with parent if larger
303             swap(_heap[slot], _heap[parentOf(slot)]);
304             slot = parentOf(slot);
305         }
306     }
307 
308     bool empty()
309     {
310         assert(_numElements >= 0);
311         return _numElements == 0;
312     }
313 
314     MidiMessage minElement()
315     {
316         assert(!empty);
317         return _heap[1];
318     }
319 
320     void popMinElement()
321     {
322         assert(!empty);
323 
324         // Put the last element into root
325         _heap[1] = _heap[_numElements];
326 
327         _numElements = _numElements-1;
328 
329         int slot = 1;
330 
331         if (slot >= _heap.length)
332         {
333             debug assert(false); // dropping excessive message
334         }
335 
336         while (1)
337         {
338             // Looking for the minimum of self and children
339 
340             int left = leftChildOf(slot);
341             int right = rightChildOf(slot);
342             int best = slot;
343             if (left <= _numElements && _heap[left]._offset < _heap[best]._offset)
344                 best = left;
345             if (right <= _numElements && _heap[right]._offset < _heap[best]._offset)
346                 best = right;
347 
348             if (best == slot) // no swap, final position
349             {
350                 // both children (if any) are larger, everything good
351                 break;
352             }
353             else
354             {
355                 // swap with smallest of children
356                 swap(_heap[slot], _heap[best]);
357 
358                 // continue to bubble down
359                 slot = best;
360             }
361         }
362     }
363 
364     static int parentOf(int index)
365     {
366         return index >> 1;
367     }
368 
369     static int leftChildOf(int index)
370     {
371         return 2*index;
372     }
373 
374     static int rightChildOf(int index)
375     {
376         return 2*index+1;
377     }
378 }
379 
380 unittest
381 {
382     MidiQueue queue = makeMidiQueue();
383 
384     foreach (k; 0..2)
385     {
386         // Enqueue QueueCapacity messages with decreasing timestamps
387         foreach(i; 0..MidiQueue.QueueCapacity)
388         {
389             queue.enqueue( makeMidiMessageNoteOn(MidiQueue.QueueCapacity-1-i, 0, 60, 100) );
390             assert(queue._numElements == i+1);
391         }
392 
393         const(MidiMessage)[] messages = queue.getNextMidiMessages(1024);
394         foreach(int i, m; messages)
395         {
396             import core.stdc.stdio;
397             // each should be in order
398             assert(m.offset == i);
399             assert(m.isNoteOn);
400         }
401     }
402 }