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