So you want to add sound to your project without too much headache? Perfect! The Arduino Musical Instrument Shield has you covered. Plop it on your Arduino Uno, and you have access to a sound catalogue of over 120 sounds from two sound banks: Percussion and Melody. It’s based off the VS1053 chip, which also has an mp3 encoder, but the board only leaves us access to the midi tones.
To see the names of the sounds, check out the datasheet on page 33.
This Demo is very simple, 3 push buttons hooked up to 3 arduino pins with 5v power and ground. Use the Serial Monitor to get information about which sound is playing, be sure that the serial port is 57600 baud in the bottom right rather than the default 9600. You can use any headphones or speaker with an 1/8″ stereo jack.
What is MIDI anyway?
So what is MIDI exactly? MIDI is a set of commands and information that tells a device how to create a sound. To understand how to give the commands, we look at Midi Protocol. It is made up of messages which are a series of 8-bit bytes of data strung along. (So the messages look like 00000001 or 1000001 for instance) If we send a continuous stream of data like that, we need a way of marking out the beginning of a message, and we do so with the status or command byte. It is different from all the others because it is the only one to have its 7th bit set. After it are all the data bit which are necessarily smaller. In decimal notation, that means that status bits range between 0-127 in value and data bits range between 128-255 or in binary, status bits begin with 1 and data bits with 0. In the code however, we use an equivalent hexadecimal because it’s a lot easier to understand what’s going on in this form.
MIDI notation equivalencies
||||||||||Data Bytes || Status Bytes
Decimal: 0-127 || 128-255
Binary: 0xxxxxxx || 1xxxxxxx
Hexadecimal: 00-7f || 80-ff
To convert decimal to hex:
0=0, 2=2, … , 8=8, 9=9, 10 = A, 11 = B, 12 = C, 13 = D, 14 = E, 15 = F
MIDI protocol Hex commands8 = Note Off
9 = Note On
A = AfterTouch (ie, key pressure)
B = Control Change
C = Program (patch) change
D = Channel Pressure
E = Pitch Wheel
(italics on the ones used in the demo)
To understand the command byte we can look at the last two values of the Hex. the notation for hex is 0xval1val2. The 0x is just a shortform used to make it more legible. For instance, we can take the byte 0xC2. Referring to the above equivalencies, we see that the first value, C means Program change- the command we use to set the sound bank- and the second value, 2 is the reference of the channel on which to do the command. There are 16 channels, with channel 1 = 0 and channel 16 = the hex value of 15, F. So 0xC2 means set the channel to the 3rd channel.
The code has the details on specific commands, if you want more technical information about them,
I recommend reading it on the MIDI Specification page.
Demo Code
/* 24-1-2013 Code developed by: Alicia Fortier Original structure: Nathan Seidle, Spark Fun Electronics This code has a Beerware license: If you meet either of the developers someday, you buy them a beer. Otherwise Public Domain MUSICAL INSTRUMENT SHIELD built Vs1053 chip, which holds the sound banks we need to communicate to it serially, see documentation page to get more info on the details on how that's done The code is (hopefully) filled with helpful comments though so that you don't need to understand all of the nitty gritty to start modifying/playing/understanding what's going on. WARNING: I tried my best, but being a human being there could be bugs/inefficiencies. Infact, I'm sure there are. Feel free to send feed back and rewrite this code (please!) or begrugingly swear and rewrite this code(more likely) *///include software library to communicated with board properly
#include <SoftwareSerial.h>
//start serial communication
SoftwareSerial mySerial(2, 3); //Soft TX on 3, we don't use RX in this code//ties to chip Reset linebyte resetMIDI = 4;
//DEFINE VALUES THAT WILL NOT BE CHANGED. MORE EFFICIENT THAN CONSTANTS//HEX NUMBERS FOR COMMANDS
#define CTRLCHANNEL 0xB0
#define CTRLINSTRUMENT 0xC0
#define CTRLON 0x09
#define CTRLOFF 0x08
#define SETVOLUME 0x07
//SOUND BANKS LOCATIONS
#define BNKPERCUSSION 0x78
#define BNKMELODY 0x79
//SOUND ARRAYS//See page 33 of vs1053 datasheet, available on slab website or from manufacturer//for what sounds these numbers correspond to//Percussion
#define MINPerIndex 27 //minimum instrument value
#define MAXPerIndex 87
#define MAXPERCUSSION 61 //length of array//Melody
#define MINMelIndex 1
#define MAXMelIndex 127
#define MAXMELODY 127 //length of array//Instantiate arraysint perSounds [MAXPERCUSSION];
int melSounds [MAXMELODY];
//BUTTONS//Sound Navigationint btnPrev = 8; //redint btnNext = 9; //blueint btnBnkToggle = 7; //Yellowboolean isPercussion= true;
//booleans to activate when btnpressedboolean triggerNext = false;
boolean triggerPrev = false;
boolean triggerToggle = false;
//used valuesint curVolume;
//inserting small pauses helps everything run smoothlyint pause = 50;
//current valuesint curIndexMAX; //keeps track of the max Index availableint curMINVal; //keeps track of the value at index [0]int curMAXVal;
int curBank; //current music bank (Percussion default, alt Melody)int curIndex; //current Index current arrayint melIndex; //current Index in melody arrayint perIndex; //current Index in percussion arrayint curInstrumentNumber;
voidsetup()
{
//Everything that needs to only be set once or needs an initial value goes here.//Start Serial communiccation to be able to view prinlns//to see the messages, click the magnifying glass in the top right corner//in the pop-up, be sure that the xxxx baud matches the number below.Serial.begin(57600);
//Setup soft serial for MIDI control; allows you to give commands in noteOn(); noteOff(); and talkMIDI();
mySerial.begin(31250);
//Reset the chip (vs1053)pinMode(resetMIDI, OUTPUT);
digitalWrite(resetMIDI, LOW);
delay(100);
digitalWrite(resetMIDI, HIGH);
delay(100);
//set btn modes, telling them you want to RECEIVE information, not give currentpinMode (btnNext, INPUT);
pinMode (btnPrev, INPUT);
pinMode (btnBnkToggle, INPUT);
//for each Index i in array; add one to MAXPERCUSSION to compensat for 0base; add one to the index on each iterationfor (int i=0; i<MAXPERCUSSION; i++)
{
//give it the corresponding number between the min and max (27-87)
perSounds[i] = MINPerIndex +i;
}
for (int i=0; i<MAXMELODY; i++)
{
//give it the corresponding number between the min and max (1-128)
melSounds[i] = MINMelIndex +i; //since index starts at 0 and the bank at 1
}
//I set these here since their value could change later in the program
curIndex = 0;
perIndex = 0;
melIndex = 0;
//default to percussion
curBank = BNKPERCUSSION;
curIndexMAX = MAXPERCUSSION-1;//MAXPERCUSSION is the array length, -1 for index value
curMAXVal = MAXPerIndex ;
curMINVal = MINPerIndex;
curVolume = 100;// max value 127//keeps track of the currentInstrumentNumber for the println
curInstrumentNumber= perSounds[perIndex];
//print new information aobut what is playing
printInfo();
}
voidloop()
{
// set the sound volume. talkMidi uses this information to talk to chip//channel command, change the volume, to current volume
talkMIDI (CTRLCHANNEL, SETVOLUME, curVolume);
//this is channel command, on channel 0, current sound bank
talkMIDI(CTRLCHANNEL, 0, curBank);
//check if the buttons are being pressed
readBTN();
//play the correct sound
playSound();
}
void playSound()
{
if (isPercussion)
{
//The instruments here aren't influenced by pitch, so it's really only the index of the instrument you're calling//command change instrument, to the value of the current index, only 1 bit data required
talkMIDI(CTRLINSTRUMENT, curIndex,0);
//channel 0, instrument in the array, at the value of the current Volume
noteOn(0, perSounds[curIndex], curVolume);
//delay to hear it properlydelay(500);
noteOff(0, perSounds[curIndex], curVolume);
curInstrumentNumber = perSounds[curIndex];
}
else
{
//melody instruments need the index of the instrument and the pitch to play it at- more like a synthesizer
talkMIDI(CTRLINSTRUMENT, melSounds[curIndex],0);
//Note on channel 1 (0x90), some note value (note), middle velocity (0x45)://channel 0, pitch(Low), volume
noteOn(0, 30, curVolume);
delay(250);
noteOff(0, 30, curVolume);
//channel 0, pitch(High), volume
noteOn(0, 80, curVolume);
delay(250);
noteOff(0, 80, curVolume);
curInstrumentNumber = melSounds[curIndex];
}
}
void readBTN()
{
//Check if your buttons are pressed//Return HIGH when the button is pressed//LOW when button is not pressedint btnNextState = digitalRead(btnNext);
int btnPrevState = digitalRead(btnPrev);
int btnToggleState = digitalRead(btnBnkToggle);
//NEXT BUTTONif (btnNextState) //if Next button pressed
{ //if next still needs to be triggeredif(triggerNext)
{ //if at Maximum, set it to minimumif (curIndex == curIndexMAX) //curIndex corresponds to the length, -1 for the index position
{
curIndex = 0;
curInstrumentNumber = curMINVal;
}
else//if not, increase index
{
curIndex ++;
curInstrumentNumber ++;
}
//turn trigger off so it only gets done once
triggerNext = false;
//print new information aobut what is playing
printInfo();
}
else//if next button not being pressed
{
triggerNext = true;//so that Next will trigger the next time its pressed
}
}
//PREVIOUS BUTTONif (btnPrevState) //if Prev button pressed
{
if(triggerPrev)
{
//if at Minimum, set it to maximumif (curIndex == 0)
{
curIndex = curIndexMAX; //curIndex corresponds to the length, -1 for the index position
curInstrumentNumber = curMAXVal;
}
else
{
curIndex --;
curInstrumentNumber --;
}
//Serial.println(perSounds[60]);
triggerPrev = false;
//print new information aobut what is playing
printInfo();
}
else
{
triggerPrev = true;
}
}
//TOGGLE BUTTONif(btnToggleState)
{//if currently percussion, change to melodyif(isPercussion && triggerToggle)
{
//store the current index as percussion index
perIndex = curIndex;
//change the current index to the melody index
curIndex = melIndex;
curInstrumentNumber = melSounds[curIndex];
curIndexMAX = MAXMELODY-1;
curMINVal = MINMelIndex;
curMAXVal = MAXMelIndex;
curBank = BNKMELODY;
isPercussion = false;
//trigger Toggle determines whether the action has already been triggered for this press//prevents change by holding down button
triggerToggle = false;
//print new information aobut what is playing
printInfo();
}
elseif (triggerToggle)
{
//store the current index as percussion index
melIndex = curIndex;
//change the current index to the melody index
curIndex = perIndex;
curInstrumentNumber = perSounds[curIndex];
curIndexMAX = MAXPERCUSSION-1 ;
curMINVal = MINPerIndex;
curMAXVal = MAXPerIndex ;
curBank = BNKPERCUSSION;
isPercussion = true;
triggerToggle = false;
printInfo();
}
}
else//if button not being held, reset the trigger to true so it activates on next press
{
triggerToggle = true;
}
}
//Send a MIDI note-on message. Like pressing a piano key//channel ranges from 0-15void noteOn(byte channel, byte note, byte attack_velocity)
{
talkMIDI( (0x90 | channel), note, attack_velocity);
}
//Send a MIDI note-off message. Like releasing a piano keyvoid noteOff(byte channel, byte note, byte release_velocity)
{
talkMIDI( (0x80 | channel), note, release_velocity);
}
//Plays a MIDI note. Doesn't check to see that cmd is greater than 127, or that data values are less than 127void talkMIDI(byte cmd, byte data1, byte data2)
{
mySerial.write(cmd);
mySerial.write(data1);
//Some commands only have one data byte. All cmds less than 0xBn have 2 data bytes //(sort of: http://253.ccarh.org/handout/midiprotocol/)if( (cmd & 0xF0) <= 0xB0)
mySerial.write(data2);
}
void printInfo()
{
Serial.println("///////////////////////////////////////");
Serial.println("120 : Percussion Bank // 121 : Melody Bank");
Serial.println("///////////////////////////////////////");
Serial.print("You are listening to bank: ");
Serial.println( curBank);
Serial.print("Instrument number: ");
Serial.println( curInstrumentNumber);
Serial.print("At Volume: ");
Serial.println( curVolume);
Serial.println("///////////////////////////////////////");
}