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 }