/******************************************************************************************************************************** ZETA RETICULI MIDI EQ CONTROLLER 2012 PATRICK GRIFFIN/REDBINARY.COM CURVE MODES: selected using program change PATCH 0: direct levels CC#HEX CC#DEC BANK/ADDR FUNCTION ====== ====== ========= ======================================== PATCH 0: direct levels 16 22 0/0 freq 1 17 23 0/1 freq 2 18 24 0/2 freq 3 19 25 0/3 freq 4 1A 26 0/4 freq 5 1B 27 0/5 freq 6 1C 28 1/0 freq 7 1D 29 1/1 freq 8 1E 30 1/2 freq 9 1F 31 1/3 freq 10 PATCH 1: pseudo-parametric 16 22 0/0 target center band 17 23 0/1 curve_max (height of target) 18 24 0/2 curve_min (depth of off-target) 19 25 0/3 curve_width (number of steps to roll-off from target to min) 20 26 0/4 curve_slope PATCH 2: note triggers note numbers 60-69 are bands, velocity is level USER MEMORIES: 0 - 9 written using cc 70-79 (&h46-4F), selected using program change 10-19 memory format: byte # descr info ====== ===== ====================================================== b00 type 0 = static/direct levels, 1 = dynamic/pseudo-parametric b01 (direct)level01 / (pseudo-parametric)initial target b02 (direct)level02 / (pseudo-parametric)target level b03 (direct)level03 / (pseudo-parametric)off-target level b04 (direct)level04 / (pseudo-parametric)width b05 (direct)level05 b06 (direct)level06 b07 (direct)level07 b08 (direct)level08 b09 (direct)level09 b10 (direct)level10 ********************************************************************************************************************************/ #include #include #include #include #include #include #define LED 11 #define BANK_ADDRESSES 6 #define BAND_COUNT 10 #define CURVE_MAX 255 #define CURVE_MIN 0 #define CURVE_WIDTH 10 //(BAND_COUNT * 2) #define CURVE_DEFAULT 127 #define PIN_INC 16 #define PIN_DEC 17 #define DEBOUNCE_DELAY 20 #define ADDR_CHANNEL 0 #define ADDR_MODE 1 #define MODE_MAX 1 #define BASE_USER_ADDR 9 #define USER_PATCH_SIZE 11 #define ADDR_MEM00 9 #define ADDR_MEM01 9 #define ADDR_MEM02 9 #define ADDR_MEM03 9 #define ADDR_MEM04 9 #define ADDR_MEM05 9 #define ADDR_MEM06 9 #define ADDR_MEM07 9 #define ADDR_MEM08 9 #define ADDR_MEM09 9 #define PROG_DIRECT 0 #define PROG_PARAMETRIC 1 //#define PROG_NOTE_DIRECT 2 //#define PROG_NOTE_PARAMETRIC 3 #define PROG_MEMORY0 10 #define PROG_MEMORY1 11 #define PROG_MEMORY2 12 #define PROG_MEMORY3 13 #define PROG_MEMORY4 14 #define PROG_MEMORY5 15 #define PROG_MEMORY6 16 #define PROG_MEMORY7 17 #define PROG_MEMORY8 18 #define PROG_MEMORY9 19 const int write_mem_cc[] = { 70, 71, 72, 73, 74, 75, 76, 77, 78, 79 }; // continuous controllers for writing user patches const int addr_mem[] = { 9, 20, 31, 42, 53, 64, 75, 86, 97, 108 }; // eeprom addresses for user patches #define CC_GAIN 6 // cc# for gain #define CC_VOLUME 7 // cc# for volume #define ADDR_GAIN 10 // spi address for gain #define ADDR_VOLUME 11 // spi address for volume int vol_value = 64; int gain_value = 64; LiquidCrystalFast lcd(9, 11, 10, 12, 13, 14, 15); // RS, RW, EN, D4, D5, D6, D7 const int pin_select_bank[] = { 0, 4 }; // which teensy pins select digital pot chips const int bank_count = 2; // # of pot chips int curve_mode;// = 1; int curve_width = 0; int curve_band = 0; boolean enable_note_ctrl = true; // whether or not notes can be used instead of or along with cc's boolean enable_as_adapter = false; // whether or not MIDI messages should be passed between standard & USB ports // initialize stored band values to default int band_value[] = { CURVE_DEFAULT, CURVE_DEFAULT, CURVE_DEFAULT, CURVE_DEFAULT, CURVE_DEFAULT, CURVE_DEFAULT, CURVE_DEFAULT, CURVE_DEFAULT, CURVE_DEFAULT, CURVE_DEFAULT }; // MIDI settings int midi_channel;// = 3; const int midi_controller_num[] = { 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }; // direct level mode controllers for freq bands // PARAMETRIC SETTINGS const int cc_affected_band = 22; // center freq const int cc_curve_max = 23; // const int cc_curve_min = 24; const int cc_curve_width = 25; int curve_max = CURVE_MAX; int curve_min = CURVE_MIN; // custom bar characters for LCD byte bar1[8] = { 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111 }; byte bar2[8] = { 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b00000 }; byte bar3[8] = { 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b00000, 0b00000 }; byte bar4[8] = { 0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b00000, 0b00000, 0b00000 }; byte bar5[8] = { 0b00000, 0b00000, 0b00000, 0b11111, 0b00000, 0b00000, 0b00000, 0b00000 }; byte bar6[8] = { 0b00000, 0b00000, 0b11111, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000 }; byte bar7[8] = { 0b00000, 0b11111, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000 }; byte bar8[8] = { 0b11111, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000 }; #define MODE_COL 11 // LCD column for mode description string #define MODE_ROW 1 // LCD row for same // LCD mode description strings const String mode_str[] = { "dir ", "par ", "usr" }; #define CH_COL 11 // LCD column for channel display string #define CH_ROW 0 // LCD row for same const String ch_str = "ch:"; // string to use on LCD for channel // set up debounce inputs Bounce bounce_inc = Bounce( PIN_INC, DEBOUNCE_DELAY ); boolean state_old_inc = false; Bounce bounce_dec = Bounce( PIN_DEC, DEBOUNCE_DELAY ); boolean state_old_dec = false; void setup() { // for debug Serial.begin(57600); for (int i = 0; i < BANK_ADDRESSES; i++) { pinMode(pin_select_bank[i], OUTPUT); } pinMode(PIN_INC, INPUT); pinMode(PIN_DEC, INPUT); midi_channel = EEPROM.read(ADDR_CHANNEL); if (midi_channel < 1 || midi_channel > 16) midi_channel = 8; curve_mode = EEPROM.read(ADDR_MODE); if (curve_mode < 0) curve_mode = 0; if (curve_mode > MODE_MAX) curve_mode = MODE_MAX; SPI.begin(); MIDI.begin(midi_channel); ResetAllBands(); lcd.begin(16, 2); // initialize the lcd // create char(0-7) array lcd.createChar (0, bar1); lcd.createChar (1, bar2); lcd.createChar (2, bar3); lcd.createChar (3, bar4); lcd.createChar (4, bar5); lcd.createChar (5, bar6); lcd.createChar (6, bar7); lcd.createChar (7, bar8); writeLCDmode(); writeLCDchannel(); writeCurve(); MIDI.turnThruOff(); } void loop() { int channel, d1, d2; int usb_channel, usb_d1, usb_d2; //HANDLE STANDARD MIDI PORT RECEIVED DATA if (MIDI.read()) { byte type = MIDI.getType(); d1 = MIDI.getData1(); d2 = MIDI.getData2(); channel = MIDI.getChannel(); byte * sysex_array = MIDI.getSysExArray(); if (channel == midi_channel || type == SystemExclusive) { switch (type) { case ControlChange: handleControlChange(d1, d2); break; case ProgramChange: handleProgramChange(d1); break; case SystemExclusive: handleSysEx(sysex_array, d1); break; case NoteOn: handleNoteOn(d1, d2); break; case NoteOff: handleNoteOff(d1); break; } } if (enable_as_adapter) { //pass MIDI to usbMIDI } } //HANDLE USB MIDI PORT RECEIVED DATA if (usbMIDI.read()) { byte usb_type = usbMIDI.getType(); usb_d1 = usbMIDI.getData1(); usb_d2 = usbMIDI.getData2(); usb_channel = usbMIDI.getChannel(); byte * usb_sysex_array = usbMIDI.getSysExArray(); if (usb_channel == midi_channel || usb_type == SystemExclusive) { switch (usb_type) { case ControlChange: handleControlChange(usb_d1, usb_d2); break; case ProgramChange: handleProgramChange(usb_d1); break; case SystemExclusive: handleSysEx(usb_sysex_array, usb_d1); break; case NoteOn: handleNoteOn(usb_d1, usb_d2); break; case NoteOff: handleNoteOff(usb_d1); break; } } if (enable_as_adapter) { //pass usbMIDI to MIDI } } // HANDLE BUTTONS // increment button bounce_inc.update(); boolean state_inc = bounce_inc.read(); if (state_old_inc != state_inc && state_inc == HIGH) { midi_channel++; if (midi_channel > 16) midi_channel = 1; MIDI.begin(midi_channel); //usbMIDI.begin(midi_channel); writeLCDchannel(); } state_old_inc = state_inc; // decrement button bounce_dec.update(); boolean state_dec = bounce_dec.read(); if (state_old_dec != state_dec && state_dec == HIGH) { midi_channel--; if (midi_channel < 1) midi_channel = 16; MIDI.begin(midi_channel); //usbMIDI.begin(midi_channel); writeLCDchannel(); } state_old_dec = state_dec; } void handleSysEx() { } void digitalPotWrite(int address, int value) { // determine which bank address is in int bank = 0; if (address > (BANK_ADDRESSES - 1)) { address = address - BANK_ADDRESSES; bank = 1; } // take the SS pin for selected bank low to select the chip: digitalWrite(pin_select_bank[bank], LOW); // send in the address and value via SPI: SPI.transfer(address); SPI.transfer(value); // take the SS pin high to de-select the chip: digitalWrite(pin_select_bank[bank], HIGH); } void writeCurve() { for (int band = 0; band < BAND_COUNT; band++) { digitalPotWrite(band, band_value[band]); writeLCDband(band, band_value[band]); } } void writeVolume(int new_volume) { digitalPotWrite(ADDR_VOLUME, new_volume); } void writeGain(int new_gain) { digitalPotWrite(ADDR_GAIN, new_gain); } void writeLCDband(int address, int value) { int row = 1; int adj_val = value; if (value > 127) { row = 0; value -= 127; lcd.setCursor( address, 1 ); lcd.print(" "); } else { lcd.setCursor( address, 0 ); lcd.print(" "); } adj_val = (value / 15) + 1; if (adj_val < 0) adj_val = 0; if (adj_val > 7) adj_val = 7; lcd.setCursor( address, row ); lcd.print(char(adj_val)); } void writeLCDmode() { EEPROM.write(ADDR_MODE, curve_mode); lcd.setCursor( MODE_COL, MODE_ROW ); lcd.print(mode_str[curve_mode]); } void writeLCDchannel() { EEPROM.write(ADDR_CHANNEL, midi_channel); lcd.setCursor( CH_COL, CH_ROW ); lcd.print(" "); lcd.setCursor( CH_COL, CH_ROW ); lcd.print(ch_str + String(midi_channel)); } void handleControlChange(int control, int value) { if (control >= write_mem_cc[0] && control <= write_mem_cc[9] && value > 63) { // handle one of the on/off cc's wanting to write a user patch int mem_index = control - write_mem_cc[0]; WriteMemorySet(mem_index + 2); } else if (control == CC_VOLUME) { vol_value = value * 2; writeVolume(value); } else if (control == CC_GAIN) { gain_value = value * 2; writeGain(value); } else { if (curve_mode == PROG_DIRECT) { // DIRECT LEVELS if (control >= midi_controller_num[0] && control <= midi_controller_num[BAND_COUNT - 1]) { // initial boundary check for cc# being valid for a freq band int band_index = control - midi_controller_num[0]; if (band_index > -1 && band_index < BAND_COUNT) { // redundant boundary check int scaled_value = value * 2; if (scaled_value < curve_min) scaled_value = curve_min; if (scaled_value > curve_max) scaled_value = curve_max; band_value[band_index] = scaled_value; digitalPotWrite(band_index, band_value[band_index]); writeLCDband(band_index, band_value[band_index]); } } } else if (curve_mode == PROG_PARAMETRIC) { // PSEUDO-PARAMETRIC if (control == cc_affected_band) { // identify affected band curve_band = 0; int divisor = 127 / BAND_COUNT; if (value > 0 && divisor > 0) { curve_band = value / divisor; } if (curve_band >= BAND_COUNT) curve_band = BAND_COUNT - 1; CalcParametricBands(); writeCurve(); } else if (control == cc_curve_min) { curve_min = value * 2; CalcParametricBands(); writeCurve(); } else if (control == cc_curve_max) { curve_max = value * 2; CalcParametricBands(); writeCurve(); } else if (control == cc_curve_width) { int divisor = 127 / (BAND_COUNT * 2); if (value > 0 && divisor > 0) { curve_width = value / divisor; } if (curve_width >= (BAND_COUNT * 2)) curve_width = (BAND_COUNT * 2) - 1; CalcParametricBands(); writeCurve(); } } } } void handleProgramChange(byte program) { if (program <= PROG_MEMORY9) { if (program == PROG_DIRECT) { curve_mode = program; curve_min = CURVE_MIN; curve_max = CURVE_MAX; ResetAllBands(); } else if (program == PROG_PARAMETRIC) { curve_mode = program; ResetAllBands(); } else if (program >= PROG_MEMORY0) { ReadMemorySet(program); } writeCurve(); writeLCDmode(); } } // SYSEX DATA CAN BE RECEIVED ON EITHER STANDARD OR USB PORT BUT CAN ONLY BE TRANSMITTED OVER USB void handleSysEx(byte * rcvd_array, int array_len) { /* [0] Begin - 0xF0 (240) [1] Manufacturer - 0x60 (96) [2] Model - 0x0D (13) [3] Device ID - 0x00 [not yet implemented] [4] Command - 0x10 (16) (store user patch) 0x11 (17) (request patch) [5] Patch num - 1 byte [6] End - 0xf7 (247) */ if (array_len == 7) { // correct size for patch data request if (rcvd_array[0] == 0xf0 && rcvd_array[6] == 0xf7) { //correctly framed if (rcvd_array[1] == 0x60 && rcvd_array[2] == 0x0D) { // correct OEM && model if (rcvd_array[4] == 0x11) { //request patch command sysExTransmitPatch((int) rcvd_array[5]); } else if (rcvd_array[4] == 0x10) { //store user patch command WriteMemorySet(rcvd_array[5]); } } } } } void handleNoteOn(int note, int vel) { if (enable_note_ctrl == true) { int val = (note - 60) * BAND_COUNT; handleControlChange(cc_affected_band, val); // map note on to controller } } void handleNoteOff(int note) { /*if (curve_mode == PROG_NOTE_DIRECT && note >= 60 && note <= 69) { int target_band = note - 60; //Serial.println( String("NoteOff: ") + note ); band_value[target_band] = 0; digitalPotWrite(target_band, band_value[target_band]); writeLCDband(target_band, band_value[target_band]); }*/ } void ReadMemorySet(byte patch) { int patch_index = patch - 10; int base_addr = addr_mem[patch_index]; curve_mode = EEPROM.read(base_addr); if (curve_mode == PROG_DIRECT) { for (int band_i = 0; band_i < BAND_COUNT; band_i++) { band_value[band_i] = EEPROM.read(base_addr + 1 + band_i); } } else if (curve_mode == PROG_PARAMETRIC) { curve_band = EEPROM.read(base_addr + 1); curve_max = EEPROM.read(base_addr + 2); curve_min = EEPROM.read(base_addr + 3); curve_width = EEPROM.read(base_addr + 4); } } void WriteMemorySet(byte index) { int base_addr = addr_mem[index]; EEPROM.write(base_addr, curve_mode); if (curve_mode == PROG_DIRECT) { for (int band_i = 0; band_i < BAND_COUNT; band_i++) { EEPROM.write(base_addr + 1 + band_i, band_value[band_i]); } } else if (curve_mode == PROG_PARAMETRIC) { EEPROM.write(base_addr + 1, curve_band); EEPROM.write(base_addr + 2, curve_max); EEPROM.write(base_addr + 3, curve_min); EEPROM.write(base_addr + 4, curve_width); } } void ResetAllBands() { for (int band_reset = 0; band_reset < BAND_COUNT; band_reset++) { band_value[band_reset] = CURVE_DEFAULT; } } void CalcParametricBands() { int band_lo = 0; int band_hi = 0; int inc = 0; if (curve_width > 0) { band_lo = curve_band - curve_width; if (band_lo < 0) band_lo = 0; band_hi = curve_band + curve_width; if (band_hi > (BAND_COUNT - 1)) band_hi = BAND_COUNT - 1; inc = (curve_max - curve_min) / curve_width; } // write affected to max, others to min for (int i = 0; i < BAND_COUNT; i++) { if (i == curve_band) { band_value[i] = curve_max; } else if (curve_width > 0 && i >= band_lo && i <= band_hi) { // inside affected range int offset = abs(curve_band - i); band_value[i] = curve_max - (inc * offset); } else { band_value[i] = curve_min; } } } void sysExTransmitPatch(int pPatch) { int patch_index = pPatch - 10; int base_addr = patch_index * USER_PATCH_SIZE + BASE_USER_ADDR; int pCurve_mode = EEPROM.read(base_addr); byte d[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (pCurve_mode == PROG_DIRECT) { for (int band_i = 0; band_i < BAND_COUNT; band_i++) { band_value[band_i] = EEPROM.read(base_addr + 1 + band_i); d[band_i] = (byte) band_value[band_i]; } } else if (pCurve_mode == PROG_PARAMETRIC) { int pCurve_band = EEPROM.read(base_addr + 1); int pCurve_max = EEPROM.read(base_addr + 2); int pCurve_min = EEPROM.read(base_addr + 3); int pCurve_width = EEPROM.read(base_addr + 4); d[0] = pCurve_band; d[1] = pCurve_max; d[2] = pCurve_min; d[3] = pCurve_width; } /* Begin - 0xF0 Manufacturer - 0x60 (96) Model - 0x0D (13) Device ID - 0x00 [not yet implemented] Command - 0x12 (transmit patch) Patch num - 1 byte Patch Data: curve mode - 1 byte curve data - 10 bytes End - 0xf7 */ byte xmit_string[] = { 0xf0, 0x60, 0x0d, 0x00, 0x12, (byte) patch_index, (byte) pCurve_mode, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], 0xf7 }; delay(250); usbMIDI.sendSysEx( 18, xmit_string); } void sysExReceivePatch(int pPatch) { //TODO }