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