DIY 6 button MIDI foot controller that sends MIDI Program Change + LCD

..so to avoid confusion largely on my part, I stared this new topic (related to this thread Serial MIDI and Arduino MIDI - Audio - Arduino Forum) with an appropriate title for easy searching by beginners like me..

I have started this project and was able to make the buttons turn on LEDs as you push them (buttons), much to my delight as a complete arduino Noob. But it's way too far from my aim. My ultimate goal would be 4 buttons that send Program Change 0 to 3 with LED and 2 bank up and down buttons that choose from 10 banks in a guitar amplifier sim program and a 2 x 16 LCD that will show the name of the banks as you scroll up and down.

I am using an Arduino Micro and would send over USB. So far, this the simpliest starting code that worked with the buttons and LEDS. I already included libraries and lines (instance) from the kindhearted Pieter:

#include <frequencyToNote.h>
#include <MIDIUSB.h>
#include <MIDIUSB_Defs.h>
#include <pitchToFrequency.h>
#include <pitchToNote.h>

#include <MIDI.h>
#include <midi_UsbTransport.h>

int ledPin1 = A0;  //
int ledPin2 = A1; //
int buttonPin1 = 8;    //
int buttonPin2 = 9;    //
int buttonState1 = 0;  // 
int buttonState2 = 0;


static const unsigned sUsbTransportBufferSize = 16;
typedef midi::UsbTransport<sUsbTransportBufferSize> UsbTransport;

UsbTransport sUsbTransport;

MIDI_CREATE_INSTANCE(UsbTransport, sUsbTransport, MIDI);

void setup() {   

  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(buttonPin1, INPUT);
  pinMode(buttonPin2, INPUT);
}
 
void loop() {

  buttonState1 = digitalRead(buttonPin1);
  if(buttonState1 == HIGH) {
    digitalWrite(ledPin1, HIGH);
    ledOffAll();
   digitalWrite(ledPin1, 255);
  
   }
   buttonState2 = digitalRead(buttonPin2);
   
  if(buttonState2 == HIGH)
  {
    digitalWrite(ledPin2, HIGH); 
    ledOffAll();
    digitalWrite(ledPin2, 255);}
}

   void ledOffAll() {
    digitalWrite(ledPin1, 0);       
    digitalWrite(ledPin2, 0);
  }

As a total noob, this would be my preferred approach - the lines of the codes are presented in a beginner friendly chunks. My 1st question is, how will I implement the MIDI part in that code. or is it even possible?

I am using an Arduino Micro and would send over USB. So far, this the simpliest starting code that worked with the buttons and LEDS.

In the words of the old joke, if I was going there I wouldn't start from here.

I would in fact start from here.

// Simple MIDI output of program message through USB
// By Grumpy_Mike August 2019

#include "MIDIUSB.h"

// to add more then simply add more pins tothe list
byte ledPin[] = {A0,A1};  // list of led pins
byte buttonPin[] = {8,9};    // list of buttons pins
byte buttonState[] = {0,0};  // start off with off
byte lastButtonState[] = {0,0};  // start off with off
byte programChanges[] = {1,2};  // list of program change messages to send

byte channel = 0; // for musicians this is channel 1

void setup() {
  for(int i =0;i< sizeof(buttonPin); i++){ // initialise input pins  
    pinMode(buttonPin[i], INPUT_PULLUP); // wire all buttons between input and ground
    pinMode(ledPin[i], OUTPUT);
  }
}
 
void loop() {
  for(int i =0;i< sizeof(buttonPin); i++){  // look at all pins
  buttonState[i] = digitalRead(buttonPin[i]); 
  if(buttonState[i] == LOW && lastButtonState[i] == HIGH) { // remember how you wired your buttons LOW = pressed
    ledOffAll();
    digitalWrite(ledPin[i],HIGH);
    programChange(channel, programChanges[i]); // send the program change message
    MidiUSB.flush();
   }
   lastButtonState[i] = buttonState[i];
  }
}

void ledOffAll() {
    for(int i = 0 ; i< sizeof(ledPin); i++){
     digitalWrite(ledPin[i], LOW); 
    }
}

void programChange(byte channel, byte patch) {
  midiEventPacket_t event = {0x0C, 0xC0 | channel, patch};
  MidiUSB.sendMIDI(event);
}

As a total noob, this would be my preferred approach - the lines of the codes are presented in a beginner friendly chunks.

I am sure you would actually want to learn something rather than have code that you repeat over and over ad nauseam.

When you find yourself repeating code over and over you need to learn about arrays. In this code there are arrays that hold the lists of the pins to use and what program change messages to send. To expand this to more pins / LEDs / Program messages simply add the extra numbers to the list. Make sure there is always an equal number of numbers in each list so you have a match between push button, LED to write and program change message to send.

There is also a state change detection so that you don't continuously keep sending program change messages while a button is pressed.

Note this code is written as if the buttons are wired up correctly, that is with no external resistors and the button wired between input and ground.

1 Like

Now if I wanted to add an LCD that page scrolls thru the 10 banks (the last 2 buttons being bank up and down switches), where will I insert the code?

All the action is here:-

if(buttonState[i] == LOW && lastButtonState[i] == HIGH) { // remember how you wired your buttons LOW = pressed
    ledOffAll();
    digitalWrite(ledPin[i],HIGH);
    programChange(channel, programChanges[i]); // send the program change message
    MidiUSB.flush();
   }

So if you want to send anything to an LCD put it here at the end of this section before the closing brace }

I have a suspicion it's going to be another array

Yes this time a character array. Avoid the use of String and use the C type of string ( the case of the S makes all the difference )
See:- char array handling guide for beginners - Programming Questions - Arduino Forum
and
string - Arduino Reference
You need the bit on "Arrays of strings". It does no harm to consolidate your knowledge of arrays as well:- array - Arduino Reference

To make your code more readable then write a function that handles the LCD print statements and insert a call to that in the action area.

What if the last two buttons will not be needing LEDs at all? They be bank up and down switches.

Add two dummy numbers to the list and then catch the value with an if statement so you don't set the LED for that. This needs doing in the action section, the setup function and the ledOffAll function.

In that function just subtract two from the loop limit section of the for loop:-

for(int i = 0 ; i< sizeof(ledPin)-2; i++){

in the other two you need:-

ledOffAll();
   if( i <= numberOfRealLEDs) { // only light up real LEDs
       digitalWrite(ledPin[i],HIGH);
   }
    programChange(channel, programChanges[i]); // send the program change message

In the setup function this will be round the pin mode instruction rather than the digital write.

super detailed as ever

I think at this stage you need it, but as you get more experienced, you will need less and less detail.

And how do I call the array in the loop?

You can’t call an array, you can only call functions. If you want to select an element of an array you give it an index in square brackets just like you have done before with those LED numbers.

Am I right to say that "buttonPushCounter" shouldn't be there and should be replaced by the "char *myString" array line?

Yes but don’t use the * at the start, this is the symbol for a pointer. You don’t need it when you use the array.

Try just printing the appropriate string to the serial monitor, to see if that bit is right. Then when it is change to the LCD.

Use the example at the bottom of this page

Don’t print a whole series of spaces first just pad out each string in the array with trailing spaces so they are all the same length.

Do I need to use another void variable to declare myString?

No, even given that there is no such thing as a void variable.

void means "the following function returns no value"

variable deceleration means "this is a variable I want to use later in the code" and optionally "when I use it, then the value it will have is this ..., until I issue an instruction to specifically change it."

In addition if a variable deceleration is inside a function, that variable can only be used inside that function, its scope is local ( to that function ).

if a variable deceleration is outside any function, that variable can be used in any function, its scope is global ( can be used over the whole code ).

I may have found something similar:

No, stop looking for code to do what you want to do.

You are having a hard enough time just making simple changes to simple code without having to burden yourself with trying to understand what a whole lot of other code does.

Just take the code you were working on with with what I told you to do in reply #8 and post it and we can work on where you went wrong. Remember I said:-

"Try just printing the appropriate string to the serial monitor, to see if that bit is right. Then when it is change to the LCD."

So I would expect you to post your code with an output to the serial monitor which in not a million miles from the example at the end of the page string - Arduino Reference

Can I use "const char" also

Why? In your case it makes absolutely no difference. The const word just means that any attempt to change this variable will flag up an error, but you are not changing it anyway.

What sort of idiot writes this sort of thing:-

PushButton presetButtons[] = {
  {2}, {3}, {4}, {5}, {6}, {7},  // ...
};

Grumpy_Mike:
Why? In your case it makes absolutely no difference. The const word just means that any attempt to change this variable will flag up an error, but you are not changing it anyway.

String literals should always be const. The C++ standard doesn't actually allow assigning them to non-const pointers. It only works on Arduino because someone made the terrible decision that all Arduino sketches should be compiled using -fpermissive ...

Grumpy_Mike:
What sort of idiot writes this sort of thing:-

PushButton presetButtons[] = {

{2}, {3}, {4}, {5}, {6}, {7},  // ...
};

This idiot.

They are objects, not integers, so I use uniform initialization. The reason is consistency, since objects may have more than one (optional) arguments to their constructors.

Pieter

They are objects, not integers, so I use uniform initialization

Being a bit picky aren’t you. It just looks like bad code, you could have put a comment as to why you did this. It makes no odds , doing it the conventional way would work just as well.
In my book this is one of those “software” things that software types think is so important but ultimately is not.

for Bank up and down can't override the state that it is (sending PC

So use an if statement to test when you have a bank change button and send a CC else send the normal PC message.

Again post your code.

Should i declare first that i'd like to send CC using button 5 and 6?

is it like this?

....If a button is pressed and it is 5 and 6, send CC.. else, send PC.

is it like this?

Yes.

Where did you get this from?

 MidiUSB.sendMIDI(midiEventPacket_t event = 0x0B, 0xB0 | channel, control, value);

It should be more like:-

    midiEventPacket_t event = {0x0B, 0xB0 | channel, control, value};
    MidiUSB.sendMIDI(event );

Honestly, I don't even know accepted commands in MidiUSB

Look at the MidiUSB.h file. At the bottom is a bit that says public:, all the calls you can make to the module follow that.

However, it is best to look at the examples in that folder as well to see how they are used.
Basically all you do is create a specific array which has a type midiEventPacket_t, this contains the details of what you want to send, then you send it. Sending it actually puts it in a buffer and the command will not actually be sent until the buffer is full. If you want that command to be sent immediately then you can call the flush method. It can be useful if you want to send a sequence of messages as fast as possible, put them in the buffer and only when you have all messages in the buffer then flush them.

You keep adding unnecessary libraries multiple times. Why are you doing this?

#include <frequencyToNote.h>
#include <MIDIUSB.h>
#include <MIDIUSB_Defs.h>
#include <pitchToFrequency.h>
#include <pitchToNote.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display
#include <MIDI.h>


// Simple MIDI output of program message through USB
// By Grumpy_Mike August 2019

#include "MIDIUSB.h"

You do not need the MIDI.h library having it in is only going to screw you up, and what is it with the pitchToNote.h, pitchToFrequency.h and frequencyToNote.h? Why are you including them? Who told you to do this? You see I didn't include those in my code so why add them.

My LiquidCrystal_I2C libiary is not the same as yours, this is a none standard library so where did you get it from? This is stopping me compiling your code on my machine.

You do not need the MIDI.h library having it in is only going to screw you up, and what is it with the pitchToNote.h, pitchToFrequency.h and frequencyToNote.h? Why are you including them? Who told you to do this? You see I didn't include those in my code so why add them.

My LiquidCrystal_I2C libiary is not the same as yours, this is a none standard library so where did you get it from? This is stopping me compiling your code on my machine.

They all came from the midiUSB lib. i guess I could weed out the useless ones.

Forgot where I got that i2c lib. standard one didnt work.

Thank you for the guidance.

They all came from the midiUSB lib.

Yes they might but there is no need to include them, they will be called up by the .h or .cpp file if needed. You have no need to include them specifically.

Separate question. When you said this,

Basically all you do is create a specific array

Shall I abandon the if-else statement that you first suggested? Or do they go together?

Shall I abandon the if-else statement that you first suggested? Or do they go together?

No they go together.

Does it have any bearing at all in the sketch?

Yes it is what you need to call if you want to send a control message. The variables you pass to this function define the channel it is sent on, the controller number and the value you want to set that controller to.

what is the equivalent of sending lets say CC#12?

What value? If you want to send a value of 65 to CC 12, then call that function

controlChange(0,12,65);

And don’t forget to flush, as your mother used to tell you.

...and kindly tell in what part of the sketch shall I insert the array and what it would look like. i mean, what kind of array.

Thanks again for the help.

I think you are miss understanding what I said.
The array is what you define in order to send a MIDI message.
It is this part

event = {0x0B, 0xB0 | channel, control, value}

Of this line

midiEventPacket_t event = {0x0B, 0xB0 | channel, control, value};

I was trying to tell you how you send a MIDI message, it doesn’t matter what message you want to send, they are all sent in this way.

ohh,, Thank you so much for your patience. I already did that yesterday but it kept looking for "function" before it. There must be some line that I'm missing. I'll post the code later (I'm away from my PC). Thanks a lot again :slight_smile: