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.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
unsignedlong 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
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
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.
// 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);}
// 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.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
int tempo = 160; // Tempo of the song // Melody notes
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;
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;
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.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// 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);
// 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);
// 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:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#include <Servo.h>
#include "notes.h"
Servo servo_r;
unsignedlong 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
#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;
}
}
#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;
}
}