Wednesday, November 21, 2012

MiniAK/Micron/ION + BCR2000 + Arduino

I made a quick Arduino program to convert the NRPN data output from a BCR2000 (or conceivably any MIDI controller that sends NRPN's) to the MiniAK / Micron / ION's rather non-standard format. It does two things:

1 - The MiniAK expects "signed" 14-bit NRPN values. The MIDI spec (and conceivably most other synths ever made) supports 14-bit unsigned values. Thus, there is no simple way to make a standard controller (even one with the cool functionality of the BCR2000) to easily handle faking out the 2's compliment negative numbers. So - the Arduino steps in and converts a normal, positive range to the MiniAK's signed range. For example, controller #226 (Patch cable 12 level) goes from -1000 to 1000 on the MiniAK. The BCR2000 is set in the range [0,2000]. Then, the Arduino converts the message to the [-1000,1000] range that the MiniAK requests.

2 - The MiniAK expects that every NRPN is 14-bit. However, when you use a toggle button on the BCR2000, it can only send NRPN MSB data. Thus, the toggle buttons can't be used as-is to control polyphony, waveform type, etc. For NRPN ranges less than 0-127 (0x7F), the Arduino converts MSB-only signals to the LSB (bit-shifts the 14-bit data value by 7 bits.

Recipe:

Ingredients:
-  a MiniAK/Micron/ION
- a BCR2000 (others should work fine)
- a PC
- two MIDI cables
- a USB A/B cable
- an Arduino (I used an R3, others should work fine)
- a MIDI Shield (I used the LinkSprite, others should work fine)

  1. Connect the Arduino to the MIDI Shield
  2. Turn the Serial port switch on the MIDI Shield to "OFF"
  3. Download and install the Arduino environment
  4. Connect the USB cable to the PC and the Arduino
  5. Open the Arduino environment.
  6. Copy and paste the code below into the sketchpad.
  7. Go to "File"/"Upload" to compile and upload the code.
  8. Turn the Serial port switch on the MIDI Shield to "ON"
  9. Connect one MIDI cable to the BCR2000 Midi OUT and the Arduino MIDI IN
  10. Connect one MIDI cable to the MiniAK Midi IN and the Arduino MIDI OUT
  11. Power up the BCR2000 and the MiniAK.
  12. Program some NRPN's on the BCR2000 and confirm functionality!

The code is pretty straightforward. There are a few add'l var's from another project I was working on but they do not interfere with the translation code. You should just be able to copy the following source code into your Arduino sketchpad, compile, and upload. I'm positive there are optimizations to be had and code to be cleaned up but it appears to be 100% functional from my testing to date. If I (or anyone else) encounters a bug I'll edit the code in the post.

[edit #1: need to support inc/dec functionality]


Copy below this line:

// MIDI Filter
// Andrew Eloff
// This program selectively processes MIDI input data and spits to the output
// The basic motivation is to have the AKAI MiniAK be controllable using
// ordinary control surfaces and software.

// How we're going to do it for the MINIAK/Micron:
// MIDI controller outputs the NRPN ranges all from 0..MAX
// Arduino converts from 0..MAX to MIN..MIN+MAX.
// remember, MINIAK/Micron believes that the NRPN value is SIGNED
// but it's really unsigned. so -100 to 100 is really
// 16284-16383 - 0-100
//


//http://ion-micron-miniak.wikia.com/wiki/Common_FAQ

/* From http://www.midi.org/techspecs/midimessages.php#3
To set or change the value of a Registered Parameter:

1. Send two Control Change messages using Control Numbers 101 (65H) and 100 (64H) to 
select the desired Registered Parameter Number, as per the following table.

2. To set the selected Registered Parameter to a specific value, send a Control Change 
messages to the Data Entry MSB controller (Control Number 6). If the selected Registered 
Parameter requires the LSB to be set, send another Control Change message to the Data Entry 
LSB controller (Control Number 38).

3. To make a relative adjustment to the selected Registered Parameter's current value, 
use the Data Increment or Data Decrement controllers (Control Numbers 96 and 97).

*/
//  0016F35A 1 -- B0 63 00 1 --- CC: NRPN MSB
//  0016F35A 1 -- B0 62 2F 1 --- CC: NRPN LSB
//  0016F35A 1 -- B0 06 00 1 --- CC: Data Entry MSB
//  0016F35A 1 -- B0 26 0C 1 --- CC: Data Entry LSB
// this is 4 messages to be sent via the serial port
// sends a value [00 0C] to address[00 2F]

typedef struct {
  unsigned int addr;
  int min_val;
  int max_val; 
} miniak_nrpn_struct;

extern miniak_nrpn_struct miniak_nrpns[];

void setup() {
  //  Set MIDI baud rate:
  Serial.begin(31250);
  note_init();
}

long transpose = 0;
long setting_state = 0; // STATE 0 = regular play STATE 1 = setting

unsigned long last_pitch_time = 0;

unsigned char message_state = 0;

#define MESSAGE_DATA_MAX (5)
unsigned char message_data[MESSAGE_DATA_MAX];
unsigned char message_size =0;

unsigned char nrpn_addr_msb = 0;
unsigned char nrpn_addr_lsb= 0;
unsigned long nrpn_addr = 0;

unsigned char nrpn_data_msb= 0;
unsigned char nrpn_data_lsb= 0;
unsigned long nrpn_data= 0;

unsigned char nrpn_data_out_msb= 0;
unsigned char nrpn_data_out_lsb= 0;
unsigned long nrpn_data_out= 0;

long nrpn_output_data_min;
long nrpn_output_data_max;

void make_bloop(int freq1, int freq2) {
  noteOn(freq1, 0x40, 0x1); 
  delay(50);        
  noteOn(freq1, 0x0, 0x1);           
  delay(50);        
  noteOn(freq2, 0x40, 0x1); 
  delay(50);        
  noteOn(freq2, 0x0, 0x1);           
  delay(50);       
#define RIBBON_MODE_CONTINUOUS (0)
#define RIBBON_MODE_NOTE (1)

unsigned char inchar;
int current_pitch_value = 0;
unsigned char current_fret_value = 0;
//int ribbon_mode = RIBBON_MODE_CONTINUOUS;
int ribbon_mode = RIBBON_MODE_NOTE;
// pressing "2" and "A" on the rock band keytar increments/decrements


int last_program_change = 0;
#define FRET_KEY_NOT_PRESSED ((unsigned char)0xFF)
#define NUM_KEYS (0x7F)
// last note state: in the future this should be a queue for polyphony.
// for now 
unsigned char last_fret[NUM_KEYS]; // last fret val for a given key
unsigned char last_velocity[NUM_KEYS]; // last key velocity for a given key
void note_init() {
  memset(last_fret,FRET_KEY_NOT_PRESSED, sizeof(last_fret));
  memset(last_velocity,0x00, sizeof(last_velocity));
}

long nrpn_convert_range_to_miniak(unsigned long addr_in, long data_in) {
/****************************************************************

****************************************************************/
// AWE TODO: create sparse matrix so we don't have to loop through
// the messages
  long ii;
  long data_out;
  unsigned long this_addr = 0;


  this_addr = 0;
  for (ii=0;this_addr != 999 && ii<1024 ;ii++) { // ii<1024 = while loop failsafe
    this_addr = miniak_nrpns[ii].addr;
    if (this_addr > addr_in) break; // assume nrpn's are in ascending order!!
    if (this_addr == 999) break; // we got to the end of the array
    if (this_addr != addr_in) continue;

    // we have a match! first, if the range is less than
    // 7 bits then we should assume the controller is sending
    // MSB only. This is most friendly to the BCR2000 and probably
    // other devices:
    if (miniak_nrpns[ii].max_val - miniak_nrpns[ii].min_val <= 0x7F) {
      data_in = data_in >> 7; // shift MSB val to LSB
    }

    // Assume that the NRPN range given from the controller
    // is the same as the Miniak's, just always in the range
    // [0..n], even though the output may be [-n/2..n/2]
    data_out = data_in + miniak_nrpns[ii].min_val;
    if (data_out > miniak_nrpns[ii].max_val)
      data_out = miniak_nrpns[ii].max_val;
    return data_out;
  }
  
  return data_in; // means there wasn't a conversion for the MINIAK NRPN
}

void nrpn_output_converted_data(unsigned char command_byte) {
// make sure to preserve channel via "command_byte"
//  0016F35A 1 -- B0 06 00 1 --- CC: Data Entry MSB
//  0016F35A 1 -- B0 26 0C 1 --- CC: Data Entry LSB
Serial.write(command_byte); // CC
Serial.write(0x06); // MSB
Serial.write(nrpn_data_out_msb);

Serial.write(command_byte); // CC
Serial.write(0x26); // LSB
Serial.write(nrpn_data_out_lsb);
}
void nrpn_convert_message_to_miniak(unsigned char msg_data[]){
/*********************************************************
** parse the message in from MIDI and then convert it
** to MINIAK-friendly messages (crazy "negative numbers")
** address data should just be passed thru
*********************************************************/
  int ii;
  
  switch (msg_data[1]) {
    case 0x63:
      // note: if addr has changed, be sure to zero out data!
      if (nrpn_addr_msb != msg_data[2]) {
        nrpn_data = nrpn_data_msb = nrpn_data_lsb = 0;
      }
      nrpn_addr_msb = msg_data[2];
      nrpn_addr = nrpn_addr&0x007F;
      nrpn_addr |= (msg_data[2]<<7);
      for (ii=0;ii<3;ii++) { // note magic number, cc always 3 bytes
        Serial.write(msg_data[ii]);
      }    
    break;

    case 0x62:
      if (nrpn_addr_lsb != msg_data[2]) {
        nrpn_data = nrpn_data_msb = nrpn_data_lsb = 0;
      }
      nrpn_addr_lsb = msg_data[2];
      nrpn_addr = nrpn_addr&0x3F80;
      nrpn_addr |= (msg_data[2]);
      for (ii=0;ii<3;ii++) { // note magic number, cc always 3 bytes
        Serial.write(msg_data[ii]);
      }    
      break;

    case 0x06:
      nrpn_data_msb = msg_data[2];
      nrpn_data &= 0x007F;
      nrpn_data |= (msg_data[2]<<7);
      nrpn_data_out = nrpn_convert_range_to_miniak(nrpn_addr,nrpn_data);
      nrpn_data_out_lsb = (nrpn_data_out&0x7F);
      nrpn_data_out_msb = (nrpn_data_out>>7)&0x7F;
      nrpn_output_converted_data(msg_data[0]);
break;

    case 0x26:
      nrpn_data_lsb = msg_data[2];
      nrpn_data &= 0x03F80;
      nrpn_data |= (msg_data[2]);
      nrpn_data_out = nrpn_convert_range_to_miniak(nrpn_addr,nrpn_data);
      nrpn_data_out_lsb = (nrpn_data_out&0x7F);
      nrpn_data_out_msb = (nrpn_data_out>>7)&0x7F;
      nrpn_output_converted_data(msg_data[0]);
    break;

  }// switch message_data[1]
}



int message_process (unsigned char *msg_data, int msg_size) {
  int ii;
  switch (msg_data[0]&0xF0) {
    case 0xB0: // control change - including NRPN!!
      nrpn_convert_message_to_miniak(msg_data); // note this outputs data too
      return 0;  
    break;
      
    case 0x80: // note off
      // now write out the data
      for (ii=0;ii<msg_size;ii++) {
        Serial.write(msg_data[ii]);
      }       
      return 0;
    break;
    
    case 0x90: // note on
      for (ii=0;ii<msg_size;ii++) {
        Serial.write(msg_data[ii]);
      }
     return 0 ;    
    break;

    case 0xF0: // sysex
    break;    
    default:
      for (ii=0;ii<msg_size;ii++) {
        Serial.write(msg_data[ii]);
      }
      return 0;
    break;
  }
}
int get_message_size(unsigned int opcode) {
/*********************************************
** return the expected message size given the
** input opcode - return 0 if sysex (passes thru)
*********************************************/
  
  switch (opcode&0xF0) { // mask off channel # for channel-specific opcodes
    case 0x80:
    case 0x90:
    case 0xA0: // polyphonic aftertouch
    case 0xB0: // cc
    case 0xE0: // pitch
      return 3;
    break;
     
    case 0xC0: // program change
    case 0xD0: // channel aftertouch
      return 2;
     break;
     
     case 0xF0: // Sysex - 
     {
       if ((opcode&0xF8) == 0xF8) {
         // system real-time message, 1 byte, includes 0xF8, 0xFE
         return 1;
       }
       switch (opcode) {
         case 0xF0: // sysex
           return 0; // pass-thru information
         break;

         case 0xF1:
         case 0xF3:
           return 2;
         break;

         default:
           return 0; // pass-thru information
         break;
       }// switch opcode if 0xF0
     }//case 0xF0
     break;

     default:
       return 0; // pass-thru information
      break;
  }
}


void loop() {

    // simple loopback - works
if (0) {    
    while(1) {
      if (Serial.available()) {
        Serial.write(Serial.read());
        delay(1);
      }
    } // while
} //if

    // read in data and advance state machine if necessary
    if (Serial.available()) {
      inchar = Serial.read();
      // test: loopback
      // Serial.write(inchar);

      // ignore (or optionally pass-thru) "real-time" messages
      // they are allowed to interrupt the state machine
      if ((inchar&(unsigned char)0xF8) == (unsigned char)0xF8) {
        // Serial.write(inchar);
        return;
      }
      
      // otherwise automatically reset the state machine
      // if we receive bit 15 set
      if (inchar&0x80) {
        message_state = 0;
      }
      
      if (message_state == 0) { // searching for opcode
          if (inchar&0x80) {
            message_data[0] = inchar;
            message_size = get_message_size(inchar); // replace with function
            message_state = 1;
            // special case: single-byte command
            if (message_size == 1) {
              message_process(message_data,message_size);
              message_state=0;
              return;
            } else if (message_size==0) { // got an undefined message, so pass thru
              // just spit out the raw data
              Serial.write(inchar);
              message_state=0;
              return;
            } else {
              message_state = 1;
              return;
            } // if message_size
          } else { // state 0, non-command means RUNNING STATE - use last state
            // advance the state, assume that message_data[0] contains
            // the previously-used state and advance accordingly
            message_size = get_message_size(message_data[0]);
            message_data[1] = inchar;
            if (message_size <= 2) {
              message_process(message_data,message_size);
              message_state = 0;
            } else if (message_size == 0) { // undefined message, just pass thru
              Serial.write(inchar);
              message_state = 0;
              return;
            } else {
              message_state = 2;
              return;
            }
          } // if inchar&0x80
      } else { // message_state doesn't equal 0
          message_data[message_state] = inchar;
          message_state++;
          if (message_size == message_state) {
            message_process(message_data, message_size);
            message_state = 0;
            return;
          }
      }// if message_state
  }
 // just echo stuff - in to out
  
}// loop()

//  plays a MIDI note.  Doesn't check to see that
//  cmd is greater than 127, or that data values are  less than 127:
void noteOn( int pitch, int velocity, int channel) {
  if (channel <1) channel = 1; // make a warning!!
  Serial.write(0x90|((channel-1)&0xF));
  Serial.write(pitch);
  Serial.write(velocity);
}
void noteOff( int pitch, int velocity, int channel) {
  if (channel <1) channel = 1; // make a warning!!
  Serial.write(0x80|((channel-1)&0xF));
  Serial.write(pitch);
  Serial.write(velocity);
}



miniak_nrpn_struct miniak_nrpns[] = {

{ 0 , 0 , 1 }, // polyphony
{ 1 , 0 , 3 }, // unison voices
{ 2 , 0 , 100 }, // unison detune
{ 3 , 0 , 2 }, // portamento enabled
{ 4 , 0 , 3 }, // portamento type
{ 5 , 0 , 127 }, // portamento time
{ 6 , 0 , 1 }, // pitchwheel type
{ 7 , 0 , 100 }, // analog drift
{ 8 , 0 , 4 }, // oscillator sync type
{ 9 , 0 , 1000 }, // FM amount
{ 10 , 0 , 5 }, // FM type
{ 11 , 0 , 2 }, // oscillator 1 waveform
{ 12 , -100 , 100 }, // oscillator 1 wave shape
{ 13 , -3 , 3 }, // oscillator 1 octave
{ 14 , -7 , 7 }, // oscillator 1 transpose
{ 15 , -999 , 999 }, // oscillator 1 pitch
{ 16 , 0 , 12 }, // oscillator 1 pitchwheel range
{ 17 , 0 , 2 }, // oscillator 2 waveform
{ 18 , -100 , 100 }, // oscillator 2 wave shape
{ 19 , -3 , 3 }, // oscillator 2 octave
{ 20 , -7 , 7 }, // oscillator 2 transpose
{ 21 , -999 , 999 }, // oscillator 2 pitch
{ 22 , 0 , 12 }, // oscillator 2 pitchwheel range
{ 23 , 0 , 2 }, // oscillator 3 waveform
{ 24 , -100 , 100 }, // oscillator 3 wave shape
{ 25 , -3 , 3 }, // oscillator 3 octave
{ 26 , -7 , 7 }, // oscillator 3 transpose
{ 27 , -999 , 999 }, // oscillator 3 pitch
{ 28 , 0 , 12 }, // oscillator 3 pitchwheel range
{ 29 , 0 , 100 }, // Oscillator1 level
{ 30 , 0 , 100 }, // Oscillator2 level
{ 31 , 0 , 100 }, // Oscillator3 level
{ 32 , 0 , 100 }, // RingModulation level
{ 33 , 0 , 100 }, // Noise level
{ 34 , 0 , 100 }, // ExtIn level
{ 35 , -50 , 50 }, // Oscillator1 balance
{ 36 , -50 , 50 }, // Oscillator2 balance
{ 37 , -50 , 50 }, // Oscillator3 balance
{ 38 , -50 , 50 }, // RingModulation balance
{ 39 , -50 , 50 }, // Noise balance
{ 40 , -50 , 50 }, // ExtIn balance
{ 41 , 0 , 100 }, // series level???
{ 42 , 0 , 1 }, // noise type
{ 43 , 0 , 20 }, // filter 1 type
{ 44 , 0 , 1023 }, // filter 1 frequency
{ 45 , 0 , 100 }, // filter 1 resonance
{ 46 , -100 , 200 }, // filter 1 key track
{ 47 , -100 , 100 }, // filter 1 envelope amount
{ 48 , 0 , 1 }, // filter 2 relative to 1
{ 49 , 0 , 20 }, // filter 2 type
{ 50 , 0 , 1023 }, // filter 2 frequency absolute
{ 51 , 0 , 100 }, // filter 2 resonance
{ 52 , -100 , 200 }, // filter 2 key track
{ 53 , -100 , 100 }, // filter 2 envelope amount
{ 54 , 0 , 100 }, // filter 1 level
{ 55 , 0 , 100 }, // filter 2 level
{ 56 , 0 , 100 }, // pre-filter level
{ 57 , -100 , 100 }, // filter 1 pan
{ 58 , -100 , 100 }, // filter 2 pan
{ 59 , -100 , 100 }, // pre-filter pan
{ 60 , 0 , 6 }, // pre-filter source
{ 61 , 0 , 1 }, // filter 1 invert phase
{ 62 , 0 , 6 }, // drive type
{ 63 , 0 , 100 }, // drive level
{ 64 , 0 , 100 }, // program level
{ 65 , -50 , 50 }, // fx mix
{ 66 , 0 , 255 }, // envelope 1 attack time
{ 67 , 0 , 2 }, // envelope 1 attack slope
{ 68 , 0 , 255 }, // envelope 1 decay time
{ 69 , 0 , 2 }, // envelope 1 decay slope
{ 70 , 0 , 255 }, // envelope 1 sustain time
{ 71 , 0 , 100 }, // envelope 1 sustain level
{ 72 , 0 , 255 }, // envelope 1 release time
{ 73 , 0 , 2 }, // envelope 1 release slope
{ 74 , 0 , 100 }, // envelope 1 velocity to envelope
{ 75 , 0 , 1 }, // envelope 1 reset every note
{ 76 , 0 , 1 }, // envelope 1 free run
{ 77 , 0 , 3 }, // envelope 1 loop
{ 78 , 0 , 1 }, // envelope 1 sustain pedal
{ 79 , 0 , 255 }, // envelope 2 attack time
{ 80 , 0 , 2 }, // envelope 2 attack slope
{ 81 , 0 , 255 }, // envelope 2 decay time
{ 82 , 0 , 2 }, // envelope 2 decay slope
{ 83 , 0 , 255 }, // envelope 2 sustain time
{ 84 , -100 , 100 }, // envelope 2 sustain level
{ 85 , 0 , 255 }, // envelope 2 release time
{ 86 , 0 , 2 }, // envelope 2 release slope
{ 87 , 0 , 100 }, // envelope 2 velocity to envelope
{ 88 , 0 , 1 }, // envelope 2 reset every note
{ 89 , 0 , 1 }, // envelope 2 free run
{ 90 , 0 , 3 }, // envelope 2 loop
{ 91 , 0 , 1 }, // envelope 2 sustain pedal
{ 92 , 0 , 255 }, // envelope 3 attack time
{ 93 , 0 , 2 }, // envelope 3 attack slope
{ 94 , 0 , 255 }, // envelope 3 decay time
{ 95 , 0 , 2 }, // envelope 3 decay slope
{ 96 , 0 , 255 }, // envelope 3 sustain time
{ 97 , -100 , 100 }, // envelope 3 sustain level
{ 98 , 0 , 255 }, // envelope 3 release time
{ 99 , 0 , 2 }, // envelope 3 release slope
{ 100 , 0 , 100 }, // envelope 3 velocity to envelope
{ 101 , 0 , 1 }, // envelope 3 reset every note
{ 102 , 0 , 1 }, // envelope 3 free run
{ 103 , 0 , 3 }, // envelope 3 loop
{ 104 , 0 , 1 }, // envelope 3 sustain pedal
{ 105 , 0 , 1 }, // lfo 1 tempo sync
{ 106 , 0 , 1023 }, // lfo 1 rate (free)
{ 107 , 0 , 6 }, // lfo 1 reset
{ 108 , 0 , 100 }, // m1 slider influencelfo 1
{ 109 , 0 , 1 }, // lfo 2 tempo sync
{ 110 , 0 , 1023 }, // lfo 2 rate (free)
{ 111 , 0 , 6 }, // lfo 2 reset
{ 112 , 0 , 100 }, // m1 slider influencelfo 2
{ 113 , 0 , 1 }, // S&H tempo sync
{ 114 , 0 , 1023 }, // S&H rate(free)
{ 115 , 0 , 4 }, // S&H reset
{ 116 , 0 , 1 }, // S&H input
{ 117 , 0 , 100 }, // S&H smoothing
{ 118 , 0 , 111 }, // tracking input
{ 119 , 0 , 9 }, // tracking preset
{ 120 , 0 , 1 }, // tracking gridsize
{ 121 , -100 , 100 }, // tracking point -16
{ 122 , -100 , 100 }, // tracking point -15
{ 123 , -100 , 100 }, // tracking point -14
{ 124 , -100 , 100 }, // tracking point -13
{ 125 , -100 , 100 }, // tracking point -12
{ 126 , -100 , 100 }, // tracking point -11
{ 127 , -100 , 100 }, // tracking point -10
{ 128 , -100 , 100 }, // tracking point -9
{ 129 , -100 , 100 }, // tracking point -8
{ 130 , -100 , 100 }, // tracking point -7
{ 131 , -100 , 100 }, // tracking point -6
{ 132 , -100 , 100 }, // tracking point -5
{ 133 , -100 , 100 }, // tracking point -4
{ 134 , -100 , 100 }, // tracking point -3
{ 135 , -100 , 100 }, // tracking point -2
{ 136 , -100 , 100 }, // tracking point -1
{ 137 , -100 , 100 }, // tracking point 0
{ 138 , -100 , 100 }, // tracking point 1
{ 139 , -100 , 100 }, // tracking point 2
{ 140 , -100 , 100 }, // tracking point 3
{ 141 , -100 , 100 }, // tracking point 4
{ 142 , -100 , 100 }, // tracking point 5
{ 143 , -100 , 100 }, // tracking point 6
{ 144 , -100 , 100 }, // tracking point 7
{ 145 , -100 , 100 }, // tracking point 8
{ 146 , -100 , 100 }, // tracking point 9
{ 147 , -100 , 100 }, // tracking point 10
{ 148 , -100 , 100 }, // tracking point 11
{ 149 , -100 , 100 }, // tracking point 12
{ 150 , -100 , 100 }, // tracking point 13
{ 151 , -100 , 100 }, // tracking point 14
{ 152 , -100 , 100 }, // tracking point 15
{ 153 , -100 , 100 }, // tracking point 16
{ 154 , 0 , 8 }, // program category
{ 155 , 0 , 161 }, // Knob X
{ 156 , 0 , 161 }, // Knob Y
{ 157 , 0 , 161 }, // Knob Z
{ 158 , -400 , 400 }, // filter 2 frequency relative
{ 159 , 0 , 24 }, // lfo 1 rate (follow tempo)
{ 160 , 0 , 24 }, // lfo 2 rate (follow tempo)
{ 161 , 0 , 24 }, // S&H rate(synced)
{ 180 , 0 , 77 }, // patch cable 1 source
{ 181 , 0 , 77 }, // patch cable 1 destination
{ 182 , -1000 , 1000 }, // patch cable 1 level
{ 183 , -1000 , 1000 }, // patch cable 1 offset
{ 184 , 0 , 114 }, // patch cable 2 source
{ 185 , 0 , 77 }, // patch cable 2 destination
{ 186 , -1000 , 1000 }, // patch cable 2 level
{ 187 , -1000 , 1000 }, // patch cable 2 offset
{ 188 , 0 , 114 }, // patch cable 3 source
{ 189 , 0 , 77 }, // patch cable 3 destination
{ 190 , -1000 , 1000 }, // patch cable 3 level
{ 191 , -1000 , 1000 }, // patch cable 3 offset
{ 192 , 0 , 114 }, // patch cable 4 source
{ 193 , 0 , 77 }, // patch cable 4 destination
{ 194 , -1000 , 1000 }, // patch cable 4 level
{ 195 , -1000 , 1000 }, // patch cable 4 offset
{ 196 , 0 , 114 }, // patch cable 5 source
{ 197 , 0 , 77 }, // patch cable 5 destination
{ 198 , -1000 , 1000 }, // patch cable 5 level
{ 199 , -1000 , 1000 }, // patch cable 5 offset
{ 200 , 0 , 114 }, // patch cable 6 source
{ 201 , 0 , 77 }, // patch cable 6 destination
{ 202 , -1000 , 1000 }, // patch cable 6 level
{ 203 , -1000 , 1000 }, // patch cable 6 offset
{ 204 , 0 , 114 }, // patch cable 7 source
{ 205 , 0 , 77 }, // patch cable 7 destination
{ 206 , -1000 , 1000 }, // patch cable 7 level
{ 207 , -1000 , 1000 }, // patch cable 7 offset
{ 208 , 0 , 114 }, // patch cable 8 source
{ 209 , 0 , 77 }, // patch cable 8 destination
{ 210 , -1000 , 1000 }, // patch cable 8 level
{ 211 , -1000 , 1000 }, // patch cable 8 offset
{ 212 , 0 , 114 }, // patch cable 9 source
{ 213 , 0 , 77 }, // patch cable 9 destination
{ 214 , -1000 , 1000 }, // patch cable 9 level
{ 215 , -1000 , 1000 }, // patch cable 9 offset
{ 216 , 0 , 114 }, // patch cable 10 source
{ 217 , 0 , 77 }, // patch cable 10 destination
{ 218 , -1000 , 1000 }, // patch cable 10 level
{ 219 , -1000 , 1000 }, // patch cable 10 offset
{ 220 , 0 , 114 }, // patch cable 11 source
{ 221 , 0 , 77 }, // patch cable 11 destination
{ 222 , -1000 , 1000 }, // patch cable 11 level
{ 223 , -1000 , 1000 }, // patch cable 11 offset
{ 224 , 0 , 114 }, // patch cable 12 source
{ 225 , 0 , 77 }, // patch cable 12 destination
{ 226 , -1000 , 1000 }, // patch cable 12 level
{ 227 , -1000 , 1000 }, // patch cable 12 offset
{ 230 , -50 , 50 }, // fx balance
{ 231 , 0 , 6 }, // fx type 0
{ 232 , -128 , 127 }, // fx param a
{ 233 , -128 , 127 }, // fx param b
{ 234 , -128 , 127 }, // fx param c
{ 235 , -128 , 127 }, // fx param d
{ 236 , -128 , 127 }, // fx param e
{ 237 , -128 , 127 }, // fx param f
{ 238 , -128 , 127 }, // fx param g
{ 239 , 0 , 24 }, // fx param h lfo synced
{ 245 , 0 , 6 }, // fx2 type
{ 246 , -32768 , 32768 }, // fx2 param a
{ 247 , -32768 , 32768 }, // fx2 param b
{ 248 , -32768 , 32768 }, // fx2 param c
{ 249 , -32768 , 32768 }, // fx2 param d
{ 250 , 0 , 24 }, // fx param a synced
{ 512 , 0 , 31 }, // arpeggiator pattern
{ 513 , 0 , 6 }, // arpeggiator pattern multiplier
{ 514 , 0 , 14 }, // arpeggiator length
{ 515 , 0 , 4 }, // arpeggiator octave range
{ 516 , 0 , 2 }, // arpeggiator octave span
{ 517 , 0 , 5 }, // arpeggiator note order
{ 518 , 0 , 2 }, // arpeggiator enabled
{ 519 , 500 , 2500 }, // arpeggiator program tempo
{ 999 , 0 , 0 }, // end message
};