A cool polyphony demo video from Sparkfun.com
Arduino Uno Musical Instrument Shield
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 commands
8 = 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 line byte 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 arrays int perSounds [MAXPERCUSSION]; int melSounds [MAXMELODY]; //BUTTONS //Sound Navigation int btnPrev = 8; //red int btnNext = 9; //blue int btnBnkToggle = 7; //Yellow boolean isPercussion= true; //booleans to activate when btnpressed boolean triggerNext = false; boolean triggerPrev = false; boolean triggerToggle = false; //used values int curVolume; //inserting small pauses helps everything run smoothly int pause = 50; //current values int curIndexMAX; //keeps track of the max Index available int 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 array int melIndex; //current Index in melody array int perIndex; //current Index in percussion array int curInstrumentNumber; void setup() { //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 current pinMode (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 iteration for (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(); } void loop() { // 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 properly delay(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 pressed int btnNextState = digitalRead(btnNext); int btnPrevState = digitalRead(btnPrev); int btnToggleState = digitalRead(btnBnkToggle); //NEXT BUTTON if (btnNextState) //if Next button pressed { //if next still needs to be triggered if(triggerNext) { //if at Maximum, set it to minimum if (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 BUTTON if (btnPrevState) //if Prev button pressed { if(triggerPrev) { //if at Minimum, set it to maximum if (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 BUTTON if(btnToggleState) {//if currently percussion, change to melody if(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(); } else if (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-15 void noteOn(byte channel, byte note, byte attack_velocity) { talkMIDI( (0x90 | channel), note, attack_velocity); } //Send a MIDI note-off message. Like releasing a piano key void 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 127 void 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("///////////////////////////////////////"); }