Added RPN/NRPN example.

This commit is contained in:
Francois Best 2016-10-06 20:19:10 +02:00
parent cb6c428913
commit 419606785d
2 changed files with 361 additions and 0 deletions

View File

@ -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();
}

View File

@ -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;
};