Example code for MIDI controllers with Pots and Buttons

oddson

Well-known member
Here is my code for a simple MIDI controller with both potentiometers and buttons using ResponsiveAnalogRead and BOUNCE libraries to deal with signal stability.

I hope it will be helpful for those looking to implement basic USB-MIDI controller projects and don't want to write their own code as yet.

I'm looking for suggestions and a general vetting for stupidity before pointing noobs at it...

Code:
//************LIBRARIES USED**************
// include the ResponsiveAnalogRead library for analog smoothing
#include <ResponsiveAnalogRead.h>
// 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 A_PINS = 6; // number of Analog PINS
const int D_PINS = 3; // number of Digital PINS
const int ON_VELOCITY = 99; // note-one velocity sent from buttons (should be 65 to  127)

// define the pins you want to use and the CC ID numbers on which to send them..
const int ANALOG_PINS[A_PINS] = {A0,A1,A2,A3,A4,A5};
const int CCID[A_PINS] = {21,22,23,24,25,26};

// define the pins and notes for digital events
const int DIGITAL_PINS[D_PINS] = {0,1,2};
const int note[D_PINS] = {60,61,62};
const int BOUNCE_TIME = 7; // 5 ms is usually sufficient
const boolean toggled = true;


//******VARIABLES***********
// a data array and a lagged copy to tell when MIDI changes are required
byte data[A_PINS];
byte dataLag[A_PINS]; // when lag and new are not the same then update MIDI CC value


//************INITIALIZE LIBRARY OBJECTS**************
// not sure if there is a better way... some way run a setup loop on global array??
// use comment tags to comment out unused portions of array definitions

// initialize the ReponsiveAnalogRead objects
ResponsiveAnalogRead analog[]{
  {ANALOG_PINS[0],true},
  {ANALOG_PINS[1],true},
  {ANALOG_PINS[2],true},
  {ANALOG_PINS[3],true},
  {ANALOG_PINS[4],true},
  {ANALOG_PINS[5],true}/*,
  {ANALOG_PINS[6],true},
  {ANALOG_PINS[7],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(DIGITAL_PINS[5], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[6], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[7], BOUNCE_TIME),*/
}; 

//************SETUP**************
void setup() {
// 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);
  }
}

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


//************ANALOG SECTION**************
void getAnalogData(){  
  for (int i=0;i<A_PINS;i++){
    // update the ResponsiveAnalogRead object every loop
    analog[i].update(); 
    // if the repsonsive value has change, print out 'changed'
    if(analog[i].hasChanged()) {
      data[i] = analog[i].getValue()>>3;
      if (data[i] != dataLag[i]){
        dataLag[i] = data[i];
        usbMIDI.sendControlChange(CCID[i], data[i], channel);
      }
    }
  }
}



//************DIGITAL SECTION**************
void getDigitalData(){
  for (int i=0;i<D_PINS;i++){
  digital[i].update();
    if (digital[i].fallingEdge()) {
      usbMIDI.sendNoteOn(note[i], ON_VELOCITY, channel);  
    }
    // Note Off messages when each button is released
    if (digital[i].risingEdge()) {
      usbMIDI.sendNoteOff(note[i], 0, channel);  
    }
  }
}
 
Last edited:
Would it be ok to include this in the examples that ship with Teensyduino?

I'd like to give you credit in the comments. How best to do that? "oddson"?

The other examples have "This example code is in the public domain". Is that ok? Or would you prefer it to have an open source license?
 
PD is fine... 'oddson' or "Leif Oddson" if you want to credit.

Is there a better way to initialize an arbitrary number of BOUNCE or RAR objects that the comment trick in my code?
 
Hello! Thanks for sharing this @oddson, it's the best example I have found for my needs.
I have made a few mofications to suit my needs however I think the way I coded things is a bit inneficient and I was wondering if i could get some help.


My objective is to have an LED per Button, every time a button is pressed its corresponding LED turns On and stays On until another button is pressed and the rest turns Off, basically a Ableton Live Scene trigger where LED's On is just a visual indicatior of what Scene is active.

Adding the LED info
Code:
const int D_LEDS = 2; // How many Pins for LEDS 
const int DIGITAL_LEDS[D_LEDS] = {11,12}; // Numbers of the LED Pins

Digital Section:
Code:
//************DIGITAL SECTION**************
//Modificaions : Changed the order of fallingEdge and risingEdge because note off was trigeering the note instead of note on.
void getDigitalData(){
  for (int i=0;i<D_PINS;i++){
  digital[i].update();
    if (digital[i].risingEdge()) {
      usbMIDI.sendNoteOn(note[i], ON_VELOCITY, channel);
      digitalWrite(DIGITAL_LEDS[0], LOW); // TURNS OFF DIGITAL_LEDS 1 - PIN 11
      digitalWrite(DIGITAL_LEDS[1], LOW); // TURNS OFF DIGITAL_LEDS 2 - PIN 12
    }
    // Note Off messages when each button is released
    if (digital[i].fallingEdge()) {
      usbMIDI.sendNoteOff(note[i], 0, channel);
      digitalWrite(DIGITAL_LEDS[i], HIGH); // TURN ON THE PRESSED BUTTON EQUIVALENT LED WHEN BUTTON IS RELEASED.
      Serial.print(" Note OFF ");
    }
  }
}

It works fine but as i add more LED's using "DIGITAL_LEDS[number], LOW" a bunch of times seems inneficint, is there a better way to go about this?
 
If there is always one, and only one mode active you can store it as a byte/integer variable.

(This may be easier than checking a pin output when you try use the mode info... what do you use it for?)

Just have the falling edge turn off the old and turn on the new and set the mode to the new index. But for a small number of modes you could just set all to low before setting the new value.

Re swapping rising/falling... are you using active low switching?
 
Oh man, I am a big noob, I just started learning 2 days ago, tbh i dont know most of what you're asking. :D

If there is always one, and only one mode active you can store it as a byte/integer variable.
(This may be easier than checking a pin output when you try use the mode info... what do you use it for?)
Yes, only one active at a time.
I'll do some research on byte/integer variables to learn what it's about and how it works.
I think i left it like that by mistake after testing some stuff and forgot about it.


Just have the falling edge turn off the old and turn on the new and set the mode to the new index. But for a small number of modes you could just set all to low before setting the new value.
So bacasically this:
void getDigitalData(){
for (int i=0;i<D_PINS;i++){
digital.update();
if (digital.risingEdge()) {
usbMIDI.sendNoteOn(note, ON_VELOCITY, channel);
}
// Note Off messages when each button is released
if (digital.fallingEdge()) {
usbMIDI.sendNoteOff(note, 0, channel);
digitalWrite(11, LOW); // turn off list
digitalWrite(12, LOW); // turn off list
digitalWrite(DIGITAL_LEDS, HIGH);
}
}
}

?
I wont ever use many Leds so i guess that works.


Re swapping rising/falling... are you using active low switching?
I have no idea, if i am is that bad? How do i check if I am? Sorry for the dumb question.
 
Set

digitalWrite(DIGITAL_LEDS, HIGH );

on falling edge.

Set the previous high to low

digitalWrite(DIGITAL_LEDS[lastHigh], LOW);

Record the current pin as last high

lastHigh = i ;

So you can turn it off on the next.

If I'm following you
 
Actually set new to HIGH after turning old LOW to avoid turning LED off if selected twice in sequence.
 
This is a my second venture into the world of Teensy midi controllers. First time I used a Teensy 3.1 to control and repurpose and old AVIOM mixer with 20 buttons and an added 16x2 LCD. I built that about two years ago and it runs solid all the time. This time I am building a Midi Foot Controller to control an Amp, Ableton Live, and a Helix. This code is working well for the main control, I am using 8 Potentiometers and 10 buttons. This leads me to my question; I would like to add banks to this code but am not quite sure how to go about implementing the banks in the code. I would like to have different values for midi notes or CC messages selected with each bank select. Right now I am planning on four banks with an indicator LEDs to show what bank I am on. Could some one point me in the right direction?

This is what I want to do but not sure how or where I need to modify code....

- One momentary switch (push button) and multiple LEDs

Pressing the button increments the bankSetting number and turns on the respective LED,
When starting the program, bankSetting 1 is selected and LED 1 is on,
When the button is pressed, bankSetting 2 is selected, LED 1 turns off and LED 2 turns on,
When the button is pressed again, bankSetting 3 is selected, LED 2 turns off and LED 3 turns on.
When the last bankSetting is selected, and the button is pressed,
bankSetting 1 is selected, the last LED turns off and LED 1 turns on.

Thanks
 
I'm happy to help if you need it... subject to limited availability ATM.

The idea is there is a matrix of note values and they are looked up by the current value of the indicator variable 'i' within the 'for' loop (as before) as well as the current mode or 'bank' value.

Other than that you just need to initialize it with your desired values and then have a button or two for bank selection. If one you increment the bank and reset to zero when needed.

If you have a lot of banks you'll want an indicator at some point... a seven or 12 segment LED works for a small number of banks... but then you'll want to be able to configure without a recompile... and to save values in eeprom.... and it never ends.
 
Code Advice Needed

i apologies in advance for the noob questions, i tried to read and learn as much as i could, but wanted some clarifications before i blindly start editing the code.
My goal is to Use a Teensy LC to work with 2 linear potentiometers & 14 momentary buttons.
It appears to me that your code is set up for 6 & 3 respectively.
I think my use case is possible with the teensy, but just wanted to check to see if there is any limitations im unaware of.
i wanted to expand on the code to work with my use case, but im not exactly sure where to start. i also have no idea if i should start with this code or the code you commented with the CD74HC4067 breakout board used for MUX channels.

Any advice or direction would be beyond appreciated.
Thank you!!!!!!
 
The Teensy LC has enough pins to look at 2 pots and 13 buttons without any multiplexer unless you want to add other hardware.

The example Many_Button_Knobs can easily be tweaked to suit. Are you planning to use usbMIDI or DIN midi?
 
I'm wanting to use usbMIDI. I'll go ahead and look into the example and see what i can figure out.
thank you for the fast response
 
So i changed these sections and the code seems to compile fine. I plan on testing it with a LC in the next few days
const int channel = 1; // MIDI channel
const int A_PINS = 2; // number of Analog PINS
const int D_PINS = 14; // number of Digital PINS
const int ON_VELOCITY = 99; // note-one velocity sent from buttons (should be 65 to 127)

// define the pins you want to use and the CC ID numbers on which to send them..
const int ANALOG_PINS[A_PINS] = {A0,A1};
const int CCID[A_PINS] = {21,22};

// define the pins and notes for digital events
const int DIGITAL_PINS[D_PINS] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13};
const int note[D_PINS] = {60,61,62,63,64,65,66,67,68,69,70,71,72};
const int BOUNCE_TIME = 7; // 5 ms is usually sufficient
const boolean toggled = true;
{ANALOG_PINS[0],true},
{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(DIGITAL_PINS[5], BOUNCE_TIME),
Bounce(DIGITAL_PINS[6], BOUNCE_TIME),
Bounce(DIGITAL_PINS[7], BOUNCE_TIME),
Bounce(DIGITAL_PINS[8], BOUNCE_TIME),
Bounce(DIGITAL_PINS[9], BOUNCE_TIME),
Bounce(DIGITAL_PINS[10], BOUNCE_TIME),
Bounce(DIGITAL_PINS[11], BOUNCE_TIME),
Bounce(DIGITAL_PINS[12], BOUNCE_TIME),
Bounce(DIGITAL_PINS[13], BOUNCE_TIME),
};
 
Looks right to me. And LC is the obvious choice for MIDI out of the current line-up because it's the lowest cost.

MIDI is not very demanding of resources and human interactions with controls are glacial in timescale compared with the speed at which T3.x run.

Changing the buttons to Control Change instead of notes is simple as it adding toggle behaviour as an option. There are a few extensions of this code on the forum.
 
Thanks for giving it a look over! Glad to know I'm headed down the right track.

I will definitely look into adding control changes to buttons at some point. With similar toggle behaviors would it be possible/practical to toggle between different banks of notes? Now I'm thinking It would be ideal to have two banks of notes i could toggle between for some buttons, and two banks of Control changes to toggle between for the remaining buttons. I know I'm getting ahead of myself, but eventually id like to implement something like that.
 
I got everything working fine with the 14 button set up, but i ran into some issues after adding an additional 6. Basically pins 0-15 work fine, and 18-21 work fine, but 16 and 17 are acting up( all are notes or knobs, no control change buttons have been added). ive tested the actual buttons and they work, but nothing happens in my DAW when i hit 16 or 17. I have found that if i hit the button attached to pin 0 then it triggers 3 notes in my DAW. im not sure if some how 16 and 17 are being sent to 0. Any help would be greatly appreciated.
Here is the edited section of my current code.
Code:
// ******CONSTANT VALUES******** 
// customize code behaviour here!
const int channel = 1; // MIDI channel
const int A_PINS = 2; // number of Analog PINS
const int D_PINS = 20; // number of Digital PINS
const int ON_VELOCITY = 99; // note-one velocity sent from buttons (should be 65 to  127)

// define the pins you want to use and the CC ID numbers on which to send them..
const int ANALOG_PINS[A_PINS] = {A0,A1};
const int CCID[A_PINS] = {21,22};

// define the pins and notes for digital events
const int DIGITAL_PINS[D_PINS] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,16,17,18,19,20,21};
const int note[D_PINS] = {60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79};
const int BOUNCE_TIME = 5; // 5 ms is usually sufficient
const boolean toggled = true;


//******VARIABLES***********
// a data array and a lagged copy to tell when MIDI changes are required
byte data[A_PINS];
byte dataLag[A_PINS]; // when lag and new are not the same then update MIDI CC value


//************INITIALIZE LIBRARY OBJECTS**************
// not sure if there is a better way... some way run a setup loop on global array??
// use comment tags to comment out unused portions of array definitions

// initialize the ReponsiveAnalogRead objects
ResponsiveAnalogRead analog[]{
  {ANALOG_PINS[0],true},
  {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(DIGITAL_PINS[5], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[6], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[7], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[8], BOUNCE_TIME), 
  Bounce(DIGITAL_PINS[9], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[10], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[11], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[12], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[13], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[16], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[17], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[18], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[19], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[20], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[21], BOUNCE_TIME),
};
 
Code:
// 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(DIGITAL_PINS[5], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[6], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[7], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[8], BOUNCE_TIME), 
  Bounce(DIGITAL_PINS[9], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[10], BOUNCE_TIME),
[COLOR="#FF0000"]  Bounce(DIGITAL_PINS[11], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[12], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[13], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[16], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[17], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[18], BOUNCE_TIME),[/COLOR]
  Bounce(DIGITAL_PINS[19], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[20], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[21], BOUNCE_TIME),
};
Somewhere in here perhaps ;)

I was disappointed when I couldn't figure out how to make this part of the code responsive to changes in the number of inputs. Since then someone posted how it's done in an unrelated topic but I've not integrated it into this code.
 
I see now... DIGITAL_PINS[16] is going to reference pin 18 as '16' is the position in the array and not the pin number inside the array.

Your pin and array index values start the same but they are not the same.

Just keep going in the numbering here 14 to 19 (the number of pins minus one, zero indexed).

You could try
Code:
  for (int i = 0; i < D_PINS ; i++) {
      digital[i] = new Bounce(DIGITAL_PINS[i], BOUNCE_TIME); 
  }
 
Last edited:
Thanks Man! i got it working. I tried not omitting pins 14 and 15, and instead getting rid of 20 and 21( ran into the same problem). When i added the full 0-21 digital pins everything worked fine.

Code:
// initialize the ReponsiveAnalogRead objects
ResponsiveAnalogRead analog[]{
  {ANALOG_PINS[0],true},
  {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(DIGITAL_PINS[5], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[6], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[7], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[8], BOUNCE_TIME), 
  Bounce(DIGITAL_PINS[9], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[10], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[11], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[12], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[13], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[14], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[15], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[16], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[17], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[18], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[19], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[20], BOUNCE_TIME),
  Bounce(DIGITAL_PINS[21], BOUNCE_TIME),
};

Im not sure if this is "optimal", but it works and im thrilled. Thanks again Oddson!
 
If you're actually using pins 14 and 15 you need to fix this too:

const int DIGITAL_PINS[D_PINS] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,16,17,18,19,20,21};


and D_PINS = 22


And if you're really using 0 - n pins in order you could just skip the DIGITAL_PINS array and put the number in directly

Code:
...  
  Bounce(18, BOUNCE_TIME),
  Bounce(19, BOUNCE_TIME),
  Bounce(20, BOUNCE_TIME),
  Bounce(21, BOUNCE_TIME),

And if the 'new' keyword makes the loop work (untested) then the whole block becomes

Code:
  for (int i = 0; i < D_PINS ; i++) {
      digital[i] = new Bounce(i BOUNCE_TIME); // initialize
  }
 
Back
Top