User:Dmoreno1899/ENES100/Music Project
Link to project Music Team team page.
Contents |
Week0 Activities [edit]
Give instructor top 3 project choice list [edit]
1 Music Project - Project taken/accepted
Write problem statement [edit]
The can to place the magnets on that is then read by the arduino is mounted, but could use work. Furthermore, little progress has been made with connecting the hall sensor probe to the arduino, the arduino to the MIDIshield, and then the MIDIshield to the keyboard.
Assign Task1 [edit]
I was assigned to work on studying the arduino and MIDIshield.
Week 1 Activities [edit]
Compare actual work done to Task2 [edit]
Teammates went over concepts studied and work done over the last weekend - I studied the Hall Effect, the Hall Effect sensor probe,and "played" with the "Hello World" code of the arduino to get a better understanding of it, and shared this information with my teammate, giving a demonstration with time delays of the blinking LED light on the arduino board.
Week 1 Narrative [edit]
As far as dissecting the singing card goes, I feel rather successful in taking it apart. It consists of a small dremel board (perhaps 1 x 1 in) that seems to act as a motherboard (supplying power and "holding" code), connected to an round audio device by two wires. This audio device (now referred to as part (b)) is approximately 1 in diameter, and a hard green plastic dome top with a flat, thin plastic bottom. I ripped out the underside to find only a copper ring and two EXTREMELY thin copper wires running from the copper ring to the edge of part (b), where they met up with the two external wires that connect the dremel board and part (b). Additionally, I ripped out the circular looking item on the dremel board, and it was in fact a battery - as evidenced by the "3v lithium battery" written on it.
I'm not sure if I'll actually be able to use this to create sound. In the first place, it might have been possible to strip the wires connecting part (b) and the dremel board, and somehow connect that so that it can "talk" to the arduino, but when I tore open the plastic underside to part (b), I tore those incredibly thin copper wires, so I'm not sure if it's broken beyond repair, or is worth the effort trying to connect them back together.
Week 1 Peer Review [edit]
Go to each team mates weekly summary wiki page. Read their activities and narrative. Then go the associated discussion page. Say something positive on this page. Try some constructive criticism. Add your name and 1sfoerster 13:32, 28 August 2011 (UTC) to create a time and date stamp. Create a new category if another team mate has already commented here.
Assign Task 2 [edit]
This coming week I plan to take home some device that I'm more confident I can get talking to the arduino - either the keyboard in the engineering design classroom, or the toy guitar, and seeing if I can get that instead to talk to the arduino. Additionally, I will start looking into how to produce sound with an arduino.
Week 2 Activities [edit]
Compare actual work done to Task3 [edit]
I managed to indeed get the arduino/MIDI shield talking to the keyboard, which I took home, and wrote a program that plays Mary had a Little Lamb over and over on a loop.
Week 2 Narrative [edit]
Tuesday in class I tested my sample code just to see if the basic connection of placing the MIDI shield on the arduino would be enough to provide sound. It was, and the code used to display this can be found here:
void setup() { // Set MIDI baud rate: Serial.begin(31250); } void loop() { // play notes from F#-0 (0x1E) to F#-5 (0x5A): for (int note = 0x1E; note < 0x5A; note ++) { //Note on channel 1 (0x90), some note value (note), middle velocity (0x45): noteOn(0x90, note, 0x45); delay(100); //Note on channel 1 (0x90), some note value (note), silent velocity (0x00): noteOn(0x90, note, 0x00); delay(100); } } // plays a MIDI note. Doesn't check to see that // cmd is greater than 127, or that data values are less than 127: void noteOn(int cmd, int pitch, int velocity) { Serial.write(cmd); Serial.write(pitch); Serial.write(velocity); }
Uploading this code to the arduino, connecting the MIDI shield to said arduino, connecting MIDI out to the MIDI shield, and MIDI in to the keyboard (or any other device with a MIDI in) will result in all notes the device (keyboard) can possibly play, in ascending sequence. Understanding how this code works requires a basic understanding of arduino environment programming, as well as basic MIDI protocol properties.
All code using MIDI protocol MUST be set to a baud rate of 31250. This is done in the void setup with:
Serial.Begin(31250);
Additionally, this code is written to play a different note after every iteration of the loop, inside of which there is a 100 millisecond delay; in other words, every time the code repeats itself (which occurs every 1 tenth of a second, it plays a different note. To do this, a "for" statement is used. A "base" value is applied to a specified integer, and every time the loop repeats itself another value is added on to that base value, up to a certain point, at which it repeats itself. A simplified way of explaining this is to say that the for function makes the the note one value higher each time the loop repeats itself.
The code "accesses" the MIDI protocol with:
void noteOn(int cmd, int pitch, int velocity) { Serial.write(cmd); Serial.write(pitch); Serial.write(velocity); }
This basically creates the noteOn function that is used in the void loop. It specifies what each of the three values for noteOn do - the MIDI protocol requires at least these three values to play a note, although there are others. Additionally, the MIDI protocol is written in hexidecimal, which is fairly easy to calculate values for using a hexidecimal calculator. "0x" always precedes any hexidecimal values, so that the code knows to read it as hexidecimal and not decimal.
All MIDI protocol messages MUST start with the command that essentially says "these following values are to be interpreted as MIDI". This value is ALWAYS 0x90. Next, pitch simply refers to what note is played. All notes have a corresponding hexidecimal value that ranges from 0 to 127/ 0 to 7F (binary/hexidecimal), where 60/3C is middle C. Finally, the third value is velocity - or how long the note is played. This is much harder to find appropriate values for, because, through testing, I found that a velocity value of 120/78 corresponds to approximately 2.88 seconds of play time. According to the previously linked page on MIDI specifications, the actual device interprets how to use velocity values. Guess and check seems to work surprisingly well, as long as the velocity value doesn't interfere with the time delay on a loop - but more on that later.
When I first tested this code, I did not understand all of the things I just tested, but rather all of this information came as a result of several hours of studying MIDI specification, and testing what code would and would not compile in the arduino environment. There were actually no problems with uploading this code, as it worked the very first time I tried connecting it all together.
The following Thursday in class I experimented with creating sound from my own code. To start, I simply wanted to play middle C over and over on a loop. I wrote the following code with the information extracted from the last code:
void setup() { Serial.begin(31250); } void loop() { noteOn(0x90, 0x3C, 0x100); delay(100); } void noteOn(int cmd, int pitch, int velocity) { Serial.write(cmd); Serial.write(pitch); Serial.write(velocity); }
Where the values for velocity and delay are arbitrary. The code worked wonderfully. The next step was significantly harder, as I set out to write Mary Had a Little Lamb. The resulting (and successful) code is as follows:
void setup() { Serial.begin(31250); //MIDI protocol baud rate is 31250 bits per second } void loop() { //Plays Mary Had a Little Lamb on a loop at 145 BPM //Bar 1 noteOn(0x90, 0x40, 0x28); //Quarter note: E delay(414); noteOn(0x90, 0x3E, 0x28); //Quarter note: D delay(414); noteOn(0x90, 0x3C, 0x28); //Quarter note: C delay(414); noteOn(0x90, 0x3E, 0x28); //Quarter note: D delay(414); //Bar 2 noteOn(0x90, 0x40, 0x28); //Quarter note: E delay(414); noteOn(0x90, 0x40, 0x28); //Quarter note: E delay(414); noteOn(0x90, 0x40, 0x28); //Half note: E delay(828); //Bar 3 noteOn(0x90, 0x3E, 0x28); //Quarter note: D delay(414); noteOn(0x90, 0x3E, 0x28); //Quarter note: D delay(414); noteOn(0x90, 0x3E, 0x28); //Half note: D delay(828); //Bar 4 noteOn(0x90, 0x40, 0x28); //Quarter note: E delay(414); noteOn(0x90, 0x43, 0x28); //Quarter note: G delay(414); noteOn(0x90, 0x43, 0x28); //Half note: G delay(828); //Bar5 noteOn(0x90, 0x40, 0x28); //Quarter note: E delay(414); noteOn(0x90, 0x3E, 0x28); //Quarter note: D delay(414); noteOn(0x90, 0x3C, 0x28); //Quarter note: C delay(414); noteOn(0x90, 0x3E, 0x28); //Quarter note: D delay(414); //Bar 6 noteOn(0x90, 0x40, 0x28); //Quarter note: E delay(414); noteOn(0x90, 0x40, 0x28); //Quarter note: E delay(414); noteOn(0x90, 0x40, 0x28); //Quarter note: E delay(414); noteOn(0x90, 0x40, 0x28); //Quarter note: E delay(414); //Bar 7 noteOn(0x90, 0x3E, 0x28); //Quarter note: D delay(414); noteOn(0x90, 0x3E, 0x28); //Quarter note: D delay(414); noteOn(0x90, 0x40, 0x28); //Quarter note: E delay(414); noteOn(0x90, 0x3E, 0x28); //Quarter note: D delay(414); //Bar 8 noteOn(0x90, 0x3C, 0x28); //Whole note: C delay(2000); //End song //Wait 2 seconds after the last note is struck to repeat } void noteOn(int cmd, int pitch, int velocity) { //Sets what each of the 3 values in the "noteOn" function does Serial.write(cmd); //All MIDI protocol messages start with 0x90 to signify the start of a MIDI message Serial.write(pitch); //The note. Middle C (0x3C), for example Serial.write(velocity); //Length of time the note is played. Values range from 0 to 127 (0x64), for example }
I encountered several problems writing this code.
- Figuring out delay values. I wanted to write this song at a certain BPM, not just get whatever BPM resulted from arbitrary delay values. I decided I wanted to see how long a single quarter note at 145 BPM played, in milliseconds. Through dimensional analysis: (145 Beats)/(1 minute) * (1 minute)/(60 seconds) * (1 second)/(1000 milliseconds) = 2.4167x10^(-3) BPMs (beats per milisecond). To calculate the length of a single beat: (1 beat) * (1 millisecond/2.4167x10^(-3) beat) = 413.793 milliseconds. I rounded up, and hence, used a delay of 414 for quarter notes. A half note is exactly twice as long as a quarter note, and is therefore 828 milliseconds.
- The biggest problem I had was actually realizing that the pitch value was a single value 0/0-127/7F (binary/hexadecimal). It was my initial belief that 0x3C was to be read as 0x[3rd octave][note C], instead of 60 (decimal). As a result I tried writing Mary Had a Little Lamb with the wrong intervals. It resulted in a "haunting" sound, that I thought to be dissonant. I incorrectly believed this was because my (initially arbitrary) velocity values were causing the notes to play longer than the delay that followed them, causing notes to overlap, and sound dissonant. This was incorrect because of A), the information I just gave, and B), the three notes used in Mary Had a Little Lamb are all part of a common chord, and as a result would be/are not dissonant when played over one another (in fact, more "advanced" versions of Mary Had a Little Lamb for piano have the player playing those exact three notes in a different octave together with the left hand)
- Another problem was dealing with velocity values. Initially, I was using a velocity value of 0x99 (hexadecimal)for the final (whole) note, with a delay of 5000 milliseconds before it would restart the loop. This resulted in the last note just not being played. I'm not sure why, but when I made the velocity value significantly (and arbitrarily) shorter (0x28), it was fine. My theory is that the velocity value was so much longer than the decimal value, that the loop would have restarted itself before the note was done playing, and so just did not play that note. Further testing is needed to understand this, playing with velocity and delay values. Perhaps using a noteOn with a "silent" (0x00) velocity after an appropriate delay, and THEN the 5 second delay before the next loop is necessary.
Important Links for MIDI Programming [edit]
- The MIDI protocol
- Hexadecimal Calculator
- Arduino references
- For those who need a stronger background in musical theory, this is not a horrible place to start.
Assign Task 3 [edit]
The next step is to get notes playing based on input values. This would ideally be tested using the button labeled "S1" on the arduino board - press the button, and play a note, essentially simulating a keyboard. This would work to simulate different input values from magnets being read from the Hall Effect Probe.
Week 3 Activities [edit]
Compare actual work done to Task 3 [edit]
I found a few pieces of code that are essential to writing a program that will cause a note to play based on an analog input.
Week 3 Narrative [edit]
I was advised to work on getting a note to play when the switch on the arduino board was pressed. However, that switch turned out to be a reset button, and not necessarily a programmable switch. There is a method for programming a button, but it requires a breadboard, resistor, and separate button to work. So another method of playing a note on command.
Because the Hall Effect Probe runs with analog, I figured researching analog interactions with arduino would be useful. I found the analogRead() function.
int sensorValue = 0 //0 acts as a place holder for the variable sensorValue void setup() { Serial.begin(9600); //Most analog devices require a baud rate of 9600 bits per second to operate ) void loop() { sensorValue = analogRead(0); //Assigns the variable sensorValue the voltage (0v to 5v) value read (interpreted from 0 to 1023 from analog pin 0 }
As a footnote, the arduino page on analogRead said that if nothing is connected to that pin, the analogRead can pick up very random values, based on a massive amount of conditions - the one that I noticed was the closeness of one's hand. I loaded the Analog Input test code that will blink a light for a length of time based on the analogRead (the lower the voltage, the faster it blinks, the higher the voltage, the slower).
I loaded this code and found that the voltage read from pin 0 was fluctuating greatly just sitting there - however, the closeness of my hand did not seem to effect the rate at which the light blinked. I then got the idea that, because the Human brain is basically a massive ball of electrical signals, that might somehow be picked up by the arduino. I held the pin against my temple, and actually saw the light begin to blink slower, by my estimates around the 700-1000 millisecond range. I found this to be fairly reliable, as I tested it about fifteen times - it's a strangely reliable way to alter the analogRead input without a potentiometer.
The second part necessary to code a note playing based on conditional statements (preferably the analogRead just used) is understanding the "if" function and comparison operators. Especially when compared to the "for" function previously used in an example code, "if" is fairly simple to understand.
int sensorValue = 0; void setup() { pinMode(13, OUTPUT); Serial.begin(9600); } void loop() { sensorValue = analogRead(0); if (sensorValue >= 512) { //If analogRead reads a value greater than or equal to 512 digitalWrite(13, HIGH); //Light is on } if (sensorValue < 512) { //If analogRead reads a value less than 512 digitalWrite(13, LOW); //Light is off } }
This is a quick example using the if statement. If the analogRead picks up a voltage approximately 2.5 or greater, the LED pin is on, if it's less, the light is off. The "Serial.begin(9600);" in the voide setup is not actually necessary if the only thing being used is the analogRead - so this code could actually be used in conjunction with MIDI protocol as is. So my next step was to replace the blinking light with a note playing on the keyboard. My end result was as follows.
int sensorValue = 0; void setup() { pinMode(13, OUTPUT); Serial.begin(31250); } void loop() { sensorValue = analogRead(0); if (sensorValue >= 512) { //If analogRead reads a value greater than or equal to 512 digitalWrite(13, HIGH); //Light is on noteOn(0x90, 0x3C, 0x100); //Play middle C for an arbitrary length of time delay(sensorValue); //Pause for a value equal to what the analogRead picked up noteOn(0x90, 0x3C, 0x00); //"Silent" note - make sure the note stops playing } if (sensorValue < 512) { //If analogRead reads a value less than 512 digitalWrite(13, LOW); //Light is off noteOn(0x90, 0x46, 0x100); //Play one octave above middle C for an arbitrary length of time delay(sensorValue); //Pause for a value equal to what the analogRead picked up noteOn(0x90, 0x46, 0x00); //"Silent" note - make sure the note stops playing } delay(5000); //Pause for 5000 seconds to ensure all notes have stopped playing } void noteOn(int cmd, int pitch, int velocity) { //Write the noteOn function Serial.write(cmd); Serial.write(pitch); Serial.write(velocity); }
It must be noted that this code did not work as intended. It resulted in a "thumping" being played twice, a pause of approximately 4 to 5 seconds, and then repeat. Originally I thought a lack (initially) of a "silent" note to "turn off" the note being played was what was causing it, but that didn't seem to be the case.
My Thoughts on Problems with the Code [edit]
- The "silent" note being within the "if" statements could be the problem. Just taking it out of there and putting it after both if statements could work - similar to factoring in algebra. For example, 2x + 2y = 2(x+y).
- Using a delay value within the if statements could somehow be causing problems. Because the sensorValue value is so difficult to control with this setup, it is essentially RNG when it is within the delay function, meaning it waits for an arbitrary length of time.
- It's possible that more precise control of the program is necessary with "If... Else" statement(s) instead of just if. Further research into "If... Else" is necessary.
- While a value from analogRead of 0 corresponds to 0V and 1023 to 5v, there is a level of uncertainty associated with dealing with how they interact on the interval in between. If 2.5v is not equal to 511.5, and there is some other kind of ratio involved, it is currently unknown, and could be problematic.
Start Next Project Week0 activities [edit]
The next team must get a note to play based on the analogRead function. This should be done more accurately with a potentiometer at first, rather than just holding the arduino to your temple. A good place to start is with breaking down the code I provided, and figuring out why it does not play a note at any given time (which it should - the if statements just have the pitch of the note different from one another, rather than the presence of a note), and then reconstruct it so that it does play a note.
From there, using previous team's knowledge, it should be possible to connect the Hall Effect Probe to the arduino, pass a magnet through its sensor, and through MIDI shield to arduino to keyboard connections, play a note. From there, it is fairly easy to get six Hall Effect Probes working on one arduino (using the 7 analog pins provided on the arduino board), and then setting up the the actual tin can to play the notes on a hand crank.