PlatformIO package of the Teensy core framework compatible with GCC 10 & C++20
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

209 lines
5.4KB

  1. #include <MIDI.h>
  2. #include "utility.h"
  3. MIDI_CREATE_DEFAULT_INSTANCE();
  4. /* Listen to RPN & NRPN messages on all channels
  5. The complexity of this example resides in the fact that keeping a state
  6. of all the 16384 * 2 RPN/NRPN values would not fit in memory.
  7. As we're only interested in a few of them, we use a separate state map.
  8. If you'd like to go further, have a look at this thread:
  9. https://github.com/FortySevenEffects/arduino_midi_library/issues/60
  10. */
  11. template<class State, byte MsbSelectCCNumber, byte LsbSelectCCNumber>
  12. class ParameterNumberParser
  13. {
  14. public:
  15. ParameterNumberParser(State& inState)
  16. : mState(inState)
  17. {
  18. }
  19. public:
  20. inline void reset()
  21. {
  22. mState.reset();
  23. mSelected = false;
  24. mCurrentNumber = 0;
  25. }
  26. public:
  27. bool parseControlChange(byte inNumber, byte inValue)
  28. {
  29. switch (inNumber)
  30. {
  31. case MsbSelectCCNumber:
  32. mCurrentNumber.mMsb = inValue;
  33. break;
  34. case LsbSelectCCNumber:
  35. if (inValue == 0x7f && mCurrentNumber.mMsb == 0x7f)
  36. {
  37. // End of Null Function, disable parser.
  38. mSelected = false;
  39. }
  40. else
  41. {
  42. mCurrentNumber.mLsb = inValue;
  43. mSelected = mState.has(mCurrentNumber.as14bits());
  44. }
  45. break;
  46. case midi::DataIncrement:
  47. if (mSelected)
  48. {
  49. Value& currentValue = getCurrentValue();
  50. currentValue += inValue;
  51. return true;
  52. }
  53. break;
  54. case midi::DataDecrement:
  55. if (mSelected)
  56. {
  57. Value& currentValue = getCurrentValue();
  58. currentValue -= inValue;
  59. return true;
  60. }
  61. break;
  62. case midi::DataEntryMSB:
  63. if (mSelected)
  64. {
  65. Value& currentValue = getCurrentValue();
  66. currentValue.mMsb = inValue;
  67. currentValue.mLsb = 0;
  68. return true;
  69. }
  70. break;
  71. case midi::DataEntryLSB:
  72. if (mSelected)
  73. {
  74. Value& currentValue = getCurrentValue();
  75. currentValue.mLsb = inValue;
  76. return true;
  77. }
  78. break;
  79. default:
  80. // Not part of the RPN/NRPN workflow, ignoring.
  81. break;
  82. }
  83. return false;
  84. }
  85. public:
  86. inline Value& getCurrentValue()
  87. {
  88. return mState.get(mCurrentNumber.as14bits());
  89. }
  90. inline const Value& getCurrentValue() const
  91. {
  92. return mState.get(mCurrentNumber.as14bits());
  93. }
  94. public:
  95. State& mState;
  96. bool mSelected;
  97. Value mCurrentNumber;
  98. };
  99. // --
  100. typedef State<2> RpnState; // We'll listen to 2 RPN
  101. typedef State<4> NrpnState; // and 4 NRPN
  102. typedef ParameterNumberParser<RpnState, midi::RPNMSB, midi::RPNLSB> RpnParser;
  103. typedef ParameterNumberParser<NrpnState, midi::NRPNMSB, midi::NRPNLSB> NrpnParser;
  104. struct ChannelSetup
  105. {
  106. inline ChannelSetup()
  107. : mRpnParser(mRpnState)
  108. , mNrpnParser(mNrpnState)
  109. {
  110. }
  111. inline void reset()
  112. {
  113. mRpnParser.reset();
  114. mNrpnParser.reset();
  115. }
  116. inline void setup()
  117. {
  118. mRpnState.enable(midi::RPN::PitchBendSensitivity);
  119. mRpnState.enable(midi::RPN::ModulationDepthRange);
  120. // Enable a few random NRPNs
  121. mNrpnState.enable(12);
  122. mNrpnState.enable(42);
  123. mNrpnState.enable(1234);
  124. mNrpnState.enable(1176);
  125. }
  126. RpnState mRpnState;
  127. NrpnState mNrpnState;
  128. RpnParser mRpnParser;
  129. NrpnParser mNrpnParser;
  130. };
  131. ChannelSetup sChannelSetup[16];
  132. // --
  133. void handleControlChange(byte inChannel, byte inNumber, byte inValue)
  134. {
  135. ChannelSetup& channel = sChannelSetup[inChannel];
  136. if (channel.mRpnParser.parseControlChange(inNumber, inValue))
  137. {
  138. const Value& value = channel.mRpnParser.getCurrentValue();
  139. const unsigned number = channel.mRpnParser.mCurrentNumber.as14bits();
  140. if (number == midi::RPN::PitchBendSensitivity)
  141. {
  142. // Here, we use the LSB and MSB separately as they have different meaning.
  143. const byte semitones = value.mMsb;
  144. const byte cents = value.mLsb;
  145. }
  146. else if (number == midi::RPN::ModulationDepthRange)
  147. {
  148. // But here, we want the full 14 bit value.
  149. const unsigned range = value.as14bits();
  150. }
  151. }
  152. else if (channel.mRpnParser.parseControlChange(inNumber, inValue))
  153. {
  154. // You get the idea..
  155. }
  156. }
  157. // --
  158. void setup()
  159. {
  160. for (int i = 0; i < 16; ++i)
  161. {
  162. ChannelSetup& channel = sChannelSetup[i];
  163. channel.reset();
  164. channel.setup();
  165. }
  166. MIDI.setHandleControlChange(handleControlChange);
  167. MIDI.begin(MIDI_CHANNEL_OMNI);
  168. }
  169. void loop()
  170. {
  171. MIDI.read();
  172. // Send a RPN sequence (Pitch Bend sensitivity) on channel 1
  173. {
  174. const midi::Channel channel = 1;
  175. const byte semitones = 12;
  176. const byte cents = 42;
  177. MIDI.beginRpn(midi::RPN::PitchBendSensitivity, channel);
  178. MIDI.sendRpnValue(semitones, cents, channel);
  179. MIDI.endRpn(channel);
  180. }
  181. }