For this week’s assignment, Badr and I tried to build a simple musical instrument using a servo to emulate a simple drum and a buzzer to play a melody.
Drum:
We defined three different drum modes (beats) that can be looped over using a pushbutton. Every time the button is clicked, the drum moves to the next mode. Originally, we planned to use two servos (drums), but it turned out noisier than we thought, so we decided to use only one. To move the servos, we have used servo.write().
Millis():
To avoid using delay() which will affect the interaction, we have used Millis to set breaks. For that, we had to create an array to store the duration of the breaks, variables to store the elapsed time, and if conditions.
unsigned long curr = millis(); // store the elapsed time bool played[] = {1, 0, 0, 0}; // boolean variables to control the modes int period[]= {200, 150, 150, 200}; // Tempo of the servo (breaks) int curr_prd=0; // keep track of the current break
Beats (Modes):
To alternate between the modes, we have used if/else conditions, boolean variables, digitalRead() to read the state of the push button and a prevButton variable to store the previous state.
// switch between the modes // default mode if (!prevbutton && digitalRead(3)==HIGH && level2==0 && level3==0){ level2=1; period[0] = 200; period[1] = 150; period[2] = 150; period[3] = 200; } // mode 3 else if (!prevbutton && digitalRead(3)==HIGH && level2==0 && level3==1){ level3=0; period[0] = 300; period[1] = 300; period[2] = 300; period[3] = 300; } // mode 2 else if (!prevbutton && digitalRead(3)==HIGH && level2==1 && level3==0){ level3=1; level2=0; period[0] = 60; period[1] = 60; period[2] = 60; period[3] = 60; }
Moving the Servo:
// set the tempo for level 3 if (curr-prev>=period[curr_prd] && level2==0 && level3==1){ prev=curr; // change the array parameters if (played[0]==1){played[0]=0; played[1]=1;} else if (played[1]==1){played[1]=0; played[2]=1;} else if (played[2]==1){played[2]=0; played[3]=1;} else if (played[3]==1){played[3]=0; played[0]=1;} curr_prd=(curr_prd+1)%4; } // set the tempo for level 2 if (curr-prev>=period[curr_prd] && level2==1 && level3==0){ prev=curr; // change the array parameters if (played[0]==1){played[0]=0; played[1]=1;} else if (played[1]==1){played[1]=0; played[2]=1;} else if (played[2]==1){played[2]=0; played[3]=1;} else if (played[3]==1){played[3]=0; played[0]=1;} curr_prd=(curr_prd+1)%4; } // set the tempo for the default level if (curr-prev>=period[curr_prd] && level2==0 && level3==0){ prev=curr; // change the array parameters if (played[0]==1){played[0]=0; played[1]=1;} else if (played[1]==1){played[1]=0; played[2]=1;} else if (played[2]==1){played[2]=0; played[3]=1;} else if (played[3]==1){played[3]=0; played[0]=1;} curr_prd=(curr_prd+1)%4; } // Move the servo if (played[0]){servo_r.write(90);} if (played[1]){servo_r.write(120);} if (played[2]){servo_r.write(100);} if (played[3]){servo_r.write(120);}
Buzzer:
We chose to play the tetris theme on the buzzer, and used the ultrasonic sensor to change the speed at which the melody is being played. The larger the values read by the sensor are, the slower the music will play.
Melody:
To play the melody, we stored the convenient notes in an array (we got them online), then proceeded to calculate how long the note is using sizeof() and the tempo which is 160.
int tempo = 160; // Tempo of the song // Melody notes int melody[] = { NOTE_E5, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_A4, NOTE_A4, NOTE_C5, NOTE_E5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_E5, NOTE_C5, NOTE_A4, NOTE_A4, NOTE_A4, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_F5, NOTE_A5, NOTE_G5, NOTE_F5, NOTE_E5, NOTE_C5, NOTE_E5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_E5, NOTE_C5, NOTE_A4, NOTE_A4, REST, NOTE_E5, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_A4, NOTE_A4, NOTE_C5, NOTE_E5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_E5, NOTE_C5, NOTE_A4, NOTE_A4, NOTE_A4, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_F5, NOTE_A5, NOTE_G5, NOTE_F5, NOTE_E5, NOTE_C5, NOTE_E5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_E5, NOTE_C5, NOTE_A4, NOTE_A4, REST, NOTE_E5, NOTE_C5, NOTE_D5, NOTE_B4, NOTE_C5, NOTE_A4, NOTE_GS4, NOTE_B4, REST, NOTE_E5, NOTE_C5, NOTE_D5, NOTE_B4, NOTE_C5, NOTE_E5, NOTE_A5, NOTE_GS5 }; // Calculate the duration of each note int notes= sizeof(melody) / sizeof(melody[0]) / 2; int note = (60000 * 4) / tempo; int divider = 0, duration = 0; // Calculate the duration of each note int notes= sizeof(melody) / sizeof(melody[0]) / 2; int note = (60000 * 4) / tempo; int divider = 0, duration = 0;
Ultrasonic Sensor:
Depending on the distance measured by the sensor, the speed of the song is changing. For that, we used map() to modify it within a certain range. We also had to convert microseconds to cm.
// get the distance using the ultrasonic sensor long duration, cm; digitalWrite(9, LOW); delayMicroseconds(2); digitalWrite(9, HIGH); delayMicroseconds(10); digitalWrite(9, LOW); duration = pulseIn(8, HIGH); // convert microseconds to cm cm = duration/29/2; if (cm>20) { cm = 20; } // map the distance form 80 to 150 to fit cm = map(cm,0,20,80,150);
Challenges:
The biggest challenge we faced was getting the drum to work without using delay(). Using delay() would have affected the interaction with the button and sensor, so we used millis() instead. When we tried to implement the millis() solution, the drum’s playing started to become irregular and sometimes stopped working after some time. To fix this, we used arrays to keep track of the angles and timings of each angle change of the servo. The code started working properly after we introduced this fix and restructured the logic we used in the loop.
We also faced some issues with taping of our servo and stick coming loose occasionally while working on the project.
Code:
#include <Servo.h> #include "notes.h" Servo servo_r; unsigned long prev=0; // variable to store the time bool played[] = {1, 0, 0, 0}; // boolean variables to control the modes int period[]= {200, 150, 150, 200}; // Tempo of the servo (breaks) int curr_prd=0; // keep track of the current break bool level2=0; // Mode 2 bool level3=0; // Mode 3 bool prevbutton=0; // store the previous state of the pushbutton int prevnote = 0; // store the prev note int currnote = 0; // store the current note int tempo = 160; // Tempo of the song // Melody notes int melody[] = { NOTE_E5, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_A4, NOTE_A4, NOTE_C5, NOTE_E5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_E5, NOTE_C5, NOTE_A4, NOTE_A4, NOTE_A4, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_F5, NOTE_A5, NOTE_G5, NOTE_F5, NOTE_E5, NOTE_C5, NOTE_E5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_E5, NOTE_C5, NOTE_A4, NOTE_A4, REST, NOTE_E5, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_A4, NOTE_A4, NOTE_C5, NOTE_E5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_E5, NOTE_C5, NOTE_A4, NOTE_A4, NOTE_A4, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_F5, NOTE_A5, NOTE_G5, NOTE_F5, NOTE_E5, NOTE_C5, NOTE_E5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_E5, NOTE_C5, NOTE_A4, NOTE_A4, REST, NOTE_E5, NOTE_C5, NOTE_D5, NOTE_B4, NOTE_C5, NOTE_A4, NOTE_GS4, NOTE_B4, REST, NOTE_E5, NOTE_C5, NOTE_D5, NOTE_B4, NOTE_C5, NOTE_E5, NOTE_A5, NOTE_GS5 }; // Calculate the duration of each note int notes= sizeof(melody) / sizeof(melody[0]) / 2; int note = (60000 * 4) / tempo; int divider = 0, duration = 0; void setup() { Serial.begin(9600); servo_r.attach(6); // Set the servo pinMode(3, INPUT); // Set the button pinmode pinMode(9, OUTPUT); // set the echo pin for the sensor pinMode(8, INPUT); // set the pin for the sensor } void loop() { // switch between the modes // default mode if (!prevbutton && digitalRead(3)==HIGH && level2==0 && level3==0){ level2=1; period[0] = 200; period[1] = 150; period[2] = 150; period[3] = 200; } // mode 3 else if (!prevbutton && digitalRead(3)==HIGH && level2==0 && level3==1){ level3=0; period[0] = 300; period[1] = 300; period[2] = 300; period[3] = 300; } // mode 2 else if (!prevbutton && digitalRead(3)==HIGH && level2==1 && level3==0){ level3=1; level2=0; period[0] = 60; period[1] = 60; period[2] = 60; period[3] = 60; } unsigned long curr = millis(); // store the elapsed time // set the tempo for level 3 if (curr-prev>=period[curr_prd] && level2==0 && level3==1){ prev=curr; // change the array parameters if (played[0]==1){played[0]=0; played[1]=1;} else if (played[1]==1){played[1]=0; played[2]=1;} else if (played[2]==1){played[2]=0; played[3]=1;} else if (played[3]==1){played[3]=0; played[0]=1;} curr_prd=(curr_prd+1)%4; } // set the tempo for level 2 if (curr-prev>=period[curr_prd] && level2==1 && level3==0){ prev=curr; // change the array parameters if (played[0]==1){played[0]=0; played[1]=1;} else if (played[1]==1){played[1]=0; played[2]=1;} else if (played[2]==1){played[2]=0; played[3]=1;} else if (played[3]==1){played[3]=0; played[0]=1;} curr_prd=(curr_prd+1)%4; } // set the tempo for the default level if (curr-prev>=period[curr_prd] && level2==0 && level3==0){ prev=curr; // change the array parameters if (played[0]==1){played[0]=0; played[1]=1;} else if (played[1]==1){played[1]=0; played[2]=1;} else if (played[2]==1){played[2]=0; played[3]=1;} else if (played[3]==1){played[3]=0; played[0]=1;} curr_prd=(curr_prd+1)%4; } // Move the servo if (played[0]){servo_r.write(90);} if (played[1]){servo_r.write(120);} if (played[2]){servo_r.write(100);} if (played[3]){servo_r.write(120);} // read the state of the button prevbutton = digitalRead(3); // get the distance using the ultrasonic senor long duration, cm; digitalWrite(9, LOW); delayMicroseconds(2); digitalWrite(9, HIGH); delayMicroseconds(10); digitalWrite(9, LOW); duration = pulseIn(8, HIGH); // convert microseconds to cm cm = duration/29/2; if (cm>20) { cm = 20; } // map the distance form 80 to 150 to fit cm = map(cm,0,20,80,150); if (curr-prevnote >= cm) { prevnote = curr; // play the note tone(11, melody[currnote], duration*0.9); // loop over the array currnote = (currnote + 1) % 100; } }