Skip to content

CC/RPN/NRPN Parser #60

Open
Open
@eclab

Description

@eclab

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions