206 lines
5.3 KiB
C++
206 lines
5.3 KiB
C++
#include <MIDI.h>
|
|
#include "utility.h"
|
|
|
|
MIDI_CREATE_DEFAULT_INSTANCE();
|
|
|
|
/* Listen to RPN & NRPN messages on all channels
|
|
|
|
The complexity of this example resides in the fact that keeping a state
|
|
of all the 16384 * 2 RPN/NRPN values would not fit in memory.
|
|
As we're only interested in a few of them, we use a separate state map.
|
|
*/
|
|
|
|
template<class State, byte MsbSelectCCNumber, byte LsbSelectCCNumber>
|
|
class ParameterNumberParser
|
|
{
|
|
public:
|
|
ParameterNumberParser(State& inState)
|
|
: mState(inState)
|
|
{
|
|
}
|
|
|
|
public:
|
|
inline void reset()
|
|
{
|
|
mState.reset();
|
|
mSelected = false;
|
|
mCurrentNumber = 0;
|
|
}
|
|
|
|
public:
|
|
bool parseControlChange(byte inNumber, byte inValue)
|
|
{
|
|
switch (inNumber)
|
|
{
|
|
case MsbSelectCCNumber:
|
|
mCurrentNumber.mMsb = inValue;
|
|
break;
|
|
case LsbSelectCCNumber:
|
|
if (inValue == 0x7f && mCurrentNumber.mMsb == 0x7f)
|
|
{
|
|
// End of Null Function, disable parser.
|
|
mSelected = false;
|
|
}
|
|
else
|
|
{
|
|
mCurrentNumber.mLsb = inValue;
|
|
mSelected = mState.has(mCurrentNumber.as14bits());
|
|
}
|
|
break;
|
|
|
|
case midi::DataIncrement:
|
|
if (mSelected)
|
|
{
|
|
Value& currentValue = getCurrentValue();
|
|
currentValue += inValue;
|
|
return true;
|
|
}
|
|
break;
|
|
case midi::DataDecrement:
|
|
if (mSelected)
|
|
{
|
|
Value& currentValue = getCurrentValue();
|
|
currentValue -= inValue;
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case midi::DataEntryMSB:
|
|
if (mSelected)
|
|
{
|
|
Value& currentValue = getCurrentValue();
|
|
currentValue.mMsb = inValue;
|
|
currentValue.mLsb = 0;
|
|
return true;
|
|
}
|
|
break;
|
|
case midi::DataEntryLSB:
|
|
if (mSelected)
|
|
{
|
|
Value& currentValue = getCurrentValue();
|
|
currentValue.mLsb = inValue;
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Not part of the RPN/NRPN workflow, ignoring.
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public:
|
|
inline Value& getCurrentValue()
|
|
{
|
|
return mState.get(mCurrentNumber.as14bits());
|
|
}
|
|
inline const Value& getCurrentValue() const
|
|
{
|
|
return mState.get(mCurrentNumber.as14bits());
|
|
}
|
|
|
|
public:
|
|
State& mState;
|
|
bool mSelected;
|
|
Value mCurrentNumber;
|
|
};
|
|
|
|
// --
|
|
|
|
typedef State<2> RpnState; // We'll listen to 2 RPN
|
|
typedef State<4> NrpnState; // and 4 NRPN
|
|
typedef ParameterNumberParser<RpnState, midi::RPNMSB, midi::RPNLSB> RpnParser;
|
|
typedef ParameterNumberParser<NrpnState, midi::NRPNMSB, midi::NRPNLSB> NrpnParser;
|
|
|
|
struct ChannelSetup
|
|
{
|
|
inline ChannelSetup()
|
|
: mRpnParser(mRpnState)
|
|
, mNrpnParser(mNrpnState)
|
|
{
|
|
}
|
|
|
|
inline void reset()
|
|
{
|
|
mRpnParser.reset();
|
|
mNrpnParser.reset();
|
|
}
|
|
inline void setup()
|
|
{
|
|
mRpnState.enable(midi::RPN::PitchBendSensitivity);
|
|
mRpnState.enable(midi::RPN::ModulationDepthRange);
|
|
|
|
// Enable a few random NRPNs
|
|
mNrpnState.enable(12);
|
|
mNrpnState.enable(42);
|
|
mNrpnState.enable(1234);
|
|
mNrpnState.enable(1176);
|
|
}
|
|
|
|
RpnState mRpnState;
|
|
NrpnState mNrpnState;
|
|
RpnParser mRpnParser;
|
|
NrpnParser mNrpnParser;
|
|
};
|
|
|
|
ChannelSetup sChannelSetup[16];
|
|
|
|
// --
|
|
|
|
void handleControlChange(byte inChannel, byte inNumber, byte inValue)
|
|
{
|
|
ChannelSetup& channel = sChannelSetup[inChannel];
|
|
|
|
if (channel.mRpnParser.parseControlChange(inNumber, inValue))
|
|
{
|
|
const Value& value = channel.mRpnParser.getCurrentValue();
|
|
const unsigned number = channel.mRpnParser.mCurrentNumber.as14bits();
|
|
|
|
if (number == midi::RPN::PitchBendSensitivity)
|
|
{
|
|
// Here, we use the LSB and MSB separately as they have different meaning.
|
|
const byte semitones = value.mMsb;
|
|
const byte cents = value.mLsb;
|
|
}
|
|
else if (number == midi::RPN::ModulationDepthRange)
|
|
{
|
|
// But here, we want the full 14 bit value.
|
|
const unsigned range = value.as14bits();
|
|
}
|
|
}
|
|
else if (channel.mRpnParser.parseControlChange(inNumber, inValue))
|
|
{
|
|
// You get the idea..
|
|
}
|
|
}
|
|
|
|
// --
|
|
|
|
void setup()
|
|
{
|
|
for (int i = 0; i < 16; ++i)
|
|
{
|
|
ChannelSetup& channel = sChannelSetup[i];
|
|
channel.reset();
|
|
channel.setup();
|
|
}
|
|
MIDI.setHandleControlChange(handleControlChange);
|
|
MIDI.begin(MIDI_CHANNEL_OMNI);
|
|
}
|
|
|
|
void loop()
|
|
{
|
|
MIDI.read();
|
|
|
|
// Send a RPN sequence (Pitch Bend sensitivity) on channel 1
|
|
{
|
|
const midi::Channel channel = 1;
|
|
const byte semitones = 12;
|
|
const byte cents = 42;
|
|
MIDI.beginRpn(midi::RPN::PitchBendSensitivity, channel);
|
|
MIDI.sendRpnValue(semitones, cents, channel);
|
|
MIDI.endRpn(channel);
|
|
}
|
|
}
|