FortySevenEffects / arduino_midi_library

MIDI for Arduino
MIT License
1.59k stars 257 forks source link

CC/RPN/NRPN Parser #60

Open eclab opened 8 years ago

eclab commented 8 years ago

So far as I can tell the existing code does break out CC, RPN, and NRPN messages, which is kind of a big deal. It just has a general-purpose CC handler.

I'm using the following code to parse these messages. It's not well tested and I've just cleaned it up here so I could post it (thus possibly introducing bugs), and as I'm not a C++ coder I thought a pull request wasn't appropriate, but maybe it might be useful to you to incorporate. Enjoy.

///// CC/RPN/NRPN PARSER
/////
///// Copyright 2016 Sean Luke
///// Licensed under Apache 2.0

///// INTRODUCTION TO THE CC/RPN/NRPN PARSER
///// The parser is located in parseControlChange(...), which
///// can be set up to be the handler for CC messages by the MIDI library.
/////
///// CC messages take one of a great many forms, which we handle in the parser
/////
///// 7-bit CC messages:
///// 1. number >=64 and < 96 or >= 102 and < 120, with value
/////       -> handleControlChange(channel, number, value, VALUE_7_BIT_ONLY)
/////
///// Potentially 7-bit CC messages:
///// 1. number >= 0 and < 32, other than 6, with value
/////       -> handleControlChange(channel, number, value, VALUE_MSB_ONLY)
/////
///// 14-bit CC messages:
///// 1. number >= 0 and < 32, other than 6, with MSB
///// 2. same number + 32, with LSB
/////       -> handleControlChange(channel, number, value, VALUE)
/////    NOTE: this means that a 14-bit CC message will have TWO handleControlChange calls.
/////          There's not much we can do about this, as we simply don't know if the LSB will arrive.  
/////
///// Continuing 14-bit CC messages:
///// 1. same number again + 32, with LSB
/////       -> handleControlChange(channel, number, revised value, VALUE)
/////       It's not clear if this is valid but I think it is
/////
///// Potentially 7-bit NRPN messages:
///// 1. number == 99, with MSB of NRPN parameter
///// 2. number == 98, with LSB of NRPN parameter
///// 3. number == 6, with value
/////       -> handleNRPN(channel, parameter, value, VALUE_MSB_ONLY)
/////
///// 14-bit NRPN messages:
///// 1. number == 99, with MSB of NRPN parameter
///// 2. number == 98, with LSB of NRPN parameter
///// 3. number == 6, with MSB of value
///// 4. number == 38, with LSB of value
/////       -> handleNRPN(channel, parameter, value, VALUE)
/////    NOTE: this means that a 14-bit NRPN message will have TWO handleNRPN calls.
/////          There's not much we can do about this, as we simply don't know if the LSB will arrive.  
/////
///// Continuing 7-bit NRPN messages:
///// 4. number == 38 again, with value
/////       -> handleNRPN(channel, number, revised value, VALUE_MSB_ONLY)
/////
///// Continuing 14-bit NRPN messages A:
///// 3. number == 6 again, with MSB of value
/////       -> handleNRPN(channel, number, revised value, VALUE)
/////
///// Continuing 14-bit NRPN messages C:
///// 3. number == 6 again, with MSB of value
///// 4. number == 38 again, with LSB of value
/////       -> handleNRPN(channel, number, revised value, VALUE)
/////    NOTE: this means that a continuing 14-bit NRPN message will have TWO handleNRPN calls.
/////          There's not much we can do about this, as we simply don't know if the LSB will arrive.  
/////
///// Incrementing NRPN messages:
///// 1. number == 99, with MSB of NRPN parameter
///// 2. number == 98, with LSB of NRPN parameter
///// 3. number == 96, with value
/////       If value == 0
/////           -> handleNRPN(channel, parameter, 1, INCREMENT)
/////       Else
/////           -> handleNRPN(channel, parameter, value, INCREMENT)
/////
///// Decrementing NRPN messages:
///// 1. number == 99, with MSB of NRPN parameter
///// 2. number == 98, with LSB of NRPN parameter
///// 3. number == 97, with value
/////       If value == 0
/////           -> handleNRPN(channel, parameter, 1, DECREMENT)
/////       Else
/////           -> handleNRPN(channel, parameter, value, DECREMENT)
/////
///// Potentially 7-bit RPN messages:
///// 1. number == 101, with MSB of RPN parameter other than 127
///// 2. number == 100, with LSB of RPN parameter other than 127
///// 3. number == 6, with value
/////       -> handleNRPN(channel, parameter, value, VALUE_MSB_ONLY)
/////
///// 14-bit RPN messages:
///// 1. number == 101, with MSB of RPN parameter other than 127
///// 2. number == 100, with LSB of RPN parameter other than 127
///// 3. number == 6, with MSB of value
///// 4. number == 38, with LSB of value
/////       -> handleNRPN(channel, parameter, value, VALUE)
/////    NOTE: this means that a 14-bit NRPN message will have TWO handleNRPN calls.
/////          There's not much we can do about this, as we simply don't know if the LSB will arrive.  
/////
///// Continuing 7-bit RPN messages A:
///// 1. number == 6 again, with value
/////       -> handleRPN(channel, number, revised value, VALUE_MSB_ONLY)
/////
///// Continuing 14-bit RPN messages A:
///// 1. number == 38 again, with new LSB of value
/////       -> handleRPN(channel, number, revised value, VALUE)
/////
///// Continuing 14-bit RPN messages B:
///// 1. number == 6 again, with MSB of value
///// 2. number == 38 again, with LSB of value
/////       -> handleRPN(channel, number, revised value, VALUE)
/////    NOTE: this means that a continuing 14-bit RPN message will have TWO handleRPN calls.
/////          There's not much we can do about this, as we simply don't know if the LSB will arrive.  
/////
///// Incrementing RPN messages:
///// 1. number == 101, with MSB of RPN parameter other than 127
///// 2. number == 100, with LSB of RPN parameter other than 127
///// 3. number == 96, with value
/////       If value == 0
/////           -> handleNRPN(channel, parameter, 1, INCREMENT)
/////       Else
/////           -> handleNRPN(channel, parameter, value, INCREMENT)
/////
///// Decrementing NRPN messages:
///// 1. number == 101, with MSB of RPN parameter other than 127
///// 2. number == 100, with LSB of RPN parameter other than 127
///// 3. number == 97, with value
/////       If value == 0
/////           -> handleNRPN(channel, parameter, 1, DECREMENT)
/////       Else
/////           -> handleNRPN(channel, parameter, value, DECREMENT)
/////
///// NULL messages:
///// 1. number == 101, value = 127
///// 2. number == 100, value = 127
/////       [nothing happens, but parser resets]
/////
/////
///// The big problem we have is that the MIDI spec got the MSB and LSB backwards for their data
///// entry values, so we don't now if the LSB is coming and have to either ignore it when it comes
///// in or send two messages, one MSB-only and one MSB+LSB.  This happens for CC, RPN, and NRPN.
/////
/////
///// Our parser maintains four bytes in a struct called _controlParser:
/////
///// 0. status.  This is one of:
/////             INVALID: the struct holds junk.  CC: the struct is building a CC.  
/////             RPN_START, RPN_END, RPN_MSB, RPN_INCREMENT_DECREMENT: the struct is building an RPN.
/////             NRPN_START, NRPN_END, NRPN_MSB, NRPN_INCREMENT_DECREMENT: the struct is building an NRPN.
///// 1. controllerNumberMSB.  In the low 7 bits.
///// 2. controllerNumberLSB.  In the low 7 bits.
///// 3. controllerValueMSB.  In the low 7 bits. This holds the previous MSB for potential "continuing" messages.

// Parser status values
#define INVALID 0
#define CC 1
#define NRPN_START 2
#define NRPN_END 3
#define NRPN_MSB 4
#define NRPN_INCREMENT_DECREMENT 5
#define RPN_START 6
#define RPN_END 7
#define RPN_MSB 8
#define RPN_INCREMENT_DECREMENT 9

// Data types
#define VALUE 0             // 14-bit data (MSB + LSB)
#define VALUE_MSB_ONLY 1    // 7-bit data which could be either the MSB, or the whole thing, we don't know
#define INCREMENT 2         // An increment request (by a 7-bit delta)
#define DECREMENT 3         // A decrement request (by a 7-bit delta)
#define VALUE_7_BIT_ONLY 4  // Known 7-bit only data (notably for CC directives which are 7-bit only)

struct _controlParser
    {
    uint8_t status = INVALID;

    // The high bit of the controllerNumberMSB is either
    // NEITHER_RPN_NOR_NRPN or it is RPN_OR_NRPN. 
    uint8_t controllerNumberMSB;

    // The high bit of the controllerNumberLSB is either
    // RPN or it is NRPN
    uint8_t controllerNumberLSB;

    // The controllerValueMSB is either a valid MSB or it is 
    // NO_MSB (128).
    uint8_t controllerValueMSB;
    };

// We make 16 parsers here, but if you only care about specific data perhaps you could
// reduct this.
struct _controlparser midiParser[16];

// You need to implement the following functions (or modify the code to pass them in as pointers)

// type can be VALUE, VALUE_MSB_ONLY, or VALUE_7_BIT_ONLY
void handleControlChange(uint8_t channel, uint8_t number, uint16_t value, uint8_t type);

// type can be VALUE, VALUE_MSB_ONLY, INCREMENT, or DECREMENT
void handleNRPN(uint8_t channel, uint8_tcontrollerNumber, uint16_t value, uint8_t type);
void handleRPN(uint8_t channel, uint8_tcontrollerNumber, uint16_t value, uint8_t type);

// also implement this one as a hook.  It can be empty. 
void handleControlChangesInGeneral(uint8_t channel, uint8_t number, uint8_t value);

// Register this function as the handler for your CC data 
void parseControlChange(byte channel, byte number, byte value)
    {
    handleControlChangesInGeneral(channel, number, value);

    _controlParser* parser = &midiParser[channel];

    // BEGIN PARSER

    // potentially 14-bit CC messages
    if (number < 6 || 
        (number > 6 && number < 32))
        {
        parser->status = CC;
        parser->controllerValueMSB = value;
        handleControlChange(channel, number, value, VALUE_MSB_ONLY);
        }

    // LSB for 14-bit CC messages, including continuation
    else if (number >= 32 && number < 64 && number != 38)
        {
        if (parser->status == CC)
            {
            handleControlChange(channel, number, (((uint16_t)parser->controllerValueMSB) << 7) | value, VALUE);
            }
        else parser->status = INVALID;
        }

    // 7-bit only CC messages
    else if ((number >= 64 && number < 96) || 
             (number >= 102 && number < 120))
        {
        parser->status = INVALID;
        handleControlChange(channel, number, value, VALUE_7_BIT_ONLY);
        }

    // Start of NRPN
    else if (number == 99)
        {
        parser->status = NRPN_START;
        parser->controllerNumberMSB = value;
        }

    // End of NRPN
    else if (number == 98)
        {
        if (parser->status == NRPN_START)
            {
            parser->status = NRPN_END;
            parser->controllerNumberLSB = value;
            }
        else parser->status = INVALID;
        }

    // Start of RPN or NULL
    else if (number == 101)
        {
        if (value == 127)  // this is the NULL termination tradition, see for example http://www.philrees.co.uk/nrpnq.htm
            {
            parser->status = INVALID;
            }
        else
            {
            parser->status = RPN_START;
            parser->controllerNumberMSB = value;
            }
        }

    // End of RPN or NULL
    else if (number == 100)
        {
        if (value == 127)  // this is the NULL termination tradition, see for example http://www.philrees.co.uk/nrpnq.htm
            {
            parser->status = INVALID;
            }
        else if (parser->status == RPN_START)
            {
            parser->status = RPN_END;
            parser->controllerNumberLSB = value;
            }
        }

    // Data Entry MSB for NRPN and RPN
    else 
        {
        uint16_t controllerNumber =  (((uint16_t) parser->controllerNumberMSB) << 7) | parser->controllerNumberLSB ;
        if (number == 6)
            {
            if (parser->status == NRPN_END || parser->status == NRPN_MSB || parser->status == NRPN_INCREMENT_DECREMENT)
                {
                parser->controllerValueMSB = value;
                handleNRPN(channel, controllerNumber, ((uint16_t)value) << 7, VALUE_MSB_ONLY);
                parser->status = NRPN_MSB;
                }
            else if (parser->status == RPN_END || parser->status == RPN_MSB || parser->status == RPN_INCREMENT_DECREMENT)
                {
                parser->controllerValueMSB = value;
                handleRPN(channel, controllerNumber , ((uint16_t)value) << 7, VALUE_MSB_ONLY);
                parser->status = RPN_MSB;
                }
            else parser->status = INVALID;
            }

        // Data Entry LSB for RPN, NRPN
        else if (number == 38)
            {
            if (parser->status == NRPN_MSB)
                {
                handleNRPN(channel, controllerNumber, (((uint16_t)parser->controllerValueMSB) << 7) | value, VALUE);
                }
            else if (parser->status == RPN_MSB)
                {
                handleRPN(channel, controllerNumber, (((uint16_t)parser->controllerValueMSB) << 7) | value, VALUE);
                }
            else parser->status = INVALID;
            }

        // Data Increment for RPN, NRPN
        else if (number == 96)
            {
            if (parser->status == NRPN_END || parser->status == NRPN_MSB || parser->status == NRPN_INCREMENT_DECREMENT)
                {
                handleNRPN(channel, controllerNumber , (value ? value : 1), INCREMENT);
                parser->status = NRPN_INCREMENT_DECREMENT;
                }
            else if (parser->status == RPN_END || parser->status == RPN_MSB || parser->status == RPN_INCREMENT_DECREMENT)
                {
                handleRPN(channel, controllerNumber , (value ? value : 1), INCREMENT);
                parser->status = RPN_INCREMENT_DECREMENT;
                }
            else parser->status = INVALID;
            }

        // Data Decrement for RPN, NRPN
        else if (number == 97)
            {
            if (parser->status == NRPN_END || parser->status == NRPN_MSB || parser->status == NRPN_INCREMENT_DECREMENT)
                {
                handleNRPN(channel, controllerNumber , (value ? value : 1), DECREMENT);
                parser->status = NRPN_INCREMENT_DECREMENT;
                }
            else if (parser->status == RPN_END || parser->status == RPN_MSB || parser->status == RPN_INCREMENT_DECREMENT)
                {
                handleRPN(channel, controllerNumber , (value ? value : 1), DECREMENT);
                parser->status = RPN_INCREMENT_DECREMENT;
                }
            else parser->status = INVALID;
            }

        // Can only be 120...127, which should never appear here
        else parser->status = INVALID;
        }
    }
eclab commented 8 years ago

Hmmm, looks like github's code parser is broken for Whitesmiths. I'm doing an attach here.

parser.zip

franky47 commented 8 years ago

Have you had a look at this example ? https://github.com/FortySevenEffects/arduino_midi_library/blob/master/examples/RPN_NRPN/RPN_NRPN.ino

I'd like to avoid putting the whole RPN/NRPN state machine into the general parser, as it would bring a lot of overhead for users who would not need it. The example is here as a demonstration of what could be done, however as I need to rework the library for USB compatibility on v5, I could try and design a plugin system to make the parser extendable, to read RPN/NRPN and other kinds of composed messages (like MTC Frames, Universal SysEx etc..)

franky47 commented 8 years ago

Hint: to format multiline code, use three ` quotes to open and close a code block. You can also specify the language to use for syntax highlighting like this:

```c++
const int foo = 42;```

Which will result in:

const int foo = 42;
eclab commented 8 years ago

On Oct 18, 2016, at 2:21 AM, Francois Best notifications@github.com wrote:

Have you had a look at this example ? https://github.com/FortySevenEffects/arduino_midi_library/blob/master/examples/RPN_NRPN/RPN_NRPN.ino

Hmmm, that's not part of the distro I have. Did you recently develop this?

That being said, there are some weaknesses in its approach, I think [my reading of it could be wrong]. The big one is that it's got state per-message! So if you have 100 NRPN different messages you have to have 100xsizeof(State) memory. Yikes!

My approach was to be message-independent, so I have a single state consisting of four numbers (per channel). Take a closer look at my parser, I (hope) you'll find its approach more plausible for inclusion in the library.

A few other items which look easily fixed:

  1. Increment and decrement are incorrect. If the value is 0 they should increment/decrement by 1.
  2. It appears that your parser can't distinguish between 7-bit messages and ones which are 7-bit but may eventually become 14-bit.
  3. It looks like this parser isn't handling various pathological error cases, such as the first half of an RPN message followed by the second half of an NRPN message. It doesn't have separate states for RPN and NRPN.

Sean

franky47 commented 8 years ago

Yes, I wrote this example last week. It was a quick draft and I think your approach is better indeed, however if you really need to read 100 RPN or NRPN, you'll need to keep a state of 100 unsigned values somewhere (albeit it could be saved somewhere else than RAM, like EEPROM or Program Space).

When you mention that increment/decrement with a value of zero should override to 1, what part of the specification do you base yourself on ? I don't see this mentioned on the official MIDI 1.0 spec.

eclab commented 8 years ago

You're correct about the neon values if you're storing them.  But there are lots of other things you'd do with them, such as map them to a Sysex output or filter them and in these cases you don't want per-NRPN functions but rather a general handler much like you're doing in your library for CC messages. As I understand it Increment / decrement is tricky because many devices implement it in one of two ways: either they send a 0 or they send an explicit value which represents the Increment Delta.  I believe, but could be wrong , that a 0 by convention means to do x++ rather than x+=v

franky47 commented 8 years ago

Yes, the state was one of the dodgy parts. What I wanted to do with my parser is avoid users the potential need of recombining 14-bit encoded values from MSB and LSB, but the extra overhead of the example is a bad design, so I'll move the value decoding code into a static helper method.

I'll integrate your code when I'm working on v5.0, at the moment I'm finishing things for v4.3 which will only bring simple RPN/NRPN output support. I'll update the example to reference this thread so users can have a look and discuss.

However, this data increment convention is very interesting. Do you have references of hardware that use any of them ?

eclab commented 8 years ago

On Oct 18, 2016, at 1:51 PM, Francois Best notifications@github.com wrote:

However, this data increment convention is very interesting. Do you have references of hardware that use any of them ?

No, but here's Phil Rees's page.

http://www.philrees.co.uk/nrpnq.htm

Sean

eclab commented 7 years ago

Well take your time: it's not a high priority to me as I wrote the whole dang thing in C anyway.

Sean

On Oct 18, 2016, at 1:51 PM, Francois Best notifications@github.com wrote:

Yes, the state was one of the dodgy parts. What I wanted to do with my parser is avoid users the potential need of recombining 14-bit encoded values from MSB and LSB, but the extra overhead of the example is a bad design, so I'll move the value decoding code into a static helper method.

I'll integrate your code when I'm working on v5.0, at the moment I'm finishing things for v4.3 which will only bring simple RPN/NRPN output support. I'll update the example to reference this thread so users can have a look and discuss.

However, this data increment convention is very interesting. Do you have references of hardware that use any of them ?

� You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or mute the thread.

eclab commented 6 years ago

I've made some modifications to the parser (not tested yet) which simplifies it, adds a few minor but useful features, and removes some assumptions which aren't necessarily true in practice. And simplified the documentation.

///// INTRODUCTION TO THE CC/RPN/NRPN PARSER
///// The parser is located in parseControlChange(...), which
///// can be set up to be the handler for CC messages by the MIDI library.
/////
///// The library is set up to maintain sixteen parsers, one per MIDI channel.
/////
///// Each parser can be set up in one of three modes:
/////
///// 1. Raw CC.  NRPN and RPN are not considered, nor 14-bit CC messages.
/////    Every incoming CC message is treated as a simple 7-bit message.
/////    This is the same as incoming CC messages from the MIDI library so
/////    it's likely not that useful to you.  To set this mode, call
/////    setParseRawCC(channel, true);
/////    
///// 2. 7-bit CC plus NRPN plus RPN.  This is the default.  CC messages will
/////    be parsed as CC, except for ones which are part of the NRPN or RPN
/////    mechanism -- these will be used to build NRPN/RPN messages.
/////
///// 3. 7-bit CC plus 14-bit CC plus NRPN plus RPN.  The CC numbers in 
/////    range 32...63 will be considered to be the 7-bit LSB for CC 
/////    numbers in range 0..31 (which will be the 7-bit MSB).  The MIDI spec
/////    defines these together as 14-bit CC messages: but very, very few
/////    devices (in fact I've never seen one) take advantage of this feature as 
/////    it is highly wasteful of valuable CC parameters, and so it is off by default.
/////    At any rate, other CC  messages will
/////    be parsed as CC, except for ones which are part of the NRPN or RPN
/////    mechanism -- these will be used to build NRPN/RPN messages.  To set this
/////    mode, call     setParse14BitCC(channel, true);
/////
///// When a CC, NRPN, or RPN message arrives, one of the
///// following functions will be called (which you need to implement):
/////
///// void handleControlChange(uint8_t channel, uint8_t number, uint16_t value, uint8_t type);
///// void handleNRPN(uint8_t channel, uint8_t number, uint16_t value, uint8_t type);
///// void handleRPN(uint8_t channel, uint8_t number, uint16_t value, uint8_t type);
/////
///// The NUMBER is the CC, RPN, or NRPN parameter number.  The VALUE is the parameter
///// value.  The TYPE is the nature of the value.  Types are:
/////
/////    VALUE            14-bit data of the form MSB * 128 + LSB
/////       VALUE_7_BIT    7-bit data (0...127)
/////     INCREMENT    A request to increment the current value by a (7-bit) Value
/////     DECREMENT    A request to decrement the current value by a (7-bit) Value
/////
///// INCREMENT and DECREMENT only occur in NRPN and RPN.
///// VALUE occurs in NRPN, RPN, and 14-bit CC.
///// VALUE_7_BIT occurs in raw-only CC and in normally 7-bit CC messages
/////
///// Additionally, the following hook is called which will be called for each and every
///// incoming raw CC message.  Implement it with an empty body if you don't need it.
///// 
///// void handleControlChangesInGeneral(channel, number, value);
/////
/////
///// THE PROBLEM WITH LSB AND MSB
///// MIDI has a serious flaw in its definition of 14-bit messages (CC, NRPN, or RPN): it is not
///// specific as to the order in which LSB or MSB arrive, or indeed whether they must arrive at all.
///// In 14-bit CC, MSB is required to come first, but LSB can then show up zero or more times.
///// Thus it's not clear when a 14-bit message will be fully formed.  It's even worse in NRPN or RPN,
///// where the MSB or LSB can come in either order, and in any combination, including none.  You could
///// see a bunch of MSBs, or just a bunch of LSBs, or some arbitrary mixture of them.  Thus you must
///// parse every single MSB or LSB arriving as a new 14-bit message (combined with previous data). 
///// Long story short, you may find multiple messages arriving to update a single value.
/////
/////
///// RPN NULL messages:            [RPN 127 with value of 127]
///// These are special messages which supposed to completely reset the parser, clearing out any
///// existing NRPN or RPN messages.

// You need to implement the following functions

//// This is called whenever a 7-bit or 14-bit CC message is received.
//// Type can be VALUE (only if 14-bit CC is turned on) or VALUE_7_BIT_ONLY (in any case)
void handleControlChange(uint8_t channel, uint8_t number, uint16_t value, uint8_t type);

// type can be VALUE, VALUE_MSB_ONLY, INCREMENT, or DECREMENT
void handleNRPN(uint8_t channel, uint8_t number, uint16_t value, uint8_t type);
void handleRPN(uint8_t channel, uint8_t number, uint16_t value, uint8_t type);

// Also implement this one as a hook.  It can be empty. 
// The value is the raw 7-bit CC value.
void handleControlChangesInGeneral(uint8_t channel, uint8_t number, uint8_t value);

// Parser status values
#define INVALID 0
#define CC 1
#define NRPN_START 2
#define NRPN_END 3
#define RPN_START 4
#define RPN_END 5

// Data types
#define VALUE 0             // 14-bit data (MSB * 128 + LSB)
#define VALUE_7_BIT 1   // 7-i5 eq5q 
#define INCREMENT 2         // An increment request (by a 7-bit delta)
#define DECREMENT 3         // A decrement request (by a 7-bit delta)

typedef struct _controlParser
    {
    uint8_t status = INVALID;

    // The high bit of the controllerNumberMSB is either
    // NEITHER_RPN_NOR_NRPN or it is RPN_OR_NRPN. 
    uint8_t controllerNumberMSB;

    // The high bit of the controllerNumberLSB is either
    // RPN or it is NRPN
    uint8_t controllerNumberLSB;

    // The controllerValueMSB is either a valid MSB or it is NO_MSB (128).
    uint8_t controllerValueMSB;

    // The controllerValueLSB is either a valid LSB or it is NO_LSB (128).
    uint8_t controllerValueLSB;

    // Set this to true to force the parser to only parse 
    // individual raw CC messages, no NRPN/RPN at all
    uint8_t parseRawCC = false;

    // Set this to true to tell the parser to parse CC messages
    // of 32...63 as the LSB for CC messages 0...31.  This is very
    // rarely implemented, if at all, by synthesizers and so probably
    // should always be false.
    uint8_t parse14BitCC = false;

    } controlParser;

controlParser midiParser[16];

void setParseRawCC(uint8_t channel, uint8_t on)
  {
  midiParser[channel].parseRawCC = on;
  }

void setParse14BitCC(uint8_t channel, uint8_t on)
  {
  midiParser[channel].parse14BitCC = on;
  }

void parseControlChange(byte channel, byte number, byte value)
    {
    handleControlChangesInGeneral(channel, number, value);

    controlParser* parser = &midiParser[channel];

  /// Raw CC messages are always sent as 7-bit
    if (parser->parseRawCC)
        {
        parser->status = INVALID;
        handleControlChange(channel, number, value, VALUE_7_BIT);
        }

    // MSB for a 14-bit CC message.  Note that when an MSB arrives, the
    // MIDI spec says that the LSB should be considered zero.  The spec
    // does not consider the situation where an LSB has already arrived
    // (perhaps we had MSB LSB LSB LSB .... MSB), where it might be
    // logical to NOT reset the LSB to zero.  So here we always assume the
    // LSB is zero no matter what.  
    else if (number != 6 && number < 32 && parser->parse14BitCC)
    {
    parser->status = CC;
    parser->controllerValueMSB = value;
    parser->controllerNumberMSB = number;
    handleControlChange(channel, number, ((uint16_t)parser->controllerValueMSB) << 7, VALUE);
    }

    // LSB for 14-bit CC messages, including continuation
      else if (number != 38 && number >= 32 && number < 64 && parser->parse14BitCC)
          {
          // This shouldn't happen -- we're supposed to always get an MSB before
          // its accompanying LSB.  But if we get an orphaned LSB, we'll assume that
          // the MSB is zero.
          if (parser->status != CC || parser->controllerNumberMSB + 32 != number)
              {
              parser->status = CC;
              parser->controllerValueMSB = 0;
              }
      handleControlChange(channel, number, (((uint16_t)parser->controllerValueMSB) << 7) | value, VALUE);
          }

  // 7-bit only CC messages, including channel mode, but not including NRPN CC messages
  // Because this is after the if-statements for parse14BitCC, we know the 32...63 range is 7-bit
      else if ((number != 6 && number != 38 && number < 96) || (number >= 102 && number < 128))
          {
          parser->status = INVALID;
          handleControlChange(channel, number, value, VALUE_7_BIT);
          }

  // Start of NRPN parameter declaration
      else if (number == 99)
          {
          parser->status = NRPN_START;
          parser->controllerNumberMSB = value;
          }

  // End of NRPN parameter declaration
      else if (number == 98)
          {
          parser->controllerValueMSB = 0;
          if (parser->status == NRPN_START)
              {
              parser->status = NRPN_END;
              parser->controllerNumberLSB = value;
              parser->controllerValueLSB = 0;
              parser->controllerValueMSB = 0;
              }
          else parser->status = INVALID;
          }

  // Start of RPN parameter declaration or NULL
      else if (number == 101)
          {
          if (value == 127)  // this is the NULL termination tradition, see for example http://www.philrees.co.uk/nrpnq.htm
              {
              parser->status = INVALID;
              }
          else
              {
              parser->status = RPN_START;
              parser->controllerNumberMSB = value;
              }
          }

  // End of RPN parameter declaration or NULL
      else if (number == 100)
          {
          parser->controllerValueMSB = 0;
          if (value == 127)  // this is the NULL termination tradition, see for example http://www.philrees.co.uk/nrpnq.htm
              {
              parser->status = INVALID;
              }
          else if (parser->status == RPN_START)
              {
              parser->status = RPN_END;
              parser->controllerNumberLSB = value;
              parser->controllerValueLSB = 0;
              parser->controllerValueMSB = 0;
              }
          }

      // We're currently parsing NRPN or RPN
      // Note that unlike 14-bit CC, the data entry MSB or LSB can come in any 
      // order and any sequence.  There's no constraints.  So the best we can do is 
      // issue an NRPN or RPN message for every single incoming data entry.  That's
      // obviously suboptimal but what can you do?  Welcome to MIDI.
      else  
          {
          uint16_t controllerNumber =  (((uint16_t) parser->controllerNumberMSB) << 7) | parser->controllerNumberLSB ;

          if (parser->status == NRPN_END)
              {
              // Data Entry MSB for RPN, NRPN
              if (number == 6)
                  {
                  parser->controllerValueMSB = value;
                  handleNRPN(channel, controllerNumber, (((uint16_t)parser->controllerValueMSB) << 7) | parser->controllerValueLSB, VALUE);
                  }

              // Data Entry LSB for RPN, NRPN
              else if (number == 38)
                  {
                  parser->controllerValueLSB = value;
                  handleNRPN(channel, controllerNumber, (((uint16_t)parser->controllerValueMSB) << 7) | parser->controllerValueLSB, VALUE);
                  }

              // Data Increment for RPN, NRPN
              else if (number == 96)
                  {
                  handleNRPN(channel, controllerNumber , (value ? value : 1), INCREMENT);
                  }

              // Data Decrement for RPN, NRPN
              else if (number == 97)
                  {
                  handleNRPN(channel, controllerNumber, (value ? value : 1), DECREMENT);
                  }
              else parser->status = INVALID;
              }
          else if (parser->status == RPN_END)
              {
              if (number == 6)
                  {
                  parser->controllerValueMSB = value;
                  handleRPN(channel, controllerNumber, (((uint16_t)parser->controllerValueMSB) << 7) | parser->controllerValueLSB, VALUE);
                  }

              // Data Entry LSB for RPN, NRPN
              else if (number == 38)
                  {
                  parser->controllerValueLSB = value;
                  handleRPN(channel, controllerNumber, (((uint16_t)parser->controllerValueMSB) << 7) | parser->controllerValueLSB, VALUE);
                  }

              // Data Increment for RPN, NRPN
              else if (number == 96)
                  {
                  handleRPN(channel, controllerNumber, (value ? value : 1), INCREMENT);
                  }

              // Data Decrement for RPN, NRPN
              else if (number == 97)
                  {
                  handleRPN(channel, controllerNumber, (value ? value : 1), DECREMENT);
                  }
              else parser->status = INVALID;
              }

          // This probably should never happen
          else parser->status = INVALID;
          }
    }
franky47 commented 6 years ago

Nice ! If you could open a PR and add some unit tests (they are run automatically when you push to the PR branch), we can look on how to integrate that into the code base.

Cheers !

eclab commented 6 years ago

That'll be a long way out. I've got a bunch of other stuff on my plate first. Heck, Gizmo's still using an archaic version of the 47Effects library largely because it's small. But when I make a few Gizmo revisions, I'll be able to verify the code above at least.

Alemau2020 commented 4 years ago

Good evening everyone, I'm writing from Italy and unfortunately for me, I'm completely in the dark about programming. I have been looking for a solution for some time to be able to modify the parameters of an Alesis Micron (which I suppose some of you will know: it receives Nrpn messages and not simple Midi CC, at least as regards the modification of non-global parameters, therefore different from the volume, from pitch bend, etc ...) and having an Arduino Uno board, I thought of looking for information on it, hoping that someone had written a code that can be used with Arduino. I would like to ask you if the current midi 5.02 library (which also includes this type of messages) is fully functional and you can use it to send specific commands to Micron (as well as to other synthesizers that use this type of message). In addition, after uploading it to Arduino Uno (everything is loaded correctly), I make an example, to send messages to ensure that a sound undergoes the variation of the attack (therefore in relation to the Attack of the amplifier in the Adsr section), I must (I have been informed about this) enter the Msb and Lsb parameters based on CC 99, 98 as regards the assignment of the Nprn and non-CC values ​​to be sent to Micron, CC 65, 64 to set the parameter you want to modify, and finally 6 and 38 to enter the range relating to the parameter to be modified (for example from 0 to 1023), but exactly in which portion of the code? Sorry for the question, certainly trivial for those who have compiled the sketch and those who know how to program, but certainly less for those who find themselves faced with a decidedly complex sketch. Thanks!

Alemau2020 commented 4 years ago

For example, in the Micron the parameter for controlling the attack of the first envelope of the amplifier section is the number 66 and has a range from 0 to 255. As I said, reading some articles, (but maybe I'm wrong) to declare that I want to use an Rnpn message I have to stick to the specifications of the MIdi CC and for the Msb values it is the CC99, while for the Lsb one of the 98. If I understood correctly by setting these values the program should interpret them as the type of messages to be output (always speaking in an abstract way, I should specify it in the portion of the sketch where the output data is processed). To specify which parameter I want to check (the number 66, the attack of the first ADSR), I must specify the CC65 for the Msb values and the CC64 for the Lsb values. To "finish", the range with which I can intervene on that particular parameter is missing, and to determine this I need CC8 for the Msb values and 36 for the Lsb values. I attach 3 screenshots related to one of the last messages sent on this page, exactly from "eclab commented on 11 Jun 2018", taken as a starting point. I note that as regards the data entry parameters, 97 and 96 are specified (increase and decrease). Assuming that I don't see (but the one proposed in that message could only be a portion of the entire code), after the declaration of use of the Nrpn messages with the midi codes CC99 and CC98, to follow (even if the provisions of the CC codes indicated in the code do not follow the same "order" with which I indicated which parameter I want to control and in what way, for example when I am in front of the line related to the "data entry": "handleNRPN (channel, controllerNumber, ((( uint16_t) parser-> controllerValueMSB) << 7) | parser-> controllerValueLSB, VALUE); " Instead of channel I should specify the midi channel that I intend to use, for example the number 1, then the number of the parameter I want to modify, I had made the example with the number 66, then midi channel 1, parameter number 66), but I have (as if it were a "novelty" :)) a doubt, these parameters, the CC6 and the CC38 should be used to specify the minimum and maximum value of a certain parameter, so I should insert 0 and 255, but in which line? What precedes this that I mentioned? "parser-> controllerValueMSB = value; } " So instead of = value, should I enter 0 or 255 depending on whether it is Msb or Lsb data? I understand that these may seem stupid questions to you, but if some things may seem intuitive, not being within the programming logic, everything can be difficult. Thanks!

Start  of NRPN parameter declaration Data entry Msb and Lsb Data increment and decrement for RPN, NRPN

eclab commented 4 years ago

Ciao da Washington, DC.

I cannot speak to how the NRPN/RPN parser is currently being used in in the MIDI library. But the general design of the parser is that it has a single function, parseControlChange(...), which is registered as the handler for incoming CC messages. Depending on the MIDI channel of an incoming CC message, this function will load the appropriate struct from the array of structs, one per channel, and use it to maintain the current state of incoming CC messages. As appropriate, it will call one of three functions that you must implement, handleControlChange, handleNRPN, and handleRPN. It'll also call handleControlChangesInGeneral, which you must also implement: but any of these can be implemented by doing nothing. In each struct, for each channel, you can set a flag to tell it to consider 14-bit CC messages or not; and there are some other gizmos. It should be pretty straightforward.

I don't understand your posting however. You're looking at the NRPN parser, but you want to send NRPN to the Micron. The NRPN parser is only for receiving and understanding NRPN. To send NRPN you need to understand how NRPN works.

NRPN is just a sequence of CC messages. That's all. The main problem is that some synthesizers interpret NRPN data in what I call "coarse" (or "MSB-only") mode, and others interpret it in "fine" (or "MSB+LSB") mode. These aren't official NRPN concepts, but the NRPN spec is vague and manufacturers have interpreted in their own inconsistent ways. In "coarse" mode, the values 0...127 are interpreted as MSB 0...127, and there is no LSB (or it's always 0). In "fine" mode, the values 0...16383 are interpreted as MSB * 128 + LSB. I believe that the Micron uses "fine" mode, but stupidly they have signed data, so some of their values are very big, and others are very small. We'll get to that.

So if you want to send a number X as NRPN parameter N in "fine" mode, you do this.

N_MSB = N / 128 or in C you can say ((N >> 7) & 127) N_LSB = N % 128 or in C you can say (N & 127) X_MSB = X / 128 or in C you can say ((X >> 7) & 127) X_LSB = X % 128 or in C you can say (X & 127)

Now send a series of CC messages to the appropriate MIDI channel as follows: Send N_MSB as CC(101) Send N_LSB as CC(100) Send X_MSB as CC(6) Send X_LSB as CC(38)

You mentioned increment and decrement messages. Do not bother with them.

Note that Alesis did many stupid things with their Ion/Micron/MiniAK MIDI code, not the least of which is that they never released a Sysex spec, grrr. Another stupid thing they did affects you: they made their NRPN signed rather than unsigned. For example, consider the Transpose for Oscillator 1 (NRPN 13). Transpose are values that range from -7...+7. If they had any brains at all, Alesis would have let you set the values 0...15 in NRPN to represent this range. But no: instead you must set the values 16377...16383 for -7...-1, and then values 0...7 for 0...+7. A great many Micron parameters are like this, and they've all got different ranges. So as a result your controller will have to deal with this discontinuity on a case-by-case basis. It's hideous.

Moreover, if you're trying to create a CC to NRPN translator for the Micron, note that there is a reason that the Micron does NRPN and not raw CC: because raw CC only can express 128 values (0...127), and many of the Micron's parameter ranges are much, much bigger than this.

Alemau2020 commented 4 years ago

Thanks for the reply, see if I can understand something and find a solution.