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 }