Help Receiving MIDI clock for tempo LED

Status
Not open for further replies.

Aussie_CrocHunter

Well-known member
Hi there,

I have a project on the go which is very near to completion! I have 6 switches - one of which is a momentary switch which is used for tap tempo (sends CC_Num val=127 on falling edge), but also has a mode-switching function attached to it. The mode switch triggers a single LED to flash which Bank is being selected. However, when the LED is not being used for indicating which bank is being selected, I would like it to receive the midi clock/tempo from a host such as logic pro, Mainstage, Ableton etc.

Can you please help me?

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;
    for (int i = 0; i < D_PINS ; i++){
  digitalWrite(GREEN_LED_PINS[i], !state[bank][i]);
  digitalWrite(RED_LED_PINS[i], state[bank][i]); 
}
    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'    
    }
  }
}
 
What you use for tempo is called the MIDI Beat Clock and most DAWs will support this since it’s needed to sync external MIDI sequencers together. This is supported in Teensyduino already and there are callbacks available for them, but you will have to turn it on in your DAW.
Code:
usbMIDI.setHandleClock(myClock);
usbMIDI.setHandleStart(myStart);
usbMIDI.setHandleContinue(myContinue);
usbMIDI.setHandleStop(myStop);

Here’s a website for how syncing works with the Beat Clock: http://midi.teragonaudio.com/tech/midispec/seq.htm
To summarize that, Clock will be sent 24 times per quarter note and that’s the main one you want to worry about for just tempo. It’s also important to restart your Clock count when a Start message is received so it’s not out of sync.

You can also find information on all MIDI messages here: http://midi.teragonaudio.com/tech/midispec.htm
 
Here’s a website for how syncing works with the Beat Clock: http://midi.teragonaudio.com/tech/midispec/seq.htm

So set a counter to zero on myStart and increment on myClock and flash when the modulus by 24 is zero (and reset the counter if we're not tracking the beat/measure).
Pause counting on myStop and continue from before on myContinue

Is that all there is to it? Can you think of 'gotchas' where the flash could lose the beat?

Code:
void myStart{
  flashCntr = 0;
}

void myContinue{
  pauseFlash = False;
}

void myStop{
  pauseFlash = True;
}

void myClock{
  if (!pauseFlash){
    flashCntr++
    flashCntr = flashCntr%24
  }
  if (flashCntr == 0){
    //flash goes here!
  }
}
 
You don't necessarily want to stop counting clocks on stop, since some DAWs will still send clocks even while stopped so for just tempo I would only worry about Clock, Start, and Continue. I don't believe there are any gotchas where it could lose the beat as long as you stick to 24 Clocks per quarter note and to reset the count on Start and Continue, which they can both use the same callback function in this case because there is no difference for this. If you were you trying to do something more complicated like using the Teensy as a standalone sequencer and syncing the playback with a DAW then you would have to worry about Stop and Song Position Pointers, but strictly for just keeping tempo it is not necessary.

Also before the question gets asked, I don't believe MIDI has a way to keep track of time signatures so you can't flash the down beat as a different color from the rest of the measure.
 
Also, in my opinion the best way to keep track of the LED state is to just turn it off after a certain number of clock cycles, so if you turn it on at Clock == 24 then you can just turn it off at Clock == 12.
 
I would only worry about Clock, Start, and Continue.
So then it's zero on start and never pause?

And the convention is to run the master clock at the current tempo and to pick-up after a resume/continue command?

I feel like you can pause some software off beat so I expected it would be trickier.
 
You can pause off beat most of the time when you do not have the MIDI Beat Clock on, some DAWs behave differently when you have it turned on because of the loss in resolution. If you take into account Song Position Pointers the closest in resolution you can get is 1 sixteenth note, those are always used in conjunction with a Continue message so it may be helpful to keep track of the clock with that for Continue messages. It’s really simple, just multiply the SPP by 6 then modulus by 24 and that’s the clock value that should be started from when a Continue message is received.
 
Code:
int ledPin = 13;
uint16_t Clock; 

void setup() {
  pinMode(ledPin, OUTPUT);
  usbMIDI.setHandleSongPosition(onSongPosition);
  usbMIDI.setHandleClock(onClock);
  usbMIDI.setHandleStart(onStart);
}

void loop() {
  usbMIDI.read();
}

void onClock(byte channel, byte note, byte velocity) {
  Clock= Clock++;
  Clock= Clock%24
  if (Clock<=3){
    digitalWrite(ledPin, HIGH);
  }else{
    digitalWrite(ledPin, LOW);
  }
}

void onStart(){
  Clock = 0;
}

void onSongPosition(uint16_t semiQ)
  Clock= semiQ%4 ; 
}
I hope to try this code out later but have no compiler, no Teensy and no music software where I'm at.
 
Besides your SPP callback the rest is fine, one SPP or MIDI Beat equals 6 MIDI Clocks so modulus by 4 won’t give you the right value. What you normally do to find the the total number of Clocks is multiply your MIDI Beats by the number of MIDI Clocks per Beat, that comes out to Clock = semiQ * 6. That’s really all you have to do since you already have your modulus by 24 in the Clock callback.
 
Besides your SPP callback the rest is fine, one SPP or MIDI Beat equals 6 MIDI Clocks so modulus by 4 won’t give you the right value. What you normally do to find the the total number of Clocks is multiply your MIDI Beats by the number of MIDI Clocks per Beat, that comes out to Clock = semiQ * 6. That’s really all you have to do since you already have your modulus by 24 in the Clock callback.

I think you were right the first time --> (SPP*6)%24 = SPP%4

Consider: by taking modulo 4 of the SPP we are effectively truncating to the number of 1/16 notes since the last quarter. Which is what we want to count clocks until the next (assuming DAW don't just start on a beat when you have clock on in which case its should be zero anyway).

I'm not conviced and I started this messages to say 'opps' so maybe there's still an opps in my thinking.

Thanks for your help.
 
Technically your thinking is close, modulus 4 would be the number of 1/16 notes, but we need the number of Clocks and not 1/16 notes. So to get the number of Clocks we have a multiply our number of 1/16 notes by 6 to get our Clocks, thus (SPP % 4) * 6 would be the same as (SPP * 6) % 24, so either would work just fine.
 
Yeah I should have thought about units and that the result needs to jump by six with each semiquaver.

Isn't there a risk of overloading the variable by doing the multiplication before the modulus?

Safer to have semiQ%4 * 6?

Thanks again
 
Last edited:
I don’t believe values get truncated until they are actually written to a variable, I’ve definitely not seen it happen with other stuff that I’ve done. As long as you know what you are going for it won’t truncate the value before you are done with it and if anything you can force the compiler to not truncate it if you are worried about it by adding (uint32_t)(semiQ*6) % 24. It shouldn’t be necessary, but if it is you can always force it to happen by casting it to the expected value and see what happens, I’ve definitely done math before with larger numbers before the final result is truncated down to fit the right variable.
 
I don’t believe values get truncated until they are actually written to a variable, I’ve definitely not seen it happen with other stuff that I’ve done. As long as you know what you are going for it won’t truncate the value before you are done with it and if anything you can force the compiler to not truncate it if you are worried about it by adding (uint32_t)(semiQ*6) % 24. It shouldn’t be necessary, but if it is you can always force it to happen by casting it to the expected value and see what happens, I’ve definitely done math before with larger numbers before the final result is truncated down to fit the right variable.

The ISO C/C++ standards say that in an arithmetic expression, 'short' and 'char'' values are promoted to 'int' (*) and then the normal rules of arithmetic promotion is done (i.e. if in a binary expression, one side is 'long' and the other 'int', the 'int' gets promoted to 'long').

* Technically it could promote it to 'unsigned int' in the case that 'unsigned short' or 'unsigned char' were the same size as 'int', but since that isn't the case for Teensies, it is more of a footnote. This is know as the value preserving rule.

Before the first standardization, the K&R C used a signedness preserving rule where 'unsigned short' always promotes to an 'unsigned int'. However, one of the changes in the original 1989 ANSI C standard (which became the ISO C standard in 1990) was switching from the signedness preserving rule to value preserving rule.
 
Code:
[COLOR="#808080"]int ledPin = 13;[/COLOR]
[B]int ClockCount; [/B]

[COLOR="#808080"]void setup() {
  pinMode(ledPin, OUTPUT);[/COLOR]
[B]  usbMIDI.setHandleSongPosition(onSongPosition);
  usbMIDI.setHandleClock(onClock);
  usbMIDI.setHandleStart(onStart);[/B]
[COLOR="#808080"]}

void loop() {
  usbMIDI.read();
}[/COLOR]

[B]
void onClock(byte channel, byte note, byte velocity) {
  if (ClockCount<=3){
    digitalWrite(ledPin, HIGH);
  }else{
    digitalWrite(ledPin, LOW);
  }
  ++ClockCount = ClockCount%24;
}

void onStart(){
  ClockCount = 0;
}

void onSongPosition(uint16_t semiQ){
  ClockCount= semiQ*6 ; 
}[/B]
This seems to work...
The overflow issue is only a problem if you limit the ClockCount to 16 bits.

We just add the bold bits into the main sketch. I think disabling the beat flash during bank-selection is the only additional thing to do.
 
Last edited:
Looks good and yeah just making it 32 bit solves the problem, though it should be unsigned so it doesn’t go negative.
 
It can't since its effective limit is 16^2*6 but your point is taken...

Learned a lot here... including the commutative rule for modulo division

Thanks so much.
 
The limit is actually (2^14)*6, but you are right it won’t overflow, I forgot how numbers worked.
 
I forgot how numbers worked.
...and I forgot how powers are written... how increment's work... and that two data bytes in MIDI is only 14 bits. Even by my standards I'm making a lot of gaffs today.

@Aussie_CrocHunter
Try this code:
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..
int ClockCount; // for tempo tracking
boolean tempoOn = false;
//************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);
  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, 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;
    tempoOn = false;
  }    
  if (modeTap.risingEdge()){
    shiftUp = false;
  }
  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]); 
    }
    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){[COLOR="#FF0000"][/COLOR]
          modeLED = LOW;
          digitalWrite(MODE_TAP_LED_PIN, [COLOR="#FF0000"]LOW[/COLOR]);
        }
      }else{
        if (modeLED == LOW && flashTimer>flashOnTime){
          modeLED = HIGH;
          digitalWrite(MODE_TAP_LED_PIN, [COLOR="#FF0000"]HIGH [/COLOR]);
        }
    }
  }else{
    tempoOn = true;
  }
}

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'    
    }
  }
}

void onClock(byte channel, byte note, byte velocity) {
  if (ClockCount<=3){
    if (tempoOn){
      digitalWrite(MODE_TAP_LED_PIN, [COLOR="#FF0000"]HIGH[/COLOR]);
    }
  }else{
    digitalWrite(MODE_TAP_LED_PIN, [COLOR="#FF0000"]LOW[/COLOR]);
  }
  ++ClockCount = ClockCount%24;
  Serial.println(ClockCount);
}

void onStart(){
  ClockCount = 0;
}

void onSongPosition(uint16_t semiQ){
  ClockCount= semiQ*6 ; 
}

Specifically, try to use the Tap pedal while clocks are coming and see if the LED behaves remotely like you would expect.

Chances are good it won't work as intended because nothing ever does on the first go but this should be close to what you asked for.

EDIT - forgot your tap LED is also active LOW...

Or is it?... I'm confused
 
Last edited:
yes tap LED is active low as well.
The LED stays on continuously and looks like it is being driven LOW constantly. When holding the button to do a mode change, the LED has visible flickers, but they are very quick. It looks like the beat clock messages are telling the LED to be on much more frequently than it should, and it also runs while doing mode change and is overriding the mode flash. I'm not certain if that's what's happening, or whether the led is simply on continuously for another reason, and the mode flashes are interrupting it.

I tried the smaller simple version of the code above as well but it doesn't work either.
 
The code in 15 is tested to work with the built in LED with both a MIDI utility and two DAW...

I've rewritten your program to abstract out the active low LED wiring. The code can be set to active high or low in the constant definitions, which will be handy if I have to build a test bed to debug this.

We could have just switched the inequality in the code that turns the LED off or on based on whether the counter is 3 or less. But the code should be easier to read with LED_ON and LED_OFF.

Code:
see below
 
Last edited:
Should the else statement be wrapped in SetTempoActive as well?
Code:
 void onClock(byte channel, byte note, byte velocity) {
  if (ClockCount<=3){
    if (SetTempoActive){
      digitalWrite(MODE_TAP_LED_PIN, LED_ON);
    }
  }else{
    digitalWrite(MODE_TAP_LED_PIN, LED_OFF);
  }
  ++ClockCount = ClockCount%24;
}
 
Hey... nicely spotted.

I think there's some confusion on my part. I figured it was safe to keep the light off but it would turn it off while you are in bank select.

So yes to your insight but also the conditional isn't set up correctly either so it's really nice you spotted this so quickly after my post.
 
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******** 

//********** PIN DEFINITIONS
const int D_PINS = 5; // number of Digital PINS
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 MODE_TAP_PIN = 5;
const int MODE_TAP_LED_PIN = 21;


//********** MIDI DEFINITIONS
const int channel = 1; // MIDI channel
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
    {60,61,62,63,64},
    {65,66,67,68,69},
    {70,71,72,73,74}
};
const int TAP_CC = 15;
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 = 600; // 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


//******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..
int ClockCount; // for tempo tracking
boolean bankSelect = false;
//************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);
  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() {
  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);  
        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] ;
    }
  }
}

//************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;
    bankSelect = true; 
    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]); 
    }
    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){
          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{
    //bankSelect = false;
  }
}

void OnControlChange(byte rcvChannel, byte controller, byte value) {
  if (rcvChannel == channel){
    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(byte channel, byte note, byte velocity) {
  if (!bankSelect){
    if (ClockCount<=3){
      digitalWrite(MODE_TAP_LED_PIN, LED_ON);
    }else{
      digitalWrite(MODE_TAP_LED_PIN, LED_OFF);
    }
  }
  ++ClockCount = ClockCount%24;
}

void onStart(){
  ClockCount = 0;
}


void onSongPosition(uint16_t semiQ){
  ClockCount= semiQ*6 ; 
}
This is my first go as sorting that out but it's late here.

The issue is when and how to override the flashing from an active clock stream while you are trying to use the bank select feature.
 
Is this an error that could be causing issues?
Code:
Users/simonglover/Documents/Arduino/Oddson_FULL_FUNCTION_TEST/Oddson_FULL_FUNCTION_TEST.ino:69:33: warning: invalid conversion from 'void (*)(byte, byte, byte) {aka void (*)(unsigned char, unsigned char, unsigned char)}' to 'void (*)()' [-fpermissive]
   usbMIDI.setHandleClock(onClock);
                                 ^
In file included from /Applications/Arduino.app/Contents/Java/hardware/teensy/avr/cores/teensy/usb_api.h:10:0,
                 from /Applications/Arduino.app/Contents/Java/hardware/teensy/avr/cores/teensy/WProgram.h:22,
                 from /var/folders/cw/2vwhj4sj64lf7lr2xw7b201r0000gn/T/arduino_build_999012/pch/Arduino.h:1:
/Applications/Arduino.app/Contents/Java/hardware/teensy/avr/cores/teensy/../usb_midi/usb_api.h:236:7: note:   initializing argument 1 of 'void usb_midi_class::setHandleClock(void (*)())'
  void setHandleClock(void (*fptr)(void)) {
       ^~~~~~~~~~~~~~
 
Status
Not open for further replies.
Back
Top