[Badr & Ziyad] Week 10: Music Instrument

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. 

View post on imgur.com

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;
  }
}

Leave a Reply