Arduino MIDI Program Change to Voodoo Labs GCX Converter
To solve this problem I used an Arduino UNO and a Olimex MIDI shield. I also want to use MIDI PC messages and Bank Selects.

For reference:


A Channel 16 PC 77 with Bank select 0 converts to binary 0x4D => Binary 01001101
A Channel 16 PC 77 with Bank Select 1 converts to binary 0xCD => Binary 11001101


Just to point out, the Bank Select value basically controls bit 8 and the PC value represents bits 6 down to 0

Based on the binary output, I scroll through bits 0 to 7 and send messages on CC 80 to CC 87. If a bit is a 1, I turn the loop on by setting the corresponding CC value to 127 and if it is a zero, I set the CC value to 0.

The Helix can generate a Bank Select and PC as one message, so for the price of one message, this project will make me 8 CC#'s effectively. ;) 

It should allow other messages through, with the Arduino's SW loop latency. There maybe some other side effects to, but those should be limited to channel 16 only. 

I have tested this on a Arduino UNO board with an Olimex MIDI Shield.

// GCX Editor
// By Rich Maes
// Converts MIDI PC's on channel 16 to GCX CC's starting at 0x80
// Allows everything else to pass.


#define MIDI_PC_CH16 0xCF
#define MIDI_CC_CH16 0xBF
#define MIDI_SYSEX 0xF0
#define MIDI_SYSRT_CLK 0xF8

boolean byteReady; 
boolean sendCCMessage;
int ccMsgsToSend;
unsigned char midiByte;
unsigned char capturePCByte;
unsigned char captureCCByte;

// Queue Logic for storing messages
int headQ = 0;
int tailQ = 0;
unsigned char tx_queue[128];

int getQDepth();
void addQueue(unsigned char myByte);
void addCCQueue(unsigned char captureCCByte, unsigned char capturePCByte);
unsigned char deQueue();

static enum {
    STATE_UNKNOWN,
    STATE_1PARAM,
    STATE_1PARAM_CONTINUE,
    STATE_2PARAM_1,
    STATE_2PARAM_2,
    STATE_2PARAM_1_CONTINUE,
    STATE_PASSTHRU
  } state = STATE_UNKNOWN;

void setup() {
  // put your setup code here, to run once:
  //  Set MIDI baud rate:
  Serial.begin(31250);
  sendCCMessage = false;
  byteReady = false;
  midiByte = 0x00;
  state = STATE_UNKNOWN;
  captureCCByte = 0;
  capturePCByte = 0;
  ccMsgsToSend = 0;
}

int getQDepth() {
int depth = 0;
    if (headQ &lt; tailQ) {
        depth = 128 - (tailQ - headQ);
    } else {
        depth = headQ - tailQ;
    }
    return depth;
}

void addQueue (unsigned char myByte) {
    int depth = 0;
    depth = getQDepth();

    if (depth &lt; 126) {
        tx_queue[headQ] = myByte;
        headQ++;
        headQ = headQ % 128; // Always keep the headQ limited between 0 and 127
    }
}

void addCCQueue(unsigned char myCaptureCCByte, unsigned char myCapturePCByte) {
    int i;
    if (getQDepth() &lt; 80) {
        // There is enough space to add our CC messages
        for (i = 0; i &lt; 8; i++) {
            addQueue(0xBF);
            addQueue(80 + i);
            addQueue(127 * ((((myCaptureCCByte * 128) + myCapturePCByte) &gt;&gt; i) % 2));
        }
    } else {
        // This is an error condition.  So reset the queue and pointers
        headQ = 0;
        tailQ = 0;
        byteReady = false;
        for (i = 0; i &lt; 128; i++) {
            tx_queue[i] = 0;
        }
    }
}

unsigned char deQueue() {
    unsigned char myByte;
    myByte = tx_queue[tailQ];
    tailQ++;
    tailQ = tailQ % 128;  // Keep this tailQ contained within a limit
    // Now that we dequeed the byte, it must be sent. 
    return myByte;
}

void loop() {
    if (byteReady) {
        if (midiByte &gt;= 0xF0) {
            // This automatically passes all clocks and System Realtime Messages
            state = STATE_PASSTHRU;
        } else if (midiByte &gt;= 0x80) {
            switch (midiByte) {
            case MIDI_PC_CH16:
                state = STATE_1PARAM;
                break;
            case MIDI_CC_CH16:
                state = STATE_2PARAM_1;
                break;
            default:
                state = STATE_PASSTHRU;
                break;
            }
        }  else {
            switch (state) {
            case  STATE_1PARAM:
                capturePCByte = midiByte;
                state = STATE_1PARAM_CONTINUE;
                addCCQueue(captureCCByte, capturePCByte);
                break;
            case STATE_2PARAM_1:
                if (midiByte == 32) state = STATE_2PARAM_2;
                else state = STATE_2PARAM_1_CONTINUE;
                break;
            case STATE_2PARAM_2:
                state = STATE_2PARAM_1_CONTINUE;
                captureCCByte = midiByte;
                break;
            default:
                state = STATE_PASSTHRU;
                break;
            }
        }
    }

    if ((state == STATE_PASSTHRU) &amp;&amp; byteReady) {
        // Just pass messages unaltered.  Also don't let any of our modified message through if
        // we are passing though.  Our burst of modified CC# can wait.
        // Serial.write(midiByte);
        addQueue(midiByte);
        // state = STATE_UNKNOWN;
    }
    byteReady = false;

    if (getQDepth() &gt; 0) {
        // We have a byte to send, dequeu and send it
        Serial.write(deQueue());
    }    
}

// The little function that gets called each time loop is called.  
// This is automated somwhere in the Arduino code.
void serialEvent() {
  if (Serial.available()) {
    // get the new byte:
    midiByte = (unsigned char)Serial.read();
    byteReady = true;
  }
}