//************LIBRARIES USED**************
// include the Bounce library for 'de-bouncing' switches -- removing electrical chatter as contacts settle
#include <Bounce.h>
#include <EEPROM.h>
// usbMIDI.h library is added automatically when code is compiled as a MIDI device
// ********************CONSTANT VALUES**********************************
// **<user configurable content> // configurable parameters as constants
//********** MIDI DEFINITIONS
int channel = 1; // MIDI channel (variable assignable in sysEx EEPROM system)
const int MODE_COUNT = 3; // number of rows of banks
const int D_PINS = 5; // number of Digital PINS in main group (excl. tap/bank)
//CC configuration matrix!! - not a const as it's configured by SysEx
int MIDI_CC_NUMS[MODE_COUNT][D_PINS] = { //rows are banks up to MODE_COUNT
{60,61,62,63,64},
{65,66,67,68,69},
{70,71,72,73,74}
}; // these are the D1 values sent for each mode|pin pair
int TAP_CC = 85; // D1 value for TAP down
const int ON_Value = 127; // note-one velocity sent from buttons (should be 65 to 127)
const int OFF_Value = 0; // note-one velocity sent from buttons (should be 65 to 127)
//******************************** PHYSICAL DEFINITIONS ***************
const int BOUNCE_TIME = 30; // 5 ms is usually sufficient
const int modeThreshold = 800; // how long to hold before mode changes
const int flashOnTime = 150; // how long flashed LED is on
const int flashOffTime = 250; // how long flashed LED is off
const bool LED_ON = HIGH; // LOW for active LOW wiring
const bool LED_OFF = LOW; // HIGH for active LOW wiring
const int onClocks = 4; // number of clock messages with LED on for tempo pulse
const int waitflash = 2; // number of dark flash cycles after bank change before tempo pulse resumes
//** </user configurable content> // don't change below here without knowing how it works!
//********** PIN DEFINITIONS (edit only if rewired or home built with different connections)
const int DIGITAL_PINS[D_PINS] = {0,1,5,3,4}; // pins to switches
const int RED_LED_PINS[D_PINS] = {6,19,8,10,17};
const int GREEN_LED_PINS[D_PINS] = {7,20,9,16,18};
const int MODE_TAP_PIN = 1; // momentary switch to set tempo and select bank
const int MODE_TAP_LED_PIN = 13; // pin to LED for tempo/bank indicator
//******VARIABLES***********
// a data array to remember the current state of each switch
boolean state[MODE_COUNT][D_PINS]; // !!!!! state of the main switches; used to select D2 as 'off' or 'on' value
elapsedMillis modeTimer,flashTimer; // timers for mode select hold and for flash control
int bank = 0 ; // index of selected bank to select CC number set
int flashcount= (-1*waitflash); // default is effective sequence stop of flash counter
boolean shiftUp; // keeps track of whether the mode change needs to be handled (true) or was (false)
boolean modeLED; // keeps track of whether LED is on without testing it..
int ClockCount; // for tempo tracking
boolean tempoFlashOn = true;// tempo flash defaults to on
//************INITIALIZE LIBRARY OBJECTS**************
// initialize the bounce objects
Bounce digital[] = {
Bounce(DIGITAL_PINS[0],BOUNCE_TIME),
Bounce(DIGITAL_PINS[1], BOUNCE_TIME),
Bounce(DIGITAL_PINS[2], BOUNCE_TIME),
Bounce(DIGITAL_PINS[3], BOUNCE_TIME),
Bounce(DIGITAL_PINS[4], BOUNCE_TIME)
};
Bounce modeTap = Bounce(MODE_TAP_PIN, BOUNCE_TIME);
//************SETUP**************
void setup() {
//'set a handle for returning MIDI messages'
usbMIDI.setHandleControlChange(OnControlChange); // used for DAW feedback of settings
usbMIDI.setHandleSongPosition(onSongPosition); // read expicit beat for pulse flash
usbMIDI.setHandleClock(onClock); // pulse flash every 24 clock
usbMIDI.setHandleStart(onStart); // reset clock
usbMIDI.setHandleSystemExclusive(onSysEx); // used to set presets into EEPROM
//'loop to configure input pins and internal pullup resisters for digital section'
for (int i=0;i<D_PINS;i++){
pinMode(DIGITAL_PINS[i], INPUT_PULLUP);
pinMode(RED_LED_PINS[i], OUTPUT);
pinMode(GREEN_LED_PINS[i], OUTPUT);
}
pinMode(MODE_TAP_PIN, INPUT_PULLUP);
pinMode(MODE_TAP_LED_PIN, OUTPUT);
digitalWrite(MODE_TAP_LED_PIN, LED_OFF);
for (int i=0;i<D_PINS;i++){
digitalWrite(GREEN_LED_PINS[i], LED_OFF); // - GREEN OFF
digitalWrite(RED_LED_PINS[i], LED_ON); // - RED ON
}
if (EEPROM.read(MODE_COUNT*D_PINS+2)>0 && EEPROM.read(MODE_COUNT*D_PINS+2)<17){ // EEPROM for channel is non-zer0 or not a viable MIDI channel
for (int i = 0; i <MODE_COUNT*D_PINS+2; i++) { // read EEPROM for sysEx data
if (i<MODE_COUNT*D_PINS){
MIDI_CC_NUMS[i/5][i] = EEPROM.read(i);
}else if(i==MODE_COUNT*D_PINS) { // index after matrix
TAP_CC = EEPROM.read(i);
Serial.println(TAP_CC);
}else { // assume last byte
channel = EEPROM.read(i);
}
delay(5); // give time for EEPROM read??
}
}
flashcount = 5;
}
//************LOOP**************
void loop() {
getMain(); // process the main switches
getModeTap(); // process the mode/tap switch
flasher(); // flasher called to handle flash queue events and end clock pulse suppression
while (usbMIDI.read()) {
// controllers must call .read() to keep the queue clear even if they are not responding to MIDI
// but we are using handles for real time messages and CCs for setting LEDs
}
}
//************DIGITAL SECTION**************
void getMain(){
for (int i=0;i<D_PINS;i++){
digital[i].update();
if (digital[i].fallingEdge() || digital[i].risingEdge()) { // to use toggle hardware switch as a trigger
if (state[bank][i]) { // if ON send OFF and vice versa
usbMIDI.sendControlChange(MIDI_CC_NUMS[bank][i], OFF_Value, channel);
digitalWrite(RED_LED_PINS[i], LED_ON);
digitalWrite(GREEN_LED_PINS[i], LED_OFF);
}else{
usbMIDI.sendControlChange(MIDI_CC_NUMS[bank][i], ON_Value, channel);
digitalWrite(RED_LED_PINS[i], LED_OFF);
digitalWrite(GREEN_LED_PINS[i], LED_ON);
}
state[bank][i] = !state[bank][i] ; // toggle state so it match new settings
}
}
}
//************MODE/TAP SECTION**************
void getModeTap(){
modeTap.update();
if (modeTap.fallingEdge()) { // falling edge could be a tap or a bank-select but tap is sent either way!
usbMIDI.sendControlChange(TAP_CC, ON_Value, channel); // currently off is not sent but can be added to risingEdge
modeTimer = 0; // reset timer to watch for mode change if held
shiftUp = true; // mark as potentially indicating a bank select increment
tempoFlashOn = false; // start suppressing the tempo pulse
flashcount = 0; // is this error or magic?
Serial.println("suspend tempo on tap");
}
if (modeTap.risingEdge()){
shiftUp = false; // ...mark that no shift up pending
if (modeTimer<modeThreshold){
tempoFlashOn = true; // no mode change so resume tempo pulse
Serial.println("resume tempo no mode change");
}
}
if (modeTimer>modeThreshold && shiftUp) { // shiftUp implies risingEdge has not been received so button held
shiftUp = false; // so shift no longer pending
bank = (bank+1)%MODE_COUNT; // increment bank and rollover at MODE_COUNT
for (int i = 0; i < D_PINS ; i++){
digitalWrite(GREEN_LED_PINS[i], LED_ON ? state[bank][i]:!state[bank][i]); // magic to allow active HIGH or LOW
digitalWrite(RED_LED_PINS[i], LED_ON ? !state[bank][i]:state[bank][i]);
}
// set counter of flashes 'owed' -- count them down after main part
flashcount = bank + 1; // flash count is 1 plus bank index
flashTimer = 0 ; // is this needed?
}
}
//************BANK FLASH SECTION**************
void flasher(){
if (flashcount>= -1*waitflash){
if (flashcount> 0){
if (flashTimer>(flashOnTime+flashOffTime)){
flashcount-- ;// decrement flashcount
flashTimer = 0;
if (modeLED){
modeLED = false;
digitalWrite(MODE_TAP_LED_PIN, LED_OFF);
}
}else{
if (modeLED == false && flashTimer>flashOnTime){
modeLED = true;
digitalWrite(MODE_TAP_LED_PIN, LED_ON);
}
}
}else{
if (flashTimer>(flashOnTime+flashOffTime)){
flashcount-- ;// decrement flashcount
flashTimer = 0;
}
}
}else{
if (!tempoFlashOn){
tempoFlashOn = true;
Serial.println("resume tempo after bank select");
}
}
}
//************MIDI HANDLERS SECTION**************
void OnControlChange(byte rcvChannel, byte controller, byte value) {
if (rcvChannel == channel){ // ...only if on correct channel (could support a different feedback channel!)
for (int i = 0; i < D_PINS ; i++){ // cycle through CCs...
if (MIDI_CC_NUMS[bank][i] == controller) { // ...and process if match
if (value >= 64) {
digitalWrite(GREEN_LED_PINS[i], LED_ON);
digitalWrite(RED_LED_PINS[i], LED_OFF); //'receiving >=64 turns green on and red off'
state[bank][i] = true; // store the current state
}else{
digitalWrite(GREEN_LED_PINS[i], LED_OFF);
digitalWrite(RED_LED_PINS[i], LED_ON); //'receiving <64 turns red on and green off'
state[bank][i] = false; // store the current state
} // 'if not the controller for ‘i’ then skip this loop'
}
}
}
}
void onClock() { // flash the tempo LED every 24 clocks
if (tempoFlashOn){
if (ClockCount<=onClocks){ // will flash on as counter starts up from zero
digitalWrite(MODE_TAP_LED_PIN, LED_ON);
}else{ // will turn off after the requireed number of clocks (onClocks)
digitalWrite(MODE_TAP_LED_PIN, LED_OFF);
}
}
ClockCount = (ClockCount+1)%24; // increment and reset at 24
}
void onStart(){
ClockCount = 0; // song restarted so clock counter reset
}
void onSongPosition(uint16_t semiQ){
ClockCount= (semiQ*6)%24 ; // set clock to explicit position from song pointer
}
void onSysEx(byte *sysExBytes, unsigned int length){
if (sysExBytes[0] == 0xF0 // sysEx start
&& sysExBytes[MODE_COUNT*D_PINS+9] == 0xF7 // sysEx end
&& sysExBytes[1] == 0x7D // 7D is private use (non-commercial)
&& sysExBytes[2] == 0x41 // 4-byte 'key' - not really needed if via USB but why not!
&& sysExBytes[3] == 0x43
&& sysExBytes[4] == 0x48
&& sysExBytes[5] == 0x30){ // read and compare static bytes to ensure valid msg
for (int i = 0; i < MODE_COUNT*D_PINS+2; i++) {
if (i<MODE_COUNT*D_PINS){
MIDI_CC_NUMS[i/5][i] = sysExBytes[i+6];
Serial.println(MIDI_CC_NUMS[i/5][i]);
}else if(i==MODE_COUNT*D_PINS) {
TAP_CC = sysExBytes[i+6];
Serial.println(TAP_CC);
}else { // assume last byte
channel = sysExBytes[i+6];
Serial.println(channel);
}
}
byte data[] = { 0xF0, 0x7D, 0xF7 }; // ACK msg - should be safe for any device even if listening for 7D
usbMIDI.sendSysEx(3,data,true); // acknowledge msg - should be safe for any device even if listening for 7D
}
}