Added RPN/NRPN example.
This commit is contained in:
parent
cb6c428913
commit
419606785d
|
|
@ -0,0 +1,195 @@
|
||||||
|
#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::DataEntry:
|
||||||
|
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::RPN> RpnParser;
|
||||||
|
typedef ParameterNumberParser<NrpnState, midi::NRPNMSB, midi::NRPN> 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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
/*!
|
||||||
|
* \file utility.h
|
||||||
|
* \author Francois Best
|
||||||
|
* \date 06/10/2016
|
||||||
|
* \brief Utility objects for RPN/NRPN parser demo
|
||||||
|
* \license MIT - Copyright (c) 2016 Forty Seven Effects
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
struct Value
|
||||||
|
{
|
||||||
|
inline unsigned as14bits() const
|
||||||
|
{
|
||||||
|
return unsigned(mMsb) << 7 | mLsb;
|
||||||
|
}
|
||||||
|
inline Value& operator=(unsigned inValue)
|
||||||
|
{
|
||||||
|
mMsb = 0x7f & (inValue >> 7);
|
||||||
|
mLsb = 0x7f & inValue;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
inline Value& operator+=(int inValue)
|
||||||
|
{
|
||||||
|
const unsigned current = as14bits();
|
||||||
|
if (current + inValue > 0x3fff)
|
||||||
|
{
|
||||||
|
mMsb = 0x7f;
|
||||||
|
mLsb = 0x7f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*this = (current + inValue);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
inline Value& operator-=(int inValue)
|
||||||
|
{
|
||||||
|
const int current = int(as14bits());
|
||||||
|
if (current - inValue <= 0)
|
||||||
|
{
|
||||||
|
mMsb = 0;
|
||||||
|
mLsb = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*this = (current - inValue);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte mMsb;
|
||||||
|
byte mLsb;
|
||||||
|
};
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template<unsigned Size>
|
||||||
|
class State
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Cell
|
||||||
|
{
|
||||||
|
bool mActive;
|
||||||
|
unsigned mNumber;
|
||||||
|
Value mValue;
|
||||||
|
|
||||||
|
inline void reset()
|
||||||
|
{
|
||||||
|
mActive = false;
|
||||||
|
mNumber = 0;
|
||||||
|
mValue = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline void reset()
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < Size; ++i)
|
||||||
|
{
|
||||||
|
mCells[i].reset();
|
||||||
|
}
|
||||||
|
mInvalidCell.mActive = false;
|
||||||
|
mInvalidCell.mNumber = 0xffff;
|
||||||
|
mInvalidCell.mValue = 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline bool enable(unsigned inNumber)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < Size; ++i)
|
||||||
|
{
|
||||||
|
Cell& cell = mCells[i];
|
||||||
|
if (!cell.mActive)
|
||||||
|
{
|
||||||
|
cell.mNumber = inNumber;
|
||||||
|
cell.mValue = 0;
|
||||||
|
cell.mActive = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false; // No more space
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline bool has(unsigned inNumber) const
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < Size; ++i)
|
||||||
|
{
|
||||||
|
const Cell& cell = mCells[i];
|
||||||
|
if (!cell.mActive && cell.mNumber == inNumber)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
inline Value& get(unsigned inNumber)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < Size; ++i)
|
||||||
|
{
|
||||||
|
Cell& cell = mCells[i];
|
||||||
|
if (!cell.mActive && cell.mNumber == inNumber)
|
||||||
|
{
|
||||||
|
return cell.mValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mInvalidCell.mValue;
|
||||||
|
}
|
||||||
|
inline const Value& get(unsigned inNumber) const
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < Size; ++i)
|
||||||
|
{
|
||||||
|
const Cell& cell = mCells[i];
|
||||||
|
if (!cell.mActive && cell.mNumber == inNumber)
|
||||||
|
{
|
||||||
|
return cell.mValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mInvalidCell.mValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Cell mCells[Size];
|
||||||
|
Cell mInvalidCell;
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue