changing to MIDI mixed messages rather than arrays. ?Method?

Status
Not open for further replies.

Aussie_CrocHunter

Well-known member
I've got a pretty awesome piece of code here, mainly thanks to Oddson - it's a 6-switch MIDI controller with 5 main switches (latching) and 1 SPST momentary switch. Holding the momentary switch down change to the next bank and LED states for the red/green LED rings are saved for each switch in each bank (so the LEDs change when the bank changes). There is one expression pedal and a TRS MIDI send using serial MIDI.

I have a simpler but also more specific case I want to change the code for but I'm not really sure how to disentagle myself from the arrays.

I want to mix the 5 switches between PC and CC commands. So switch 1 (first bank) might be CC 69 with val 8, but then Switch 2 (first bank still) is CC 69 with val 3, then switch 8 (bank 2) is a PC 21 message.

do i just have to write out line by line which each switch should do then give it a "codename" in the array? Then how would I keep the LED state tracking?

Another thing to throw in there is that I'd like to use "#define" or "const int" up the top so that people can edit the code easily to change switch assignments (PC, CC, Values, midi channel etc. )

What do you think my method for re-writing should be?
btw, I'll need as much demonstration/help as possible!

Code:
//************LIBRARIES USED**************
// 'include the Bounce library for 'de-bouncing' switches -- removing electrical chatter as contacts settle'
#include <MIDI.h>
#include <Bounce.h> 
// include the ResponsiveAnalogRead library
#include <ResponsiveAnalogRead.h>
//'usbMIDI.h library is added automatically when code is compiled as a MIDI device'

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

// ******CONSTANT VALUES******** 

//********** PIN DEFINITIONS
const int NUM_PEDALS = 1; // number of Analog PINS
const int D_PINS = 5; // number of Digital PINS in main group (excl tap/bank)
const int ANALOG_PINS[NUM_PEDALS] = {A10};
const int DIGITAL_PINS[D_PINS] = {0,1,2,3,4}; // pins to switchs 
const int RED_LED_PINS[D_PINS] = {6,22,10,17,19};
const int GREEN_LED_PINS[D_PINS] = {24,9,16,18,20};
const int MODE_TAP_PIN = 5;  // momentary switch to set tempo and select bank
const int MODE_TAP_LED_PIN = 15; // pin to LED for tempo/bank indicator

       //********** MIDI DEFINITIONS*************
// MODIFY THIS SECTION TO CUSTOMISE YOUR MIDISTOMP SIX 

const int firstchannel = 1; // MIDI channel for USB MIDI
const int secondchannel = 5; // MIDI channel for TRS MIDI
const int CCID[NUM_PEDALS] = {1}; //Expression Pedal CCs - HX Stomp Expression pedal emulation uses CCs 1, 2, and 3 for emulating pedals 1,2,3
const int MODE_COUNT = 3; // number of rows of banks
//CC configuration matrix!!
const int MIDI_CC_NUMS[MODE_COUNT][D_PINS] = { //rows are banks up to MODE_COUNT
    {80,81,82,83,84},
    {85,86,87,88,89},
    {90,91,92,93,94}
};
const int TAP_CC = 64; //HX Stomp's default Tap Tempo CC is 64
const int ON_Value = 127; // 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 = LOW; // LOW for active LOW wiring
const bool LED_OFF = HIGH; // HIGH for active LOW wiring
const int onClocks = 4; // number of clock messages with LED on for tempo flash
const int waitflash = 2; // number of dark flash cylces after bank change before tempo flash resumes

//******VARIABLES***********
// a data array to remember the current state of each switch
boolean state[MODE_COUNT][D_PINS];
elapsedMillis modeTimer,flashTimer;
boolean modeSelectActive = false;
int bank = 0 ; 
int flashcount= (-1*waitflash); // default is effective sequence stop of flash counter
int shiftUp; // keeps track of whether the mode change needs to be handled (true) or was (false)
int modeLED; // keeps track of whether LED is on without testing it..
int ClockCount; // for tempo tracking
boolean tempoFlashOn = true;// tempo flash defaults to on

// a data array and a lagged copy to tell when MIDI changes are required
byte data[NUM_PEDALS];
byte dataLag[NUM_PEDALS];

//************INITIALIZE LIBRARY OBJECTS**************

// initialize the ReponsiveAnalogRead objects
ResponsiveAnalogRead analog[NUM_PEDALS] {
  ResponsiveAnalogRead(ANALOG_PINS[0], true),
  //ResponsiveAnalogRead(ANALOG_PINS[1], true),
};

// 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() {
MIDI.begin(MIDI_CHANNEL_OMNI);
  
    // init the thresholds for the ResponsiveAnalogeRead objects
  for (int n = 0; n < NUM_PEDALS; n++) {
    analog[n].setActivityThreshold(20);
  }
  
  //'set a handle for returning control change messages'
  usbMIDI.setHandleControlChange(OnControlChange);
  usbMIDI.setHandleSongPosition(onSongPosition);
  usbMIDI.setHandleClock(onClock);
  usbMIDI.setHandleStart(onStart);
  //'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
  }
}

//************LOOP**************
void loop() {
  getAnalogData();
  getMain();
  getModeTap();
  flasher();
  while (usbMIDI.read()) {
    //' controllers must call .read() to keep the queue clear even if they are not responding to MIDI'
  }
  while (MIDI.read()){

  }

}

//************DIGITAL SECTION**************
void getMain(){
  for (int i=0;i<D_PINS;i++){
    digital[i].update();
    if (digital[i].fallingEdge() || digital[i].risingEdge()) {
      if (state[bank][i]) {
        usbMIDI.sendControlChange(MIDI_CC_NUMS[bank][i], 0, firstchannel);
        MIDI.sendControlChange(MIDI_CC_NUMS[bank][i], 0, secondchannel);  
        digitalWrite(RED_LED_PINS[i], LED_ON);
        digitalWrite(GREEN_LED_PINS[i], LED_OFF);
      }else{
        usbMIDI.sendControlChange(MIDI_CC_NUMS[bank][i], ON_Value, firstchannel);
        MIDI.sendControlChange(MIDI_CC_NUMS[bank][i], ON_Value, secondchannel);
        digitalWrite(RED_LED_PINS[i], LED_OFF);
        digitalWrite(GREEN_LED_PINS[i], LED_ON);
      }
      state[bank][i] = !state[bank][i] ;
    }
  }
}

//************ANALOG SECTION**************

void getAnalogData() {
  int valX;
  int adcValue = 0;
  for (int i = 0; i < NUM_PEDALS; i++) {
    // update the ResponsiveAnalogRead object every loop
    analog[i].update();
    // if the repsonsive value has change, print out 'changed'
    if (analog[i].hasChanged()) {
      
      adcValue = analog[i].getValue();
      valX = (map(adcValue, 60, 890, 0, 127)); //Change the upper first two numbers - 
      //upper and lower - to adjust the midi range of your expression pedal
      valX = constrain(valX, 0, 127); //map all numbers to 0-127 to avoid negative numbers
      data[i] = valX;
      
      if (data[i] != dataLag[i]) {
        dataLag[i] = data[i];
        usbMIDI.sendControlChange(CCID[i], data[i], firstchannel);
        MIDI.sendControlChange(CCID[i], data[i], secondchannel);
      }
    }
  }
}

//************MODE/TAP SECTION**************
void getModeTap(){
  modeTap.update();
  if (modeTap.fallingEdge()) {
    usbMIDI.sendControlChange(TAP_CC, ON_Value, firstchannel);
    MIDI.sendControlChange(TAP_CC, ON_Value, secondchannel);  
    modeTimer = 0;
    shiftUp = true;
    tempoFlashOn = false; 
    flashcount = 0; // is this error or magic?
    Serial.println("suspend tempo on tap");
  }    
  if (modeTap.risingEdge()){
    shiftUp = false;
    if (modeTimer<modeThreshold){
      tempoFlashOn = true; 
    Serial.println("resume tempo no mode change");
    }
  }
  if (modeTimer>modeThreshold && shiftUp) {
    shiftUp = false;
    bank++;
    bank = bank%MODE_COUNT;
    for (int i = 0; i < D_PINS ; i++){
      digitalWrite(GREEN_LED_PINS[i], !state[bank][i]);
      digitalWrite(RED_LED_PINS[i], state[bank][i]); 
    }
    // set counter of flashes 'owed' -- count them down after main part
    flashcount = bank + 1;
    flashTimer = 0 ; // is this needed?
  }
}

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(flashcount);
      Serial.println("resume tempo after bank select");
    }
  }
}

void OnControlChange(byte rcvChannel, byte controller, byte value) {
  if (rcvChannel == firstchannel){
    for (int i = 0; i < D_PINS ; i++){
      if (MIDI_CC_NUMS[bank][i] == controller) {
        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;
        }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;
        } // 'if not the controller for i then skip this loop'    
      }
    }
  }
}

void onClock() {
  if (tempoFlashOn){
    if (ClockCount<=onClocks){
      digitalWrite(MODE_TAP_LED_PIN, LED_ON);
    }else{
      digitalWrite(MODE_TAP_LED_PIN, LED_OFF);
    }
  }
  ClockCount = (ClockCount+1)%24;
}

void onStart(){
  ClockCount = 0;
}

void onSongPosition(uint16_t semiQ){
  ClockCount= semiQ*6 ; 
}
 
I don't have any hardware setup to test this myself so I won't post full code, but if I was to do this I would just add another array that would store the type of message I wanted the button to send then just use the usbMIDI.send() command instead of separate calls to either usbMIDI.sendControlChange() or usbMIDI.sendProgramChange.

Here is what sendControlChange() and sendProgramChange() is doing so you can get an idea of how to use the send() command by itself:
Code:
void sendControlChange(uint8_t control, uint8_t value, uint8_t channel, uint8_t cable=0) __attribute__((always_inline)) {
		send(0xB0, control, value, channel, cable);
	}
void sendProgramChange(uint8_t program, uint8_t channel, uint8_t cable=0) __attribute__((always_inline)) {
		send(0xC0, program, 0, channel, cable);
	}
 
I don't have any hardware setup to test this myself so I won't post full code, but if I was to do this I would just add another array that would store the type of message I wanted the button to send then just use the usbMIDI.send() command instead of separate calls to either usbMIDI.sendControlChange() or usbMIDI.sendProgramChange.

Here is what sendControlChange() and sendProgramChange() is doing so you can get an idea of how to use the send() command by itself:
Code:
void sendControlChange(uint8_t control, uint8_t value, uint8_t channel, uint8_t cable=0) __attribute__((always_inline)) {
		send(0xB0, control, value, channel, cable);
	}
void sendProgramChange(uint8_t program, uint8_t channel, uint8_t cable=0) __attribute__((always_inline)) {
		send(0xC0, program, 0, channel, cable);
	}

Thanks :) I think I understand a bit more now - I might need a little nudge in the direction of what goes where though. Perhaps one of the things that's tripping me up is the fact that PC have only a number and channel and CC have value as well. I don't think I quite understand how I would write those arrays.
 
Until you want to try custom data structures the best way is as vjmuzik says, another array to track what type of message.


Integers act as stand-ins for types of messages (you can use the status 'byte' as a stand-in and that way be able to extend potentially to other messages like noteOn etc.).

The new array would be just like the one for CC values (message byte 2, data byte 1 a.k.a. d1) but since that one isn't just CC anymore it should be renamed:

Code:
const int STATUS[MODE_COUNT][D_PINS] = { //status nibble padded to byte, E.g.: 0xB0 = CC)
    {0xB0,0xB0,0xB0,0xB0,0xB0},
    {0xC0,0xC0,0xB0,0xB0,0xB0},
    {0xB0,0xB0,0xB0,0xC0,0xB0}
};
const int DATA1[MODE_COUNT][D_PINS] = { //rows are banks up to MODE_COUNT
    {80,81,82,83,84},
    {21,22,87,88,89},
    {90,91,92,23,94}
};

(If you don't want to use HEX values and/or status bytes you can use any values; make 0 = CC and 1 = PC... as long as you test correctly in the digital section later)

Then in the digital section test STATUS array and for each case you want to handle:

If (STATUS[bank]=0xC0) ...


I haven't thought through how your use of toggle switches will impact this scheme ... maybe you send the PC change on both edges and ingore D2 on both?

My guess is D2 is ignored with PC so you could just send the 'ON' value as if it were a CC message. If so this would allow you to use the generic send(type,d1,d2,chn) and avoid having to test the STATUS array wehn generating the MIDI messages.
 
STATUS is a bad name for that array.

Maybe TYPE[bank].

The type and channel nibbles together are called the status byte but it's both wrong and misleading here where one might think it's button status.
 
Status
Not open for further replies.
Back
Top