Button Code Explanation:
While it may seem overly-simplified and even non-sensical at first glance, the button code utilized for MIDI keyboards, and all devices with keyboards is very easy to use, and very logical in its construction. In order to best understand the code, let’s walk through how it is written, and then observe it in action it from a very practical perspective: pressing a note and releasing it after a few short moments.
The first thing we need to do is establish some variables that we’ll use throughout the code. These variables are buttonState and lastButtonState. “buttonState” describes the status of the button, pushed or un-pushed, or in coding language, “HIGH” or “LOW,” respectively. lastButtonState also provides information on whether the button is pushed or un-pushed, but instead of the present status of the button as buttonState, lastButtonState instead logically describes the immediately previous status of the button. This will make more sense when we dive into the code, but for right now, imagine pressing a key and then releasing it immediately after. After you’ve released it, the current buttonState is “LOW,” since the key is no longer pushed, but the previous status of the button, the lastButtonState is “HIGH” because just a moment ago, the key was pressed. These variables are designed to help the code understand when you’re pressing the button and when you’re releasing it, and subsequently, when to sound the note and when to stop it. Both buttonState and lastButtonState are described as “bool” rather than “int” because they are variables that can only be described as “HIGH” or “LOW”, “TRUE” or “FALSE” or 0 and 1, rather than a range of numeric values like integers. Set both of these variables to LOW to start with.
Now that we’ve established buttonState and lastButtonState, let’s take a look at the few lines of code in the loop. We first set the buttonState and lastButtonState equal to one another. This means that whenever the buttonState is HIGH the lastButtonState will also be HIGH and whenever the buttonState is LOW then the lastButtonState will be LOW. Next, we set the buttonState equal to the status of the physical button on the breadboard through a digitalRead. So, if you’re physically pushing or not pushing the button, the buttonState will change to HIGH or LOW respectively. Then, we create a conditional that will sound the note, that requires two things to be true: the buttonState must be HIGH and the lastButtonState must be LOW. This is because we want the note to sound when we play it initially, but as we can see from the code, the current buttonState is not set equal to the state of the physical button until after the buttonState and lastButtonState are set equal to each other. That means that because the buttonState doesn’t get a reading of the button at first and is instead set to LOW, that the lastButtonState is also LOW until it loops again.
The reason that the pitch will not sound again and sustain instead while the key is being held down is because the conditional for sounding the note will no longer be true the second time around. Because the button is pressed still, the buttonState is still HIGH, and will set the lastButtonState to HIGH as well. This does not meet the conditions of the conditional statement to sound the note, which are that the buttonState must be HIGH and the lastButtonState must be LOW, which is no longer true.
So, we’re almost there, we just have to add two minor things to make the keyboard work. The first is that we want to create a conditional to turn the note off, so that it’s not continuously sounding forever. In order to do this, we have to capture the exact moment at which we release the button, so that the button is not currently pressed, though it just was. In coding terms, the buttonState is LOW but the lastButtonState, meaning the most immediate previous status of the button, is HIGH. We create a conditional statement where these two conditions must be met.
The last thing we should add in order to avoid any electrical noise is add a delay of about 5 milliseconds to the end of each conditional statement to avoid any weird voltage flipping.
Video:
Code:
int blueLed = 7; // make variable for each of the components on the breadboard
int greenLed = 8; // create bool for buttonState and lastButtonState of each note
int whiteLed = 9;
int yellowLed = 10;
int buttonPin1 = 33;
int buttonPin2 = 34;
int buttonPin3 = 35;
int buttonPin4 = 36;
bool buttonState1 = LOW;
bool lastButtonState1 = LOW;
bool buttonState2 = LOW;
bool lastButtonState2 = LOW;
bool buttonState3 = LOW;
bool lastButtonState3 = LOW;
bool buttonState4 = LOW;
bool lastButtonState4 = LOW;
void setup() {
pinMode(blueLed, OUTPUT); // set pinMode for the digital inputs
pinMode(greenLed, OUTPUT);
pinMode(whiteLed, OUTPUT);
pinMode(yellowLed, OUTPUT);
pinMode(buttonPin1, INPUT);
pinMode(buttonPin2, INPUT);
pinMode(buttonPin3, INPUT);
pinMode(buttonPin4, INPUT);
}
void loop() {
checkButton1(); // abbreviated functions for each of the keys
checkButton2();
checkButton3();
checkButton4();
}
void checkButton1() {
lastButtonState1 = buttonState1; // first key: C note and blue LED
buttonState1 = digitalRead(buttonPin1);
if (lastButtonState1 == LOW and buttonState1 == HIGH) {
usbMIDI.sendNoteOn(60, 127, 1);
digitalWrite(blueLed, HIGH);
delay(5);
} else if (lastButtonState1 == HIGH and buttonState1 == LOW) {
usbMIDI.sendNoteOff(60, 0, 1);
digitalWrite(blueLed, LOW);
delay(5);
}
}
void checkButton2() {
lastButtonState2 = buttonState2; // second key: E note and green LED
buttonState2 = digitalRead(buttonPin2);
if (lastButtonState2 == LOW and buttonState2 == HIGH) {
usbMIDI.sendNoteOn(64, 127, 1);
digitalWrite(greenLed, HIGH);
delay(5);
} else if (lastButtonState2 == HIGH and buttonState2 == LOW) {
usbMIDI.sendNoteOff(64, 0, 1);
digitalWrite(greenLed, LOW);
delay(5);
}
}
void checkButton3() { // third key: G note and white LED
lastButtonState3 = buttonState3;
buttonState3 = digitalRead(buttonPin3);
if (lastButtonState3 == LOW and buttonState3 == HIGH) {
usbMIDI.sendNoteOn(67, 127, 1);
digitalWrite(whiteLed, HIGH);
delay(5);
} else if (lastButtonState3 == HIGH and buttonState3 == LOW) {
usbMIDI.sendNoteOff(67, 0, 1);
digitalWrite(whiteLed, LOW);
delay(5);
}
}
void checkButton4() { // fourth key: B note and yellow LED
lastButtonState4 = buttonState4;
buttonState4 = digitalRead(buttonPin4);
if (lastButtonState4 == LOW and buttonState4 == HIGH) {
usbMIDI.sendNoteOn(71, 127, 1);
digitalWrite(yellowLed, HIGH);
delay(5);
} else if (lastButtonState4 == HIGH and buttonState4 == LOW) {
usbMIDI.sendNoteOff(71, 0, 1);
digitalWrite(yellowLed, LOW);
delay(5);
}
}
Serial Monitor and Potentiometer Video:
Serial Monitor and Potentiometer Code:
int potPin = A13; // establish variables
int ccVal = 0;
int lastCCVal = 0;
void setup() { // serial monitor setup
Serial.begin(9600);
}
void loop() { // checkPot function in the loop
checkPot();
}
void checkPot() { // check potentiometer function
usbMIDI.read();
lastCCVal = ccVal;
ccVal = map(analogRead(potPin), 0, 1023, 10, 20); //change range of values from 0-1023 and 10-20
if(ccVal != lastCCVal) { //send cc's only when the potentiometer position changes
Serial.println(ccVal);
usbMIDI.sendControlChange(1, ccVal, 1);
}
}