My button only MIDI controller code isn't working (HELP!!)

Hey all, I’m trying to build a midi foot controller and to get it to do exactly what I’m looking for I decided to write my own code from scratch. I’m confident that the issue is easy to solve for someone with more coding knowledge than me but I seem to be totally stuck.

I’m using the MIDI.h library but am really only concerened with sending control change messages. Because I am a total novice when it comes to electronics and coding I started off with a 3 button prototype. I wanted to make the first button work as a sort of shift button, adding between 0 to 2 to the message value so I could effectively have the other two buttons act as six different buttons depending on the state of the first. Unsurprisingly my efforts to convert those aspirations to working code have proved uneffective. After booting up the midi to serial converter it just sends a control change value of 55 and 60 alternatively (seen in attached picture). Pressing the buttons does nothing to affect the messages. Heres my code, sorry there are no comments but if you have any questions I’d be happy to answer.

#include <MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE();

const int button1 = 2;
const int button2 = 3;
const int button3 = 4;

int fctr=0;
int button1Status = 0;
int button2Status = 0;
int button3Status = 0;

int midiVal1 = 55;
int midiVal2 = 60;

int buttonState1 = 0;
int buttonState2 = 0;
int buttonState3 = 0;

void setup() {

  MIDI.begin(MIDI_CHANNEL_OMNI);
  Serial.begin(115200);     
  pinMode(button1, INPUT);
  pinMode(button2, INPUT);
  pinMode(button3, INPUT);
}


void loop () {
 
  buttonState1 = digitalRead(button1);

  if ((fctr == 0)&& buttonState1 == HIGH){
      fctr = 1;
    } 
  if ((fctr == 1)&& buttonState1 == HIGH){
      fctr = 2;
    }
   if ((fctr == 2)&& buttonState1 == HIGH){
      fctr = 0;
    }

  buttonState2 = digitalRead(button2);

  if (buttonState2 = HIGH){
    MIDI.sendControlChange((midiVal1 + fctr), 127, 1);
  } else {
    MIDI.sendControlChange((midiVal1 + fctr), 0, 1);
  }
  
  buttonState3 = digitalRead(button3);

  if (buttonState3 = HIGH){
    MIDI.sendControlChange((midiVal2 + fctr), 128, 1);
  } else {
    MIDI.sendControlChange((midiVal2 + fctr), 0, 1);
  }
}

The logic for your buttons is off: you have to detect when a button becomes pressed, not when it is pressed. Otherwise it’ll continuously send MIDI messages while you press the buttons.
See the state change example that comes with the IDE.

Since you don’t mention any resistors, I assume your push button inputs are floating. Use pinMode(buttonX, INPUT_PULLUP) to enable the internal pull-up resistor. Connect your buttons between the Arduino input pin and ground. Reading HIGH means that the button is released, reading LOW means that the button is pressed.

A single = is used for assignment, double == are used for comparison. if (buttonState3 = HIGH) will assign HIGH to buttonState3 and then evaluate to true, which is not what you want.

MIDI messages have 7-bit data bytes, so the maximum value you can send is 0b0111’1111 or 127. 128 is not a valid MIDI value.


That being said, what you’re trying to do can easily be done using the Control Surface library:

[color=#5e6d03]#include[/color] [color=#434f54]<[/color][b][color=#d35400]Control_Surface[/color][/b][color=#434f54].[/color][color=#000000]h[/color][color=#434f54]>[/color] [color=#434f54]// Include the Control Surface library[/color]

[color=#434f54]// Instantiate a MIDI interface for use with the Hairless MIDI<->Serial bridge.[/color]
[b][color=#d35400]HairlessMIDI_Interface[/color][/b] [color=#00979c]midi[/color][color=#000000];[/color]

[color=#434f54]// Instantiate 3 Banks, with one address per bank. [/color]
[color=#434f54]// Banks can change the address of the buttons below.[/color]
[color=#434f54]// Having 3 banks means that each button has 3 possible addresses.[/color]
[color=#434f54]// 1 address per bank means that changing the bank increments the [/color]
[color=#434f54]// address (controller number) by 1.[/color]
[b][color=#d35400]Bank[/color][/b][color=#434f54]<[/color][color=#000000]3[/color][color=#434f54]>[/color] [color=#000000]bank[/color][color=#000000]([/color][color=#000000]1[/color][color=#000000])[/color][color=#000000];[/color]
[color=#434f54]//   │       └───── number of addresses per bank[/color]
[color=#434f54]//   └───────────── number of banks[/color]

[color=#434f54]// Instantiate a Bank selector to control which one of the three Banks is active.[/color]
[color=#434f54]// Pressing the push button will select the next bank, adding 1 to the address[/color]
[color=#434f54]// of the two buttons below. After 3 pushes, you're back at Bank 1.[/color]
[b][color=#d35400]IncrementSelector[/color][/b][color=#434f54]<[/color][color=#000000]3[/color][color=#434f54]>[/color] [color=#000000]selector[/color] [color=#434f54]=[/color] [color=#000000]{[/color]
    [color=#000000]bank[/color][color=#434f54],[/color]       [color=#434f54]// Bank to manage[/color]
    [color=#000000]2[/color][color=#434f54],[/color]          [color=#434f54]// push button pin[/color]
[color=#000000]}[/color][color=#000000];[/color]

[color=#434f54]// Instantiate two push buttons that send Control Change events when pressed[/color]
[color=#434f54]// or released.[/color]
[b][color=#d35400]Bankable[/color][/b][color=#434f54]:[/color][color=#434f54]:[/color][b][color=#d35400]CCButton[/color][/b] [color=#000000]button1[/color] [color=#434f54]=[/color] [color=#000000]{[/color]
  [color=#000000]bank[/color][color=#434f54],[/color] [color=#434f54]// bank determines the address offset, relative to the base address[/color]
  [color=#000000]3[/color][color=#434f54],[/color]    [color=#434f54]// push button pin[/color]
  [color=#000000]55[/color][color=#434f54],[/color]   [color=#434f54]// base address (controller number)[/color]
[color=#000000]}[/color][color=#000000];[/color]
[b][color=#d35400]Bankable[/color][/b][color=#434f54]:[/color][color=#434f54]:[/color][b][color=#d35400]CCButton[/color][/b] [color=#000000]button2[/color] [color=#434f54]=[/color] [color=#000000]{[/color]
  [color=#000000]bank[/color][color=#434f54],[/color] [color=#434f54]// bank determines the address offset, relative to the base address[/color]
  [color=#000000]4[/color][color=#434f54],[/color]    [color=#434f54]// push button pin[/color]
  [color=#000000]60[/color][color=#434f54],[/color]   [color=#434f54]// base address (controller number)[/color]
[color=#000000]}[/color][color=#000000];[/color]

[color=#00979c]void[/color] [color=#5e6d03]setup[/color][color=#000000]([/color][color=#000000])[/color] [color=#000000]{[/color]
  [b][color=#d35400]Control_Surface[/color][/b][color=#434f54].[/color][color=#d35400]begin[/color][color=#000000]([/color][color=#000000])[/color][color=#000000];[/color] [color=#434f54]// Initialize Control Surface[/color]
[color=#000000]}[/color]

[color=#00979c]void[/color] [color=#5e6d03]loop[/color][color=#000000]([/color][color=#000000])[/color] [color=#000000]{[/color]
  [b][color=#d35400]Control_Surface[/color][/b][color=#434f54].[/color][color=#5e6d03]loop[/color][color=#000000]([/color][color=#000000])[/color][color=#000000];[/color] [color=#434f54]// Update the Control Surface[/color]
[color=#000000]}[/color]

Pieter

Hey thank you so much! This library is amazing and perfect for what I'm looking to do. I'm just curious, how would I make it so the buttons are toggle rather than momentary?

You can use CCButtonLatched<3> instead of CCButton. (3 is the number of banks again, it has to know how many different latched states it has to remember.)
The full list of available MIDI elements can be found here.

Perfect! Thank you again! I appreciate the reference sheet and I looked through the examples in the actual library. One thing that I wanted to do was have an RGB LED that corresponded to which bank I had selected. I think I found the command (IncrementSelectorLEDs) but I don’t know how exactly to set it up so it does what I’m looking for it to do. Thanks again for all your help.

The clean solution would be to define a Selector callback that gets called when the bank selection changes. That's explained here: Control Surface: Custom-Selector-Callback.ino

Alternatively, if you look at the Bank documentation, you'll see that there is a getSelection() method. It returns the index of the bank that is currently active. In your case, it's either 0, 1, 2. You can use that to change the LED state.

Alright what am I doing incorrectly here. I don’t really understand how to use the getSelection command I tried using an if statement to turn on an LED (my most recent attempt can be seen in the void loop section of the code).

Is there some way to get the getSelection to return a numerical value that I can then use in an if statement? I tried looking on the github page that you linked but all I was able to find was this

virtual setting_t getSelection() const { return bankSetting; }

uint8_t getTracksPerBank() const { return tracksPerBank; }

uint8_t getOffset() const { return getSelection() * getTracksPerBank(); }

and that really didn’t make a whole lot of sense to me. Heres my code. And thank you again! I really feel like I’m learning a whole lot and it’s really nice to have someone I can ask questions too

#include <Control_Surface.h> // Include the Control Surface library

int redPin = 9;
int greenPin = 10;
int bluePin = 11;
 
//comment this line if using a Common Cathode LED
#define COMMON_ANODE

// Instantiate a MIDI interface for use with the Hairless MIDI<->Serial bridge.
HairlessMIDI_Interface midi;

// Instantiate 3 Banks, with one address per bank.
// Banks can change the address of the buttons below.
// Having 3 banks means that each button has 3 possible addresses.
// 1 address per bank means that changing the bank increments the
// address (controller number) by 1.
Bank<3> bank(1);
//   │       └───── number of addresses per bank
//   └───────────── number of banks

// Instantiate a Bank selector to control which one of the three Banks is active.
// Pressing the push button will select the next bank, adding 1 to the address
// of the two buttons below. After 3 pushes, you're back at Bank 1.
IncrementSelector<3> selector = {
    bank,       // Bank to manage
    2,          // push button pin
};

// Instantiate two push buttons that send Control Change events when pressed
// or released.
Bankable::CCButtonLatched<3> button1 = {
  bank, // bank determines the address offset, relative to the base address
  3,    // push button pin
  55,   // base address (controller number)
};
Bankable::CCButtonLatched<3> button2 = {
  bank, // bank determines the address offset, relative to the base address
  4,    // push button pin
  60,   // base address (controller number)
};

void setup() {
  Control_Surface.begin(); // Initialize Control Surface
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);  
}

void loop() {
  Control_Surface.loop(); // Update the Control Surface
  if (getSelection(bank)==0) setColor(14, 130, 150);
  if (getSelection(bank)==1) setColor(110, 0, 45);
  if (getSelection(bank)==2) setColor(200, 0, 0);
}

void setColor(int red, int green, int blue)
{
  #ifdef COMMON_ANODE
    red = 255 - red;
    green = 255 - green;
    blue = 255 - blue;
  #endif
  analogWrite(redPin, red);
  analogWrite(greenPin, green);
  analogWrite(bluePin, blue);  
}

The getSelection function is a member function of the Bank class.
See this tutorial, for example.
Maybe a more familiar example would be Serial.println():
Serial is an instance of the HardwareSerial class, and that class defines a println member function.
Similarly, bank (cfr. Serial) is an instance of the Bank class (cfr. HardwareSerial), and that class defines a getSelection member function (cfr. println.

You call member functions using the member access operator (.), so the right syntax would be:

 if (bank.getSelection() == 0) setColor(14, 130, 150);