Tuesday, April 15, 2014

RedBoard Tutorial 3: Simple MIDI Note Button Controller

Introduction
This tutorial series is intended for people that attended the Arduino / RedBoard Beginner's Workshop at Square Sounds Festival in Melbourne in April, 2014 (though it may be of use to others). This particular tutorial deals with how to make a very simple MIDI controller that generates a note by pressing a button. 




Hardware Setup
The hardware setup for this is relatively straightforward. However, it should be noted that this example will be using "pull up resistors" that are internal to the Red Board - and that these resistors can be externalised (thus making the programming slightly simpler but the physical circuit more complex). The button will be read via a digital input pin, which can be either HIGH or LOW (i.e. 1 or 0).

Let's start with a blank breadboard and a blank Red Board!

This is a simple push button. Normally, there is no connection between the two pairs of legs. When you push the button, a connection is made allowing current and voltage to pass through. We can use this to test whether or not the button has been pushed.

Place the button on the breadboard.


Add a connection between Arduino Red Board digital pin 2 and one leg of the button.



Add another connection between ground and the other leg of the button.



Pull Up Resistor
The above diagram represents the situation of a pull up resistor. The button element has two states - either connected (when the points marked "1" and "2" are joined i.e. the button is pushed down) or disconnected (when the points marked "1" and "2" are not joined i.e. the button is not pushed down).

If the button is up (i.e. disconnected), then the digital input pin is connected to the 5V signal via the 10k resistor. The digital input pin reads a HIGH state (which somewhat paradoxically corresponds to the button NOT being pressed).

If the button is down (i.e. connected), then the digital input pin will take the path of least resistance, and will be connected to the ground instead - as there is no resistor in place. The digital input pin reads a LOW state (which somewhat paradoxically corresponds to the button BEING pressed).

Without the 10k resistor and 5V path, the digital input pin would simply pick up noise whenever the button is disconnected - hence, the 10k resistors pulls up the digital input pin to 5V whenever there is no connection to ground via the button.

We could have built this circuit on the breadboard using the 10k resistor from the previous exercise. However, the Red Board has the pull up resistor and the 5V connection already on board - internally. There is no need to complicate the hardware setup.

Hence, the hardware setup is very straightforward.



Programming and Code
To actually read the value of the button, the Red Board must be programmed in some way. Let's summarise what the program should do:
• Read the current value of the button
• If the value is different than the previous time the button was read, send a musical note event
• If the button has gone from being de-pressed to being pressed, the musical note event should be a "note on" event, signifying the start of a note
• If the button has gone from being pressed to being de-pressed, the musical note event should be a "note off" event, signifying the end of a note

Although there are a few methods for achieving this, a (hopefully) easy to follow method is as follows:

Let's go through this code structure by structure and line by line.

The first two lines create two variables that are called "current_button_state" and "previous_button_state". This means that we are reserving two bytes within our working memory (RAM) that can be referred to by these names anywhere in our program.

These bytes can be written to, like so:
 current_button_state = 1;

And these bytes can be read from, like so:
some_other_variable = current_button_state;

However, in order to achieve any of that, they had to be defined. Because we've defined them before the setup() function, they are then available to us throughout the program structure.

In setup(), we first open up a serial port just like in RedBoard Tutorial 2 in the line Serial.begin(57600);. pinMode(2, INPUT) sets up digital pin two as a digital input pin.

The final line of setup() curiously performs a "digitalWrite" command - even though pin 2 is actually an input. This line activates the internal pull up resistor, as discussed previously.

The first line of current_button_state reads the value of the digital pin 2 (our button) as a data value. But there's a twist. The code is:
current_button_state = 1 - digitalRead(2);

Now, keep in mind that if the button is pressed, digitalRead(2) will equal 0 and if the button is not pressed, digitalRead(2) will equal 1. Therefore, we can subtract digitalRead(2) from 1 to flip around that logic.

If the button is pressed:
current_button_state = 1 - digitalRead(2) = 1 - 0 = 1

If the button is not pressed:
current_button_state = 1 - digitalRead(2) = 1 - 1 = 0

This makes more sense to us logically, and will be useful later on.

The next line of code is a conditional IF statement, and means that if the current state of the button is not the same as the previous state, execute some block of code that is contained within the curly brackets { and }.

The next five lines only occur if there is a change in the state of the button. Otherwise we would be sending continuous note on / note off events!

previous_button_state = current_button_state;
Serial.write(0x90);
Serial.write(60);
Serial.write(current_button_state * 127);
delay(5);

The first line out of these five is very important. It stores the current value of the button into the previous value. Thus, when the loop() code runs again, the previous value will be stored and a valid comparison to the (future) current value can be made.

The next three lines deal with generating a note on or note off message. Once again, details about the MIDI protocol can be found here, however a note message has three main components:

• A channel from 1 - 16
• A note (pitch) number from 0 - 127 where 60 is equal to middle C
• Each increment in pitch number is traditionally equal to a semitone
• A velocity value from 0 - 127 where 0 is off (silence / note-off) and 127 is maximum velocity
• Velocity may correspond to volume, tone colour and / or a number of other attributes

The format for this type of message is as follows:
byte 1: the value 144 plus the channel number from 0 to 15 (note: 144 as hexadecimal is 0x90)
byte 2: the pitch value from 0 to 127
byte 3: the velocity value from 0 to 127


Let's re-examine the three Serial.write statements:
Serial.write(0x90);
Serial.write(60);
Serial.write(current_button_state * 127);

Each note event will have:
• A channel number of 1
• A pitch value of 60 (middle C)
• A velocity that is determined by the current_button_state multiplied by 127.

The current button state is either 1 or 0, depending on if the button is pressed or de-pressed. If the current button state is pressed, then the formatted message will be: a note-on event with a channel of 1, a pitch of 60 and a velocity of 127. If the current button state is de-pressed, then the formatted message will be: a note-off event with a channel of 1, a pitch of 60 and a velocity of 0 .

A short delay ensures that mechanical false positive triggering of the button does not cause unwanted note-on or note-off events. This delay after a mechanical button or switch is called "de-bouncing".




Software Setup and Mapping
Once the code has been uploaded to the Red Board, make sure to launch and set up the SerialThing app. A note-on or note-off event will be sent for every press / de-press of the button that occurs. Watch the video below for a brief demonstration. Make sure that the virtual bus to where SerialThing is routed is also set as an input device in the music software.









0 comments: