iPlug2 - C++ Audio Plug-in Framework
IPlugMidi.h
Go to the documentation of this file.
1 /*
2  ==============================================================================
3 
4  This file is part of the iPlug 2 library. Copyright (C) the iPlug 2 developers.
5 
6  See LICENSE.txt for more info.
7 
8  ==============================================================================
9 */
10 
11 #pragma once
12 
19 #include <cassert>
20 #include <cstdint>
21 #include <cstdio>
22 #include <algorithm>
23 
24 #include "IPlugLogger.h"
25 
26 BEGIN_IPLUG_NAMESPACE
27 
30 struct IMidiMsg
31 {
32  int mOffset;
33  uint8_t mStatus, mData1, mData2;
34 
37  {
38  kNone = 0,
39  kNoteOff = 8,
40  kNoteOn = 9,
41  kPolyAftertouch = 10,
42  kControlChange = 11,
43  kProgramChange = 12,
44  kChannelAftertouch = 13,
45  kPitchWheel = 14
46  };
47 
50  {
51  kNoCC = -1,
52  kModWheel = 1,
53  kBreathController = 2,
54  kUndefined003 = 3,
55  kFootController = 4,
56  kPortamentoTime = 5,
57  kChannelVolume = 7,
58  kBalance = 8,
59  kUndefined009 = 9,
60  kPan = 10,
61  kExpressionController = 11,
62  kEffectControl1 = 12,
63  kEffectControl2 = 13,
64  kUndefined014 = 14,
65  kUndefined015 = 15,
66  kGeneralPurposeController1 = 16,
67  kGeneralPurposeController2 = 17,
68  kGeneralPurposeController3 = 18,
69  kGeneralPurposeController4 = 19,
70  kUndefined020 = 20,
71  kUndefined021 = 21,
72  kUndefined022 = 22,
73  kUndefined023 = 23,
74  kUndefined024 = 24,
75  kUndefined025 = 25,
76  kUndefined026 = 26,
77  kUndefined027 = 27,
78  kUndefined028 = 28,
79  kUndefined029 = 29,
80  kUndefined030 = 30,
81  kUndefined031 = 31,
82  kSustainOnOff = 64,
83  kPortamentoOnOff = 65,
84  kSustenutoOnOff = 66,
85  kSoftPedalOnOff = 67,
86  kLegatoOnOff = 68,
87  kHold2OnOff = 69,
88  kSoundVariation = 70,
89  kResonance = 71,
90  kReleaseTime = 72,
91  kAttackTime = 73,
92  kCutoffFrequency = 74,
93  kDecayTime = 75,
94  kVibratoRate = 76,
95  kVibratoDepth = 77,
96  kVibratoDelay = 78,
97  kSoundControllerUndefined = 79,
98  kUndefined085 = 85,
99  kUndefined086 = 86,
100  kUndefined087 = 87,
101  kUndefined088 = 88,
102  kUndefined089 = 89,
103  kUndefined090 = 90,
104  kTremoloDepth = 92,
105  kChorusDepth = 93,
106  kPhaserDepth = 95,
107  kUndefined102 = 102,
108  kUndefined103 = 103,
109  kUndefined104 = 104,
110  kUndefined105 = 105,
111  kUndefined106 = 106,
112  kUndefined107 = 107,
113  kUndefined108 = 108,
114  kUndefined109 = 109,
115  kUndefined110 = 110,
116  kUndefined111 = 111,
117  kUndefined112 = 112,
118  kUndefined113 = 113,
119  kUndefined114 = 114,
120  kUndefined115 = 115,
121  kUndefined116 = 116,
122  kUndefined117 = 117,
123  kUndefined118 = 118,
124  kUndefined119 = 119,
125  kAllNotesOff = 123
126  };
127 
133  IMidiMsg(int offset = 0, uint8_t status = 0, uint8_t data1 = 0, uint8_t data2 = 0)
134  : mOffset(offset)
135  , mStatus(status)
136  , mData1(data1)
137  , mData2(data2)
138  {}
139 
145  void MakeNoteOnMsg(int noteNumber, int velocity, int offset, int channel = 0)
146  {
147  Clear();
148  mStatus = channel | (kNoteOn << 4) ;
149  mData1 = noteNumber;
150  mData2 = velocity;
151  mOffset = offset;
152  }
153 
158  void MakeNoteOffMsg(int noteNumber, int offset, int channel = 0)
159  {
160  Clear();
161  mStatus = channel | (kNoteOff << 4);
162  mData1 = noteNumber;
163  mOffset = offset;
164  }
165 
170  void MakePitchWheelMsg(double value, int channel = 0, int offset = 0)
171  {
172  Clear();
173  mStatus = channel | (kPitchWheel << 4);
174  int i = 8192 + (int) (value * 8192.0);
175  i = std::min(std::max(i, 0), 16383);
176  mData2 = i>>7;
177  mData1 = i&0x7F;
178  mOffset = offset;
179  }
180 
186  void MakeControlChangeMsg(EControlChangeMsg idx, double value, int channel = 0, int offset = 0)
187  {
188  Clear();
189  mStatus = channel | (kControlChange << 4);
190  mData1 = idx;
191  mData2 = (int) (value * 127.0);
192  mOffset = offset;
193  }
194 
199  void MakeProgramChange(int program, int channel = 0, int offset = 0)
200  {
201  Clear();
202  mStatus = channel | (kProgramChange << 4);
203  mData1 = program;
204  mOffset = offset;
205  }
206 
211  void MakeChannelATMsg(int pressure, int offset, int channel)
212  {
213  Clear();
214  mStatus = channel | (kChannelAftertouch << 4);
215  mData1 = pressure;
216  mData2 = 0;
217  mOffset = offset;
218  }
219 
225  void MakePolyATMsg(int noteNumber, int pressure, int offset, int channel)
226  {
227  Clear();
228  mStatus = channel | (kPolyAftertouch << 4);
229  mData1 = noteNumber;
230  mData2 = pressure;
231  mOffset = offset;
232  }
233 
236  int Channel() const
237  {
238  return mStatus & 0x0F;
239  }
240 
244  {
245  unsigned int e = mStatus >> 4;
246  if (e < kNoteOff || e > kPitchWheel)
247  {
248  return kNone;
249  }
250  return (EStatusMsg) e;
251  }
252 
255  int NoteNumber() const
256  {
257  switch (StatusMsg())
258  {
259  case kNoteOn:
260  case kNoteOff:
261  case kPolyAftertouch:
262  return mData1;
263  default:
264  return -1;
265  }
266  }
267 
270  int Velocity() const
271  {
272  switch (StatusMsg())
273  {
274  case kNoteOn:
275  case kNoteOff:
276  return mData2;
277  default:
278  return -1;
279  }
280  }
281 
284  int PolyAfterTouch() const
285  {
286  switch (StatusMsg())
287  {
288  case kPolyAftertouch:
289  return mData2;
290  default:
291  return -1;
292  }
293  }
294 
297  int ChannelAfterTouch() const
298  {
299  switch (StatusMsg())
300  {
301  case kChannelAftertouch:
302  return mData1;
303  default:
304  return -1;
305  }
306  }
307 
310  int Program() const
311  {
312  if (StatusMsg() == kProgramChange)
313  {
314  return mData1;
315  }
316  return -1;
317  }
318 
321  double PitchWheel() const
322  {
323  if (StatusMsg() == kPitchWheel)
324  {
325  int iVal = (mData2 << 7) + mData1;
326  return (double) (iVal - 8192) / 8192.0;
327  }
328  return 0.0;
329  }
330 
334  {
335  return (EControlChangeMsg) mData1;
336  }
337 
341  {
342  if (StatusMsg() == kControlChange && ControlChangeIdx() == idx)
343  {
344  return (double) mData2 / 127.0;
345  }
346  return -1.0;
347  }
348 
352  static bool ControlChangeOnOff(double msgValue)
353  {
354  return (msgValue >= 0.5);
355  }
356 
358  void Clear()
359  {
360  mOffset = 0;
361  mStatus = mData1 = mData2 = 0;
362  }
363 
367  static const char* StatusMsgStr(EStatusMsg msg)
368  {
369  switch (msg)
370  {
371  case kNone: return "none";
372  case kNoteOff: return "noteoff";
373  case kNoteOn: return "noteon";
374  case kPolyAftertouch: return "aftertouch";
375  case kControlChange: return "controlchange";
376  case kProgramChange: return "programchange";
377  case kChannelAftertouch: return "channelaftertouch";
378  case kPitchWheel: return "pitchwheel";
379  default: return "unknown";
380  };
381  }
382 
386  static const char* CCNameStr(int idx)
387  {
388  static const char* ccNameStrs[128] =
389  {
390  "BankSel.MSB",
391  "Modulation",
392  "BreathCtrl",
393  "Contr. 3",
394  "Foot Ctrl",
395  "Porta.Time",
396  "DataEntMSB",
397  "MainVolume",
398  "Balance",
399  "Contr. 9",
400  "Pan",
401  "Expression",
402  "FXControl1",
403  "FXControl2",
404  "Contr. 14",
405  "Contr. 15",
406  "Gen.Purp.1",
407  "Gen.Purp.2",
408  "Gen.Purp.3",
409  "Gen.Purp.4",
410  "Contr. 20",
411  "Contr. 21",
412  "Contr. 22",
413  "Contr. 23",
414  "Contr. 24",
415  "Contr. 25",
416  "Contr. 26",
417  "Contr. 27",
418  "Contr. 28",
419  "Contr. 29",
420  "Contr. 30",
421  "Contr. 31",
422  "BankSel.LSB",
423  "Modul. LSB",
424  "BrthCt LSB",
425  "Contr. 35",
426  "FootCt LSB",
427  "Port.T LSB",
428  "DataEntLSB",
429  "MainVolLSB",
430  "BalanceLSB",
431  "Contr. 41",
432  "Pan LSB",
433  "Expr. LSB",
434  "Contr. 44",
435  "Contr. 45",
436  "Contr. 46",
437  "Contr. 47",
438  "Gen.P.1LSB",
439  "Gen.P.2LSB",
440  "Gen.P.3LSB",
441  "Gen.P.4LSB",
442  "Contr. 52",
443  "Contr. 53",
444  "Contr. 54",
445  "Contr. 55",
446  "Contr. 56",
447  "Contr. 57",
448  "Contr. 58",
449  "Contr. 59",
450  "Contr. 60",
451  "Contr. 61",
452  "Contr. 62",
453  "Contr. 63",
454  "Damper Ped",
455  "Porta. Ped",
456  "Sostenuto ",
457  "Soft Pedal",
458  "Legato Sw",
459  "Hold 2",
460  "SoundCont 1",
461  "SoundCont 2",
462  "SoundCont 3",
463  "SoundCont 4",
464  "SoundCont 5",
465  "SoundCont 6",
466  "SoundCont 7",
467  "SoundCont 8",
468  "SoundCont 9",
469  "SoundCont 10",
470  "Gen.Purp.5",
471  "Gen.Purp.6",
472  "Gen.Purp.7",
473  "Gen.Purp.8",
474  "Portamento",
475  "Contr. 85",
476  "Contr. 86",
477  "Contr. 87",
478  "Contr. 88",
479  "Contr. 89",
480  "Contr. 90",
481  "FX 1 Depth",
482  "FX 2 Depth",
483  "FX 3 Depth",
484  "FX 4 Depth",
485  "FX 5 Depth",
486  "Data Incr",
487  "Data Decr",
488  "Non-RegLSB",
489  "Non-RegMSB",
490  "Reg LSB",
491  "Reg MSB",
492  "Contr. 102",
493  "Contr. 103",
494  "Contr. 104",
495  "Contr. 105",
496  "Contr. 106",
497  "Contr. 107",
498  "Contr. 108",
499  "Contr. 109",
500  "Contr. 110",
501  "Contr. 111",
502  "Contr. 112",
503  "Contr. 113",
504  "Contr. 114",
505  "Contr. 115",
506  "Contr. 116",
507  "Contr. 117",
508  "Contr. 118",
509  "Contr. 119",
510  "Contr. 120",
511  "Reset Ctrl",
512  "Local Ctrl",
513  "AllNoteOff",
514  "OmniModOff",
515  "OmniModeOn",
516  "MonoModeOn",
517  "PolyModeOn"
518  };
519 
520  return ccNameStrs[idx];
521  }
522 
524  void LogMsg()
525  {
526  Trace(TRACELOC, "midi:(%s:%d:%d:%d)", StatusMsgStr(StatusMsg()), Channel(), mData1, mData2);
527  }
528 
530  void PrintMsg() const
531  {
532  DBGMSG("midi: offset %i, (%s:%d:%d:%d)\n", mOffset, StatusMsgStr(StatusMsg()), Channel(), mData1, mData2);
533  }
534 };
535 
538 struct ISysEx
539 {
540  int mOffset, mSize;
541  const uint8_t* mData;
542 
547  ISysEx(int offset = 0, const uint8_t* pData = nullptr, int size = 0)
548  : mOffset(offset)
549  , mSize(size)
550  , mData(pData)
551  {}
552 
554  void Clear()
555  {
556  mOffset = mSize = 0;
557  mData = NULL;
558  }
559 
566  char* SysExStr(char* str, int maxlen, const uint8_t* pData, int size)
567  {
568  assert(str != NULL && maxlen >= 3);
569 
570  if (!pData || !size) {
571  *str = '\0';
572  return str;
573  }
574 
575  char* pStr = str;
576  int n = maxlen / 3;
577  if (n > size) n = size;
578  for (int i = 0; i < n; ++i, ++pData) {
579  sprintf(pStr, "%02X", (int)*pData);
580  pStr += 2;
581  *pStr++ = ' ';
582  }
583  *--pStr = '\0';
584 
585  return str;
586  }
587 
589  void LogMsg()
590  {
591  char str[96];
592  Trace(TRACELOC, "sysex:(%d:%s)", mSize, SysExStr(str, sizeof(str), mData, mSize));
593  }
594 
595 };
596 
597 /*
598 
599 IMidiQueue
600 (c) Theo Niessink 2009-2011
601 <http://www.taletn.com/>
602 
603 
604 This software is provided 'as-is', without any express or implied
605 warranty. In no event will the authors be held liable for any damages
606 arising from the use of this software.
607 
608 Permission is granted to anyone to use this software for any purpose,
609 including commercial applications, and to alter it and redistribute it
610 freely, subject to the following restrictions:
611 
612 1. The origin of this software must not be misrepresented; you must not
613  claim that you wrote the original software. If you use this software in a
614  product, an acknowledgment in the product documentation would be
615  appreciated but is not required.
616 2. Altered source versions must be plainly marked as such, and must not be
617  misrepresented as being the original software.
618 3. This notice may not be removed or altered from any source distribution.
619 
620 
621 IMidiQueue is a fast, lean & mean MIDI queue for IPlug instruments or
622 effects. Here are a few code snippets showing how to implement IMidiQueue in
623 an IPlug project:
624 
625 
626 MyPlug.h:
627 
628 #include "WDL/IPlug/IMidiQueue.h"
629 
630 class MyPlug: public IPlug
631 {
632 protected:
633  IMidiQueue mMidiQueue;
634 }
635 
636 
637 MyPlug.cpp:
638 
639 void MyPlug::OnReset()
640 {
641  mMidiQueue.Resize(GetBlockSize());
642 }
643 
644 void MyPlug::ProcessMidiMsg(IMidiMsg* pMsg)
645 {
646  mMidiQueue.Add(pMsg);
647 }
648 
649 void MyPlug::ProcessBlock(double** inputs, double** outputs, int nFrames)
650 {
651  for (int offset = 0; offset < nFrames; ++offset)
652  {
653  while (!mMidiQueue.Empty())
654  {
655  IMidiMsg* pMsg = mMidiQueue.Peek();
656  if (msg.mOffset > offset) break;
657 
658  // To-do: Handle the MIDI message
659 
660  mMidiQueue.Remove();
661  }
662 
663  // To-do: Process audio
664 
665  }
666  mMidiQueue.Flush(nFrames);
667 }
668 
669 */
670 
671 #ifndef DEFAULT_BLOCK_SIZE
672  #define DEFAULT_BLOCK_SIZE 512
673 #endif
674 
678 {
679 public:
680  IMidiQueue(int size = DEFAULT_BLOCK_SIZE)
681  : mBuf(NULL), mSize(0), mGrow(Granulize(size)), mFront(0), mBack(0)
682  {
683  Expand();
684  }
685 
686  ~IMidiQueue()
687  {
688  free(mBuf);
689  }
690 
691  // Adds a MIDI message at the back of the queue. If the queue is full,
692  // it will automatically expand itself.
693  void Add(const IMidiMsg& msg)
694  {
695  if (mBack >= mSize)
696  {
697  if (mFront > 0)
698  Compact();
699  else if (!Expand()) return;
700  }
701 
702 #ifndef DONT_SORT_IMIDIQUEUE
703  // Insert the MIDI message at the right offset.
704  if (mBack > mFront && msg.mOffset < mBuf[mBack - 1].mOffset)
705  {
706  int i = mBack - 2;
707  while (i >= mFront && msg.mOffset < mBuf[i].mOffset) --i;
708  i++;
709  memmove(&mBuf[i + 1], &mBuf[i], (mBack - i) * sizeof(IMidiMsg));
710  mBuf[i] = msg;
711  }
712  else
713 #endif
714  mBuf[mBack] = msg;
715  ++mBack;
716  }
717 
718  // Removes a MIDI message from the front of the queue (but does *not*
719  // free up its space until Compact() is called).
720  inline void Remove() { ++mFront; }
721 
722  // Returns true if the queue is empty.
723  inline bool Empty() const { return mFront == mBack; }
724 
725  // Returns the number of MIDI messages in the queue.
726  inline int ToDo() const { return mBack - mFront; }
727 
728  // Returns the number of MIDI messages for which memory has already been
729  // allocated.
730  inline int GetSize() const { return mSize; }
731 
732  // Returns the "next" MIDI message (all the way in the front of the
733  // queue), but does *not* remove it from the queue.
734  inline IMidiMsg& Peek() const { return mBuf[mFront]; }
735 
736  // Moves back MIDI messages all the way to the front of the queue, thus
737  // freeing up space at the back, and updates the sample offset of the
738  // remaining MIDI messages by substracting nFrames.
739  inline void Flush(int nFrames)
740  {
741  // Move everything all the way to the front.
742  if (mFront > 0) Compact();
743 
744  // Update the sample offset.
745  for (int i = 0; i < mBack; ++i) mBuf[i].mOffset -= nFrames;
746  }
747 
748  // Clears the queue.
749  inline void Clear() { mFront = mBack = 0; }
750 
751  // Resizes (grows or shrinks) the queue, returns the new size.
752  int Resize(int size)
753  {
754  if (mFront > 0) Compact();
755  mGrow = size = Granulize(size);
756  // Don't shrink below the number of currently queued MIDI messages.
757  if (size < mBack) size = Granulize(mBack);
758  if (size == mSize) return mSize;
759 
760  void* buf = realloc(mBuf, size * sizeof(IMidiMsg));
761  if (!buf) return mSize;
762 
763  mBuf = (IMidiMsg*)buf;
764  mSize = size;
765  return size;
766  }
767 
768 protected:
769  // Automatically expands the queue.
770  bool Expand()
771  {
772  if (!mGrow) return false;
773  int size = (mSize / mGrow + 1) * mGrow;
774 
775  void* buf = realloc(mBuf, size * sizeof(IMidiMsg));
776  if (!buf) return false;
777 
778  mBuf = (IMidiMsg*)buf;
779  mSize = size;
780  return true;
781  }
782 
783  // Moves everything all the way to the front.
784  inline void Compact()
785  {
786  mBack -= mFront;
787  if (mBack > 0) memmove(&mBuf[0], &mBuf[mFront], mBack * sizeof(IMidiMsg));
788  mFront = 0;
789  }
790 
791  // Rounds the MIDI queue size up to the next 4 kB memory page size.
792  inline int Granulize(int size) const
793  {
794  int bytes = size * sizeof(IMidiMsg);
795  int rest = bytes % 4096;
796  if (rest) size = (bytes - rest + 4096) / sizeof(IMidiMsg);
797  return size;
798  }
799 
800  IMidiMsg* mBuf;
801 
802  int mSize, mGrow;
803  int mFront, mBack;
804 };
805 
806 END_IPLUG_NAMESPACE
static const char * StatusMsgStr(EStatusMsg msg)
Get the Status Message as a CString.
Definition: IPlugMidi.h:367
A class to help with queuing timestamped MIDI messages.
Definition: IPlugMidi.h:677
int Program() const
Get the program index from a Program Change message.
Definition: IPlugMidi.h:310
Encapsulates a MIDI message and provides helper functions.
Definition: IPlugMidi.h:30
void MakePitchWheelMsg(double value, int channel=0, int offset=0)
Create a pitch wheel/bend message.
Definition: IPlugMidi.h:170
EControlChangeMsg
Constants for MIDI CC messages.
Definition: IPlugMidi.h:49
int NoteNumber() const
Gets the MIDI note number.
Definition: IPlugMidi.h:255
IMidiMsg(int offset=0, uint8_t status=0, uint8_t data1=0, uint8_t data2=0)
Create an IMidiMsg, an abstraction for a MIDI message.
Definition: IPlugMidi.h:133
IPlug logging a.k.a tracing functionality.
ISysEx(int offset=0, const uint8_t *pData=nullptr, int size=0)
Create an ISysex.
Definition: IPlugMidi.h:547
int ChannelAfterTouch() const
Get the Pressure value from an AfterTouch message.
Definition: IPlugMidi.h:297
int Velocity() const
Get the velocity value of a NoteOn/NoteOff message.
Definition: IPlugMidi.h:270
int Channel() const
Gets the channel of a MIDI message.
Definition: IPlugMidi.h:236
void Clear()
Clear the data pointer and size (does not modify the external data!)
Definition: IPlugMidi.h:554
double PitchWheel() const
Get the value from a Pitchwheel message.
Definition: IPlugMidi.h:321
void MakePolyATMsg(int noteNumber, int pressure, int offset, int channel)
Create a Poly AfterTouch message.
Definition: IPlugMidi.h:225
void MakeNoteOffMsg(int noteNumber, int offset, int channel=0)
Make a Note Off message.
Definition: IPlugMidi.h:158
static const char * CCNameStr(int idx)
Get the CC name as a CString.
Definition: IPlugMidi.h:386
void PrintMsg() const
Print a message (DEBUG BUILDS)
Definition: IPlugMidi.h:530
static bool ControlChangeOnOff(double msgValue)
Helper to get a boolean value from a CC messages.
Definition: IPlugMidi.h:352
EControlChangeMsg ControlChangeIdx() const
Gets the controller index of a CC message.
Definition: IPlugMidi.h:333
void LogMsg()
Log a message (TRACER BUILDS)
Definition: IPlugMidi.h:589
EStatusMsg
Constants for the status byte of a MIDI message.
Definition: IPlugMidi.h:36
EStatusMsg StatusMsg() const
Gets the MIDI Status message.
Definition: IPlugMidi.h:243
char * SysExStr(char *str, int maxlen, const uint8_t *pData, int size)
Get the bytes of a sysex message as a CString.
Definition: IPlugMidi.h:566
void MakeChannelATMsg(int pressure, int offset, int channel)
Create a Channel AfterTouch message.
Definition: IPlugMidi.h:211
A struct for dealing with SysEx messages.
Definition: IPlugMidi.h:538
void MakeControlChangeMsg(EControlChangeMsg idx, double value, int channel=0, int offset=0)
Create a CC message.
Definition: IPlugMidi.h:186
void MakeNoteOnMsg(int noteNumber, int velocity, int offset, int channel=0)
Make a Note On message.
Definition: IPlugMidi.h:145
void Clear()
Clear the message.
Definition: IPlugMidi.h:358
void MakeProgramChange(int program, int channel=0, int offset=0)
Create a Program Change message.
Definition: IPlugMidi.h:199
int PolyAfterTouch() const
Get the Pressure value from a PolyAfterTouch message.
Definition: IPlugMidi.h:284
void LogMsg()
Log a message (TRACER BUILDS)
Definition: IPlugMidi.h:524
double ControlChange(EControlChangeMsg idx) const
Get the value of a CC message.
Definition: IPlugMidi.h:340