To summarize the previous posts, I made a singing stuffed animal that happens to be a pig. It sings a different song according to the wig it is wearing which functions as an electrical switch between VCC and ground. This change in voltage is detected by an Arduino Uno. This post will go over the Arduino code that I wrote.
The full source is up in github. Please take it and do as you would like with it. Let me know what you get up to with it.
I have not worked with embedded audio in any great capacity, so I decided to work within the already existing tone() function. The tone() produces a PWM output at a given frequency and optional duration. (Here is an Arduino tutorial for tone generation and I’ve written some tutorial material over on learning.codasign.) It’s quite simple audio generation and is blocking, so the processor can’t do anything else while it is playing a note.
I could have used something like the Adafruit Wave Shield to play a nice, good quality audio file, but frankly that just wasn’t the aesthetic I was going for. I wanted a monophonic synthesizer playing back ridiculous pop tunes.
Basics of the Code
Let’s start with the code. If you aren’t familiar with programming an Arduino, it is largely built on two functions: setup() which is called only once when the board is turned on or reset and loop() which is called repeatedly while the board is powered.
// pins int ledsPin = 3; int blackWigPin = 5; int mohawkPin = 6; int beehivePin = 7; int speakerPin = 8; void setup() { pinMode(ledsPin, OUTPUT); pinMode(blackWigPin, INPUT); pinMode(beehivePin, INPUT); pinMode(mohawkPin, INPUT); // startup tune tone(speakerPin, NOTE_C3, 300); delay(300); tone(speakerPin, NOTE_E3, 300); delay(300); tone(speakerPin, NOTE_G3, 300); delay(300); tone(speakerPin, NOTE_C4, 500); delay(1000); }
First the different pins are named so that I don’t have to keep track of which pin is for what. The pin modes are then set for the digital pins and a little startup tune is played. It’s not a particularly creative tune, just an arpeggio.
Below is the first part of the loop() function.
void loop() { int blackWigValue = digitalRead(blackWigPin); int beehiveValue = digitalRead(beehivePin); int mohawkValue = digitalRead(mohawkPin); if (beehiveValue) { // play Tik Tok song for(int i=0; i<43; i++){ digitalWrite(ledsPin, HIGH); int noteDuration = 2000/ttDurations[i]; tone(speakerPin, ttNotes[i], noteDuration); delay(noteDuration + ttRests[i]); } // turn off eyes digitalWrite(ledsPin, LOW); // wait 4 seconds before playing again delay(4000); } if (mohawkValue) { // play Super Bass song for(int i=0; i<42; i++){ digitalWrite(ledsPin, HIGH); int noteDuration = 2000/sbDurations[i]; tone(speakerPin, sbNotes[i], noteDuration); delay(noteDuration + sbRests[i]); } // turn off eyes digitalWrite(ledsPin, LOW); // wait 4 seconds before playing again delay(4000); } if (blackWigValue) { // play Bad Romance song for(int i=0; i<29; i++){ digitalWrite(ledsPin, HIGH); int noteDuration = 2000/brDurations[i]; tone(speakerPin, brNotes[i], noteDuration); delay(noteDuration + brRests[i]); } // turn off eyes digitalWrite(ledsPin, LOW); // wait 4 seconds before playing again delay(4000); }
At the beginning of the function the voltage is read from each of the wig pins. Each pin is then tested in turn and if the wig is in place (because a high voltage was read), then the corresponding tune is played. The information about each tune is stored in three arrays - one storing the pitch (a frequency value), a second storing the duration of the note, and a third for any silence for after the note (to allow separation or staccato). The LED eyes are turned on for the duration of the tune. All three songs - Tik Tok by Ke$ha, Bad Romance by Lady Gaga, and Superbass by Nicky Minaj - are played back at the same tempo 2000/4 * 1/1000 = 500 ms per quarter note, or 120 bpm. This wasn't intentional, it just happened that all three songs sound ok at that tempo.
If there is no wig on, no audio is played but the eyes glow so that it is obvious that the pig is still on and the battery is being drained. This is pretty much a copy and paste from the Fading Example.
// no wigs on else { for(int fadeValue = 0 ; fadeValue <= 255; fadeValue +=5) { // sets the value (range from 0 to 255): analogWrite(ledsPin, fadeValue); // wait for 30 milliseconds to see the dimming effect delay(30); } // fade out from max to min in increments of 5 points: for(int fadeValue = 255 ; fadeValue >= 0; fadeValue -=5) { // sets the value (range from 0 to 255): analogWrite(ledsPin, fadeValue); // wait for 30 milliseconds to see the dimming effect delay(30); } } }
Transcribing the Music
The Arduino doesn't understand sheet music or produce a pitch on command. The tone() function does understand what a frequency is in Hz and a duration in ms. The pitches header file included in the Tone Example (or directly found here) makes everything easier though by mapping frequency values to note names.
I found online free, and with dubious copyright, midi files of the three songs I wanted. (It should be noted that I also tried some LMFAO, but their stuff is just not melodic enough to translate well to a monophonic synth.) I then opened the midi file in MuseScore and hand transcribed the pitches and durations into code through largely trial and error.
My transcription of Bad Romance is below. The others are available in the full source.
#include "pitches.h" // Bad Romance - 29 int brNotes[] = { NOTE_C4, NOTE_D4, NOTE_E4, NOTE_C4, NOTE_F4, NOTE_E4, NOTE_F4, NOTE_E4, NOTE_D4, NOTE_B3, NOTE_C4, NOTE_D4, NOTE_E4, NOTE_E4, NOTE_E4, NOTE_D4, NOTE_C4, NOTE_A3, NOTE_A3, NOTE_E4, NOTE_E4, NOTE_F4, NOTE_E4, NOTE_A3, NOTE_A3, NOTE_E4, NOTE_E4, NOTE_F4,NOTE_E4 }; int brDurations[] = { 8, 8, 8, 8, 3, 8, 8, 8, 2, 8, 4, 4, 3, 8, 8, 4, 2, 4, 4, 8, 8, 8, 8, 8, 4, 8, 8, 8, 8 }; int brRests[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 550, 0, 0, 0, 0, 0, 120, 0, 0, 0, 0, 0, 0 };
The values in the duration array are relative to a 4/4 bar, so an 8 means an eighth note or 2000/8 ms. A quarter note is 4, a half note a 2 and so on. The rests array is in ms just to avoid division by zero errors in the converting from a relative duration to ms.