From 419606785dc504f52efd13b624d305c7d524868f Mon Sep 17 00:00:00 2001 From: Francois Best Date: Thu, 6 Oct 2016 20:19:10 +0200 Subject: [PATCH] Added RPN/NRPN example. --- res/Examples/MIDI_RPN_NRPN/MIDI_RPN_NRPN.ino | 195 +++++++++++++++++++ res/Examples/MIDI_RPN_NRPN/utility.h | 166 ++++++++++++++++ 2 files changed, 361 insertions(+) create mode 100644 res/Examples/MIDI_RPN_NRPN/MIDI_RPN_NRPN.ino create mode 100644 res/Examples/MIDI_RPN_NRPN/utility.h diff --git a/res/Examples/MIDI_RPN_NRPN/MIDI_RPN_NRPN.ino b/res/Examples/MIDI_RPN_NRPN/MIDI_RPN_NRPN.ino new file mode 100644 index 0000000..587aad2 --- /dev/null +++ b/res/Examples/MIDI_RPN_NRPN/MIDI_RPN_NRPN.ino @@ -0,0 +1,195 @@ +#include +#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 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 RpnParser; +typedef ParameterNumberParser 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(); +} diff --git a/res/Examples/MIDI_RPN_NRPN/utility.h b/res/Examples/MIDI_RPN_NRPN/utility.h new file mode 100644 index 0000000..e1d874c --- /dev/null +++ b/res/Examples/MIDI_RPN_NRPN/utility.h @@ -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 + +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 +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; +};