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 /// offset = Offset in current buffer, in frames. 387 /// channel = MIDI Channel. 388 /// value = Amount of pitch, -1 to 1. Not sure what unit! FUTUURE understand how much semitones MIDI says it should be. 389 MidiMessage makeMidiMessagePitchWheel(int offset, int channel, float value) 390 { 391 int ivalue = 8192 + cast(int)(value * 8192.0); 392 if (ivalue < 0) 393 ivalue = 0; 394 if (ivalue > 16383) 395 ivalue = 16383; 396 return makeMidiMessage(offset, channel, MidiStatus.pitchWheel, ivalue & 0x7F, ivalue >> 7); 397 } 398 399 /// Make a Channel Aftertouch MIDI message. It has no note number, and acts for the whole instrument. 400 /// Also called: Channel Pressure. 401 MidiMessage makeMidiMessageChannelPressure(int offset, int channel, float value) 402 { 403 int ivalue = cast(int)(value * 128.0); 404 if (ivalue < 0) 405 ivalue = 0; 406 if (ivalue > 127) 407 ivalue = 127; 408 return makeMidiMessage(offset, channel, MidiStatus.channelAftertouch, ivalue, 0); // no note number 409 } 410 411 MidiMessage makeMidiMessageControlChange(int offset, int channel, MidiControlChange index, float value) 412 { 413 int ivalue = cast(int)(value * 128.0f); 414 if (ivalue < 0) 415 ivalue = 0; 416 if (ivalue > 127) 417 ivalue = 127; 418 return makeMidiMessage(offset, channel, MidiStatus.controlChange, index, ivalue); 419 } 420 421 422 MidiQueue makeMidiQueue() 423 { 424 return MidiQueue(42); 425 } 426 427 /// Priority queue for MIDI messages 428 struct MidiQueue 429 { 430 nothrow: 431 @nogc: 432 433 private this(int dummy) 434 { 435 _outMessages = makeVec!MidiMessage(); 436 initialize(); 437 } 438 439 @disable this(this); 440 441 ~this() 442 { 443 } 444 445 void initialize() 446 { 447 // Clears all pending MIDI messages 448 _framesElapsed = 0; 449 _insertOrder = 0; 450 _heap.clearContents(); 451 452 MidiMessageWithOrder dummy; 453 _heap.pushBack(dummy); // empty should have element 454 } 455 456 /// Enqueue a message in the priority queue. 457 void enqueue(MidiMessage message) 458 { 459 // Tweak message to mark it with current stamp 460 // This allows to have an absolute offset for MIDI messages. 461 assert(message._offset >= 0); 462 message._offset += _framesElapsed; 463 insertElement(message); 464 } 465 466 /// Gets all the MIDI messages for the next `frames` samples. 467 /// It is guaranteed to be in order relative to time. 468 /// These messages are valid until the next call to `getNextMidiMessages`. 469 /// The time reference is afterwards advanced by `frames`. 470 const(MidiMessage)[] getNextMidiMessages(int frames) 471 { 472 _outMessages.clearContents(); 473 accumNextMidiMessages(_outMessages, frames); 474 return _outMessages[]; 475 } 476 477 /// Another way to get MIDI messages is to pushBack them into an external buffer, 478 /// in order to accumulate them for different sub-buffers. 479 /// When you use this API, you need to provide a Vec yourself. 480 void accumNextMidiMessages(ref Vec!MidiMessage output, int frames) 481 { 482 int framesLimit = _framesElapsed; 483 484 if (!empty()) 485 { 486 MidiMessage m = minElement(); 487 488 while(m._offset < (_framesElapsed + frames)) 489 { 490 // Subtract the timestamp again 491 m._offset -= _framesElapsed; 492 assert(m._offset >= 0); 493 assert(m._offset < frames); 494 output.pushBack(m); 495 popMinElement(); 496 497 if (empty()) 498 break; 499 500 m = minElement(); 501 } 502 } 503 _framesElapsed += frames; 504 } 505 506 private: 507 508 // Frames elapsed since the beginning 509 int _framesElapsed = 0; 510 511 // Scratch buffer to return slices of messages on demand (messages are copied there). 512 Vec!MidiMessage _outMessages; 513 514 // 515 // Min heap implementation below. 516 // 517 518 int numElements() pure const 519 { 520 return cast(int)(_heap.length) - 1; // item 0 is unused 521 } 522 523 /// Rolling counter used to disambiguate from messages pushed with the same timestamp. 524 uint _insertOrder = 0; 525 526 // Useful slots are in 1..QueueCapacity+1 527 // means it can contain QueueCapacity items at most 528 Vec!MidiMessageWithOrder _heap; // important that this is grow-only 529 530 static struct MidiMessageWithOrder 531 { 532 MidiMessage message; 533 uint order; 534 } 535 536 void insertElement(MidiMessage message) 537 { 538 // Insert the element at the next available bottom level slot 539 int slot = numElements() + 1; 540 541 // Insert at end of heap, then bubble up 542 _heap.pushBack(MidiMessageWithOrder(message, _insertOrder++)); 543 544 // Bubble up 545 while (slot > 1 && compareLargerThan(_heap[parentOf(slot)], _heap[slot])) 546 { 547 int parentIndex = parentOf(slot); 548 549 // swap with parent if should be popped later 550 MidiMessageWithOrder tmp = _heap[slot]; 551 _heap[slot] = _heap[parentIndex]; 552 _heap[parentIndex] = tmp; 553 554 slot = parentIndex; 555 } 556 } 557 558 bool empty() 559 { 560 assert(numElements() >= 0); 561 return numElements() == 0; 562 } 563 564 MidiMessage minElement() 565 { 566 assert(!empty); 567 return _heap[1].message; 568 } 569 570 void popMinElement() 571 { 572 assert(!empty); 573 574 // Put the last element into root 575 _heap[1] = _heap.popBack(); 576 577 int slot = 1; 578 579 while (1) 580 { 581 // Looking for the minimum of self and children 582 583 int left = leftChildOf(slot); 584 int right = rightChildOf(slot); 585 int best = slot; 586 587 if ((left <= numElements()) && compareLargerThan(_heap[best], _heap[left])) 588 { 589 best = left; 590 } 591 if (right <= numElements() && compareLargerThan(_heap[best], _heap[right])) 592 { 593 best = right; 594 } 595 596 if (best == slot) // no swap, final position 597 { 598 // both children (if any) are larger, everything good 599 break; 600 } 601 else 602 { 603 // swap with smallest of children 604 MidiMessageWithOrder tmp = _heap[slot]; 605 _heap[slot] = _heap[best]; 606 _heap[best] = tmp; 607 608 // continue to bubble down 609 slot = best; 610 } 611 } 612 } 613 614 static int parentOf(int index) 615 { 616 return index >> 1; 617 } 618 619 static int leftChildOf(int index) 620 { 621 return 2*index; 622 } 623 624 static int rightChildOf(int index) 625 { 626 return 2*index+1; 627 } 628 629 // Larger means "should be popped later" 630 // Priorities can't ever be equal. 631 private static bool compareLargerThan(MidiMessageWithOrder a, MidiMessageWithOrder b) 632 { 633 if (a.message._offset != b.message._offset) 634 { 635 return a.message._offset > b.message._offset; 636 } 637 if (a.order != b.order) 638 { 639 int diff = cast(int)(a.order - b.order); 640 return (diff > 0); 641 } 642 else 643 { 644 // Impossible, unless 2^32 messages have been pushed with the same timestamp 645 // which is perhaps too experimental as far as music goes 646 assert(false); 647 } 648 } 649 } 650 651 unittest 652 { 653 MidiQueue queue = makeMidiQueue(); 654 foreach (k; 0..2) 655 { 656 int capacity = 511; 657 // Enqueue QueueCapacity messages with decreasing timestamps 658 foreach(i; 0..capacity) 659 { 660 queue.enqueue( makeMidiMessageNoteOn(capacity-1-i, 0, 60, 100) ); 661 assert(queue.numElements() == i+1); 662 } 663 664 const(MidiMessage)[] messages = queue.getNextMidiMessages(1024); 665 666 foreach(size_t i, m; messages) 667 { 668 // each should be in order 669 assert(m.offset == cast(int)i); 670 671 assert(m.isNoteOn); 672 } 673 } 674 } 675 676 // Issue #575: MidiQueue should NOT reorder messages that come with same timestamp. 677 unittest 678 { 679 MidiQueue queue = makeMidiQueue(); 680 int note = 102; 681 int vel = 64; 682 int chan = 1; 683 int offset = 0; 684 685 queue.enqueue( makeMidiMessageNoteOn(offset, chan, note, vel) ); 686 queue.enqueue( makeMidiMessageNoteOn(offset, chan, note, vel) ); 687 queue.enqueue( makeMidiMessageNoteOff(offset, chan, note) ); 688 queue.enqueue( makeMidiMessageNoteOff(offset, chan, note) ); 689 690 const(MidiMessage)[] messages = queue.getNextMidiMessages(1024); 691 assert(messages.length == 4); 692 assert(messages[0].isNoteOn()); 693 assert(messages[1].isNoteOn()); 694 assert(messages[2].isNoteOff()); 695 assert(messages[3].isNoteOff()); 696 } 697 698 unittest 699 { 700 ubyte[3] buf; 701 MidiMessage msg = makeMidiMessageNoteOn(0, 1, 102, 64); 702 assert(3 == msg.toBytes(null, -1)); 703 assert(3 == msg.toBytes(buf.ptr, 3)); 704 assert(buf == [0x91, 102, 64 ]); 705 706 msg = makeMidiMessageChannelPressure(0, 4, 1.0f); 707 assert(2 == msg.toBytes(buf.ptr, -1)); 708 assert(2 == msg.toBytes(buf.ptr, 3)); 709 assert(buf == [0xD4, 127, 64 ]); 710 }