Implementing "Mode Switching" to use multiple CCs for same switch

Status
Not open for further replies.

Aussie_CrocHunter

Well-known member
Hi,

I have 6 switches on my usb midi device. Five switches using the code I've put at the bottom here, and a sixth momentary switch (pin 5) with a single LED (pin 21).

What I'm aiming to do is have the momentary switch (pin 5) send midi cc on and off (not toggle) with each press, but ALSO have a total of three banks of switches for the 5 latching switches. To do so, I was thinking that the momentary switch could be held down for 21.5-2 seconds which would change all the CC values to the second bank with an acknowledgement long flash by the single led (pin 21), and then so on to the third bank and back to the 1st bank (cycling through the banks infinitely).

One issue i perceive is how to make the switch on pin 5 send midi CC responsively without delay, whilst also having it not send midi if a long-press is detected. I don't know if those two things can coexist.

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

// ******CONSTANT VALUES******** 
  // customize code behaviour here!
  const int channel = 1; // MIDI channel
  const int D_PINS = 5; // number of Digital PINS
  const int ON_Value = 127; // note-one velocity sent from buttons (should be 65 to  127)
  
  
  // 'define the pins and notes for digital events'
  const int DIGITAL_PINS[D_PINS] = {0,1,2,3,4};
  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 MIDI_CC_NUMS[D_PINS] = {60,61,62,63,64};
  const int BOUNCE_TIME = 7; // 5 ms is usually sufficient
  const boolean toggled = true;
  
  
  //******VARIABLES***********
    // a data array to remember the current state of each switch
  boolean state[D_PINS];
  
  
  //************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)
  }; 
  
  //************SETUP**************
    void setup() {
      
      //'set a handle for returning control change messages'
      usbMIDI.setHandleControlChange(OnControlChange);
    
      //'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);
      }
    }
  
  //************LOOP**************
    void loop() {
      getDigitalData();
      while (usbMIDI.read()) {
        //' controllers must call .read() to keep the queue clear even if they are not responding to MIDI'
      }
    }
  
  
  //************DIGITAL SECTION**************
    void getDigitalData(){
      for (int i=0;i<D_PINS;i++){
        digital[i].update();
        if (digital[i].fallingEdge() || digital[i].risingEdge()) {
          if (state[i]) {
            usbMIDI.sendControlChange(MIDI_CC_NUMS[i], 0, channel);  
          }else{
            usbMIDI.sendControlChange(MIDI_CC_NUMS[i], ON_Value, channel);  
          }
           state[i] = !state[i] ;
           
           digitalWrite(RED_LED_PINS[i], state[i]);
           digitalWrite(GREEN_LED_PINS[i], !state[i]);
        }
      }
    }
  
  
  void OnControlChange(byte channel, byte controller, byte value) {
    // add channel check?
      for (int i = 0; i < D_PINS ; i++){
        if (MIDI_CC_NUMS[i] == controller) {
          if (value >= 64) {
            digitalWrite(GREEN_LED_PINS[i], LOW);
            digitalWrite(RED_LED_PINS[i], HIGH); //'receiving >64 turns green on and red off'
            state[i] = true;
          }else{
            digitalWrite(GREEN_LED_PINS[i], HIGH);
            digitalWrite(RED_LED_PINS[i], LOW); //'receiving <64 turns red on and green off'
            state[i] = false;
          } // 'if not the controller for i then skip this loop'    
        }
      }
  }
 
If you want a CC sent exactly when you detect the first fallingEdge you have to be OK with it being sent when it turns out your user is just trying to scroll through banks.

So when you're changing banks your DAW would get the CC message with the ON value even though you didn't want one -- would could be unfortunate if the CC turns on an effect, for example.

But if you're second function is tempo sync (and I know it is) then you could reset a timer at the first fallingEdge and send two CC messages based on the next two taps (i.e. the next two fallingEdge on the pin).

The the decision in code is then: if the risingEdge after a fallingEdge (where a counter is still zero) is within T mS then increment the bank variable (or roll it over) and if not then send CC messages (with whatever D2 values you want) on the next two fallingEdges detected and then set a variable as a counter for taps (fallingEdges) to reset after then third tap
In pseudo code
Code:
if fallingEdge 
  if counter = 0
    elapsedTime = 0 // start timer
  else if counter = 1
    if elapsedTime < threshold
      bank++ // increment bank variable (+wrap)
    else
      cc(on)
    end if
  else if counter = 2
    cc(off)
  end if
  counter++
end if

It's a bit rough in my head and a bit late here.

Can you confirm that's the action you want and that you get the idea that you cannot both send and not send CC on the first detected press?

If it's ok to have an 'on' CC message sent when you're just scrolling through banks then the two-tap option is possible.
 
Ok I think that would work. I did think that it would be unavoidable to have the cc sent and not sent. The first tap being a test for CC or bank change is a really good idea. I don't know many people (guitarists) who would do less than three taps on a tap tempo pedal!
 
Here's a thing.... send no CC at tap 1 and send cc at implied location of tap three based on the interval between one and two.....

But, as you've alluded, what to do if there is a third tap as it's not going to be exactly the same.

If the CC that is being scanned by the DAW for tempo is sent on tap one but then no second is sent, would it matter? Maybe just let it? There's bound to be a timeout when only one is received.



The psuedo-code is wrong above. You need to check for risingEdge while counter is at one and check is duration is above threshold.

The tap behaviour can be complicated but the scanning for a long hold is easy.

Don't know if I'll get a chance to try today but I'll post something soon.
 
Last edited:
If the TAP CC message can be sent on every downstroke and it can be the same D2 value (doesn't need off and on values) then the code should be fairly simple.
Here's an untested bit of code I'll be adding ... much of it is just to count out flashes after MODE is changed.
Code:
//************MODE/TAP SECTION**************
void getModeTap(){
    modeTap.update();
    if (modeTap.fallingEdge()) {
        usbMIDI.sendControlChange(TAP_CC, ON_Value, channel);  // can we send any D2 for Tap?
        modeTimer = 0; 
    }    
    if (modeTap.risingEdge()) {
        if (modeTimer>modeThreshold){
                bank++
                bank = bank%3; // should return 0 when bank=3
                flashcount = bank + 1; // count plus 1 so zero-index is one flash
                // set counter of flashes 'owed' -- count them down in code below
            }
        }
    }
[COLOR="#B22222"]    // if flashcount > 0 flash and start counter
    if (flashcount){
        if (flashTimer>flashTime){
            if (flashTimer>(flashTime*2)){
                digitalWrite(MODE_TAP_PIN, HIGH);
            }else{
                digitalWrite(MODE_TAP_PIN, LOW);
                flashcount-- // decrement flashcount
            }
    }[/COLOR]
}

Even if there's more needed to implement the Tap (like separate start and end values in D2) it should be fairly simple to do.

Then we just need to switch to a two-dimensional matrix of CC numbers with 'bank' as the second index value.
Code:
//CC configuration matrix!!
const int MIDI_CC_NUMS[D_PINS][3] = {
    {60,61,62,63,64};
    {65,66,67,68,69};
    {70,71,72,73,74}
}

I hope to compile and test these additions shortly but I'm not at home and have limited resources for Teensy work.
 
Last edited:
Got a chance to compile the code changes I wrote -- yikes I really need to compile as I go.

There's two major mistakes just in the matrix declaration I quoted above...

You can try this if you like but expect it to either fail outright or have some quirks as I almost always have boneheaded errors in anything I write off the cuff. But I do believe it's not distant from working code.

With just a borrowed laptop and a headerless T3 I need to sort out a MIDI monitor and the pinout before I can test out the functionality of the mode/bank selector but I'm out of time for now.

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

// ******CONSTANT VALUES******** 
// customize code behaviour here!
const int channel = 1; // MIDI channel
const int D_PINS = 5; // number of Digital PINS
const int ON_Value = 127; // note-one velocity sent from buttons (should be 65 to  127)


// 'define the pins and notes for digital events'
const int DIGITAL_PINS[D_PINS] = {0,1,2,3,4};
const int MODE_TAP_PIN = 5;
const int MODE_TAP_LED_PIN = 15;
const int MODE_COUNT = 3;
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 BOUNCE_TIME = 7; // 5 ms is usually sufficient
const int TAP_CC = 15;
const int modeThreshold = 500; // how long to hold before mode changes
const int flashTime = 150; // how long flashed LED is on (and OFF?)

//CC configuration matrix!!
const 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}
};

//******VARIABLES***********
// a data array to remember the current state of each switch
boolean state[D_PINS];
elapsedMillis modeTimer,flashTimer;
boolean modeSelectActive = false;
int bank = 0 ; 
int flashcount;

//************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 control change messages'
  usbMIDI.setHandleControlChange(OnControlChange);

  //'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_LED_PIN, OUTPUT);
}

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


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


//************MODE/TAP SECTION**************
void getModeTap(){
  modeTap.update();
  if (modeTap.fallingEdge()) {
    usbMIDI.sendControlChange(TAP_CC, ON_Value, channel);  // can we send any D2 for Tap?
    modeTimer = 0; 
  }    
  if (modeTap.risingEdge()) {
    if (modeTimer>modeThreshold){
      bank++;
      flashcount = bank + 1;
      // set counter of flashes 'owed' -- count them down after main part
    }
  }
// if flashcount > 0 flash and start counter
  if (flashcount){
    if (flashTimer>flashTime){
      if (flashTimer>(flashTime*2)){
        digitalWrite(MODE_TAP_PIN, HIGH);
      }else{
        digitalWrite(MODE_TAP_PIN, LOW);
        flashcount-- ;// decrement flashcount
      }
    }
  }
}

void OnControlChange(byte channel, byte controller, byte value) {
// add channel check?
  for (int i = 0; i < D_PINS ; i++){
    if (MIDI_CC_NUMS[bank][i] == controller) {
      if (value >= 64) {
        digitalWrite(GREEN_LED_PINS[i], LOW);
        digitalWrite(RED_LED_PINS[i], HIGH); //'receiving >64 turns green on and red off'
        state[i] = true;
      }else{
        digitalWrite(GREEN_LED_PINS[i], HIGH);
        digitalWrite(RED_LED_PINS[i], LOW); //'receiving <64 turns red on and green off'
        state[i] = false;
      } // 'if not the controller for i then skip this loop'    
    }
  }
}
 
Yeah well I think I'd have been more surprised if it had worked. The switch logic is really all that can be wrong. Once we can set bank value then the rest will work as before... The taps we'll sort out when we're sure what needs to be sent and when. (I.e. do we need D2=0 messages between D2=127 on quarter note pulse? It's likely the software just ignores them allowing for note-type event timings to ignore the off message).


The LED will be wonky for a bit... I'll have to drop Serial.print commands in there to stand in for LED hardware. The flashing mechanism is a bit of a stab in the dark.

Normally you just put a delay between flashes but you can't do that here as your pedal has to keep scanning for changes. And you need different visual signals for each of three modes.

I just noticed that the line of code to wrap the bank index is missing... uses a modulus 3 division to wrap the index to [0,1,2]... and the inequality is likely backwards in setting the LED pin and counting down the flashes.
I'm sure there's more but I can't compile again for a while (and have not compiled these either!).

Code:
//************MODE/TAP SECTION**************
void getModeTap(){
  modeTap.update();
  if (modeTap.fallingEdge()) {
    usbMIDI.sendControlChange(TAP_CC, ON_Value, channel);  // can we send any D2 for Tap?
    modeTimer = 0; 
  }    
  if (modeTap.risingEdge()) {
    if (modeTimer>modeThreshold){
      bank++;
      [B][COLOR="#FF0000"]bank = bank%3;[/COLOR][/B]
      flashcount = bank + 1;
      // set counter of flashes 'owed' -- count them down after main part
    }
  }
// if flashcount > 0 flash and start counter
  if (flashcount){
    if (flashTimer>flashTime){
      [B]if (flashTimer[COLOR="#FF0000"]<[/COLOR](flashTime*2))[/B]{
        digitalWrite(MODE_TAP_PIN, HIGH);
      }else{
        digitalWrite(MODE_TAP_PIN, LOW);
        flashcount-- ;// decrement flashcount
      }
    }
  }
}
 
Code:
//************LIBRARIES USED**************
// 'include the Bounce library for 'de-bouncing' switches -- removing electrical chatter as contacts settle'
#include <Bounce.h> 
//'usbMIDI.h library is added automatically when code is compiled as a MIDI device'

// ******CONSTANT VALUES******** 
// customize code behaviour here!
const int channel = 1; // MIDI channel
const int D_PINS = 5; // number of Digital PINS
const int ON_Value = 127; // note-one velocity sent from buttons (should be 65 to  127)


// 'define the pins and notes for digital events'
const int DIGITAL_PINS[D_PINS] = {0,1,2,3,4};
const int MODE_TAP_PIN = 5;
const int MODE_TAP_LED_PIN = 15;
const int MODE_COUNT = 3;
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 BOUNCE_TIME = 7; // 5 ms is usually sufficient
const int TAP_CC = 15;
const int modeThreshold = 500; // how long to hold before mode changes
const int flashOnTime = 150; // how long flashed LED is on (and OFF?)
const int flashOffTime = 75; // how long flashed LED is on (and OFF?)

//CC configuration matrix!!
const 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}
};

//******VARIABLES***********
// a data array to remember the current state of each switch
boolean state[D_PINS];
elapsedMillis modeTimer,flashTimer;
boolean modeSelectActive = false;
int bank = 0 ; 
int flashcount;
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..
//************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 control change messages'
usbMIDI.setHandleControlChange(OnControlChange);

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

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


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

//************MODE/TAP SECTION**************
void getModeTap(){
  
  modeTap.update();
  if (modeTap.fallingEdge()) {
    usbMIDI.sendControlChange(TAP_CC, ON_Value, channel);  // can we send any D2 for Tap?
    modeTimer = 0;
    shiftUp = true;
  }    
  if (modeTap.risingEdge()){
    shiftUp = false;
  }
  if (modeTimer>modeThreshold && shiftUp) {
    shiftUp = false;
    bank++;
    bank = bank%MODE_COUNT;
    flashcount = bank + 1;
    flashTimer = 0 ;
    // set counter of flashes 'owed' -- count them down after main part
  }
// if flashcount > 0 flash and start counter

  if (flashcount){ 
    if (flashTimer>(flashOnTime+flashOffTime)){ 
      flashcount-- ;// decrement flashcount
      flashTimer = 0;
      if (modeLED == HIGH){
        modeLED = LOW;
        digitalWrite(MODE_TAP_LED_PIN, LOW);
      }
    }else{
      if (modeLED == LOW && flashTimer>flashOnTime){
        modeLED = HIGH;
        digitalWrite(MODE_TAP_LED_PIN, HIGH);
      }
    }
  }
}

void OnControlChange(byte channel, byte controller, byte value) {
// add channel check?
  for (int i = 0; i < D_PINS ; i++){
    if (MIDI_CC_NUMS[bank][i] == controller) {
      if (value >= 64) {
        digitalWrite(GREEN_LED_PINS[i], LOW);
        digitalWrite(RED_LED_PINS[i], HIGH); //'receiving >64 turns green on and red off'
        state[i] = true;
      }else{
        digitalWrite(GREEN_LED_PINS[i], HIGH);
        digitalWrite(RED_LED_PINS[i], LOW); //'receiving <64 turns red on and green off'
        state[i] = false;
      } // 'if not the controller for i then skip this loop'    
    }
  }
}

Edit... closer still... seems to work but needs some cleanup...
 
Last edited:
I can't quite explain what's going on when I test this, but it doesn't seem to be exactly as it should be. I can get the LED to flash, but the mode doesn't seem to change. Also, the mode switching seems to be assigned to either the first switch, or something is messed up. (I changed the pins back to my pins)
 
I did have the PINs configured for my test rig but it looks like I put them back to how I thought yours are working but I don't know where you put the mode switch or it's LED.

Can you share the configuration lines you are using?
Code:
const int DIGITAL_PINS[D_PINS] = {0,1,2,3,4};
const int MODE_TAP_PIN = 5;
const int MODE_TAP_LED_PIN = 15;
...
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 TAP_CC = 15; // what CC do you want for Tap?

...
//CC configuration matrix!!
const 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}
};
 
My issue was a loose wire. This is now working very well! When I hold the button the LED blinks once. Then Blinks Twice for the second bank, but doesn't blink when going back to zero. I'm thinking perhaps the blinking should be:

Blink twice when moving from the original mode (0) to mode 1, blink 3 times when moving to mode 2 (counting 1,2,3, rather than 0,1,2), and then blink once when back to mode 0.

Otherwise, working very well. I had to increase my bounce time up to 15! I'll do further testing but it's basically not an issue.

In regard to the tap sending MIDI, i think by making the hold time longer it will avoid the effects of cycling through the modes also slowing the tempo right down and increasing the chance of that timeout period happening. If you think of a better solution, I'd be happy to use it, but perhaps we should make a section so we can easily have a user comment out the CC being sent by the tap button?

This is working well . THanks so much for your help oddson!
 
Also, is there any way to store the state each switch on the banks ahould be in (red or green)?

Here's the scenario: Midi is sent by the software when the control changes. If I change to the next bank, the software doesn't know that, and does not send a midi message to say what colors the LEDs should be. So, changing banks leaves the LEDs showing the previous bank's settings. If we could store somehow the state of each Controller's LED, then changing banks could also update the LEDs ?
 
Also, is there any way to store the state each switch on the banks ahould be in (red or green)?

Here's the scenario: Midi is sent by the software when the control changes. If I change to the next bank, the software doesn't know that, and does not send a midi message to say what colors the LEDs should be. So, changing banks leaves the LEDs showing the previous bank's settings. If we could store somehow the state of each Controller's LED, then changing banks could also update the LEDs ?

Ummm... I think I just forgot to make the arrays two-dimensional. The same logic that works for which button states is which should work for banks too.


My issue was a loose wire. This is now working very well! When I hold the button the LED blinks once. Then Blinks Twice for the second bank, but doesn't blink when going back to zero. I'm thinking perhaps the blinking should be:

Blink twice when moving from the original mode (0) to mode 1, blink 3 times when moving to mode 2 (counting 1,2,3, rather than 0,1,2), and then blink once when back to mode 0.
That is how it was working for me. Does HIGH = LED On with your wiring?

Otherwise, working very well. I had to increase my bounce time up to 15! I'll do further testing but it's basically not an issue.

In regard to the tap sending MIDI, i think by making the hold time longer it will avoid the effects of cycling through the modes also slowing the tempo right down and increasing the chance of that timeout period happening. If you think of a better solution, I'd be happy to use it, but perhaps we should make a section so we can easily have a user comment out the CC being sent by the tap button?
There are constants to set the time in milliseconds. The flashes may be too short too.
 
Try this code buy check the pin and CC values first.
Code:
//************LIBRARIES USED**************
// 'include the Bounce library for 'de-bouncing' switches -- removing electrical chatter as contacts settle'
#include <Bounce.h> 
//'usbMIDI.h library is added automatically when code is compiled as a MIDI device'

// ******CONSTANT VALUES******** 
// customize code behaviour here!
const int channel = 1; // MIDI channel
const int D_PINS = 5; // number of Digital PINS
const int ON_Value = 127; // note-one velocity sent from buttons (should be 65 to  127)


// 'define the pins and notes for digital events'
[COLOR="#FF0000"]const int DIGITAL_PINS[D_PINS] = {0,1,2,3,4};
const int MODE_TAP_PIN = 5;
const int MODE_TAP_LED_PIN = 15;[/COLOR]
const int MODE_COUNT = 3;
[COLOR="#FF0000"]const int RED_LED_PINS[D_PINS] = {6,19,8,10,17};
const int GREEN_LED_PINS[D_PINS] = {7,20,9,16,18};[/COLOR]
const int BOUNCE_TIME = 7; // 5 ms is usually sufficient
const int modeThreshold = 500; // how long to hold before mode changes
const int flashOnTime = 150; // how long flashed LED is on (and OFF?)
const int flashOffTime = 75; // how long flashed LED is on (and OFF?)

//CC configuration matrix!!
[COLOR="#FF0000"]const 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}
};
const int TAP_CC = 15;[/COLOR]

//******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;
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..
//************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 control change messages'
  usbMIDI.setHandleControlChange(OnControlChange);
  
  //'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);
}

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


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

//************MODE/TAP SECTION**************
void getModeTap(){
  modeTap.update();
  if (modeTap.fallingEdge()) {
    usbMIDI.sendControlChange(TAP_CC, ON_Value, channel);  // can we send any D2 for Tap?
    modeTimer = 0;
    shiftUp = true;
  }    
  if (modeTap.risingEdge()){
    shiftUp = false;
  }
  if (modeTimer>modeThreshold && shiftUp) {
    shiftUp = false;
    bank++;
    bank = bank%MODE_COUNT;
    flashcount = bank + 1;
    flashTimer = 0 ;
    // set counter of flashes 'owed' -- count them down after main part
  }
  // if flashcount > 0 flash and start counter
  
  if (flashcount){
    if (flashTimer>(flashOnTime+flashOffTime)){ 
        flashcount-- ;// decrement flashcount
        flashTimer = 0;
        if (modeLED == HIGH){
          modeLED = LOW;
          digitalWrite(MODE_TAP_LED_PIN, LOW);
        }
      }else{
        if (modeLED == LOW && flashTimer>flashOnTime){
          modeLED = HIGH;
          digitalWrite(MODE_TAP_LED_PIN, HIGH);
        }
    }
  }
}

void OnControlChange(byte channel, byte controller, byte value) {
// add channel check?
  for (int i = 0; i < D_PINS ; i++){
    if (MIDI_CC_NUMS[bank][i] == controller) {
      if (value >= 64) {
        digitalWrite(GREEN_LED_PINS[i], LOW);
        digitalWrite(RED_LED_PINS[i], HIGH); //'receiving >64 turns green on and red off'
        state[bank][i] = true;
      }else{
        digitalWrite(GREEN_LED_PINS[i], HIGH);
        digitalWrite(RED_LED_PINS[i], LOW); //'receiving <64 turns red on and green off'
        state[bank][i] = false;
      } // 'if not the controller for i then skip this loop'    
    }
  }
}
 
oops. broke D2 on the other pins...

Code:
//************DIGITAL SECTION**************
void getDigitalData(){
  for (int i=0;i<D_PINS;i++){
    digital[i].update();
    if (digital[i].fallingEdge() || digital[i].risingEdge()) {
      if (state[COLOR="#FF0000"][bank][/COLOR][i]) {
        usbMIDI.sendControlChange(MIDI_CC_NUMS[bank][i], 0, channel);  
      }else{
        usbMIDI.sendControlChange(MIDI_CC_NUMS[bank][i], ON_Value, channel);  
      }
       state[bank][i] = !state[bank][i] ;
       digitalWrite(RED_LED_PINS[i], state[bank][i]);
       digitalWrite(GREEN_LED_PINS[i], !state[bank][i]);
    }
  }
}
Missed this one.

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

// ******CONSTANT VALUES******** 
// customize code behaviour here!
const int channel = 1; // MIDI channel
const int D_PINS = 5; // number of Digital PINS
const int ON_Value = 127; // note-one velocity sent from buttons (should be 65 to  127)


// 'define the pins and notes for digital events'
const int DIGITAL_PINS[D_PINS] = {0,1,2,3,4};
const int MODE_TAP_PIN = 5;
const int MODE_TAP_LED_PIN = 21;
const int MODE_COUNT = 3;
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 BOUNCE_TIME = 30; // 5 ms is usually sufficient
const int modeThreshold = 600; // how long to hold before mode changes
const int flashOnTime = 250; // how long flashed LED is on (and OFF?)
const int flashOffTime = 250; // how long flashed LED is on (and OFF?)

//CC configuration matrix!!
const 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}
};
const int TAP_CC = 15;

//******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;
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..
//************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 control change messages'
  usbMIDI.setHandleControlChange(OnControlChange);
  
  //'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, HIGH);

  digitalWrite(7, HIGH);   //SW1 - GREEN OFF
  digitalWrite(16, HIGH);  //SW2 - GREEN OFF
  digitalWrite(20, HIGH);  //SW3 - GREEN OFF
  digitalWrite(9, HIGH);   //SW4 - GREEN OFF
  digitalWrite(18, HIGH);  //SW5 - GREEN OFF
}

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


//************DIGITAL SECTION**************
void getDigitalData(){
  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, channel);  
      }else{
        usbMIDI.sendControlChange(MIDI_CC_NUMS[bank][i], ON_Value, channel);  
      }
       state[bank][i] = !state[bank][i] ;
       digitalWrite(RED_LED_PINS[i], state[bank][i]);
       digitalWrite(GREEN_LED_PINS[i], !state[bank][i]);
    }
  }
}

//************MODE/TAP SECTION**************
void getModeTap(){
  modeTap.update();
  if (modeTap.fallingEdge()) {
    usbMIDI.sendControlChange(TAP_CC, ON_Value, channel);  // can we send any D2 for Tap?
    modeTimer = 0;
    shiftUp = true;
  }    
  if (modeTap.risingEdge()){
    shiftUp = false;
  }
  if (modeTimer>modeThreshold && shiftUp) {
    shiftUp = false;
    bank++;
    bank = bank%MODE_COUNT;
    flashcount = bank + 1;
    flashTimer = 0 ;
    // set counter of flashes 'owed' -- count them down after main part
  }
  // if flashcount > 0 flash and start counter
  
  if (flashcount){
    if (flashTimer>(flashOnTime+flashOffTime)){ 
        flashcount-- ;// decrement flashcount
        flashTimer = 0;
        if (modeLED == HIGH){
          modeLED = LOW;
          digitalWrite(MODE_TAP_LED_PIN, HIGH);
        }
      }else{
        if (modeLED == LOW && flashTimer>flashOnTime){
          modeLED = HIGH;
          digitalWrite(MODE_TAP_LED_PIN, LOW);
        }
    }
  }
}

void OnControlChange(byte channel, byte controller, byte value) {
// add channel check?
  for (int i = 0; i < D_PINS ; i++){
    if (MIDI_CC_NUMS[bank][i] == controller) {
      if (value >= 64) {
        digitalWrite(GREEN_LED_PINS[i], LOW);
        digitalWrite(RED_LED_PINS[i], HIGH); //'receiving >64 turns green on and red off'
        state[bank][i] = true;
      }else{
        digitalWrite(GREEN_LED_PINS[i], HIGH);
        digitalWrite(RED_LED_PINS[i], LOW); //'receiving <64 turns red on and green off'
        state[bank][i] = false;
      } // 'if not the controller for i then skip this loop'    
    }
  }
}

Thanks so much for that! Sorry It's taken me a while to respond.

The code updates you've sent work well overall. The flashes are great and the addition of a timer for the flash really helps :) Yes, my Mode LED is wired LOW=ON HIGH=OFF

There is still one issue, When I set it all up in mainstage, The different banks do not seem to hold or "know" what LED states they should be in.
I can see that when I change patches in Mainstage, that it is sending all 15CCs value back to teensy (I'm using midimonitor app on mac) however, when I change banks the LEDs remain as they were on the previous bank until I either change patches on Mainstage again (which the sends the midi and will update the lights) or start using switches.

Thank for your help here. This is so close to being finished!
 
Code:
for (int i = 0; i < D_PINS ; i++){
  digitalWrite(GREEN_LED_PINS[i], !state[bank][i]);
  digitalWrite(RED_LED_PINS[i], state[bank][i]); 
}

Add this before flashcount = bank + 1;

The negation operator might be on the wrong one again.
 
So what did you want with the clock? -- you'll need an override on the clock flash from the first TAP fallingEdge until after the second TAP is sent and for some delay time if the bank threshold is broken.

We can see if a flash every quarter note (24 clocks?) from the first received and surpress the flash (but not the count) during bank selection.
 
Last edited:
Status
Not open for further replies.
Back
Top