1

I want to make my Arduino to play several simple melodies, and also be able to skip a melody by pressing a button and go on with the next song. therefore I cannot use delay() because the code should constantly check if a button is pressed. So I wanted to use millis(), but dont't know how to implement it.

this is how song code looks like with delay():

#define NOTE_B0  31
#define NOTE_C1  33
#define NOTE_CS1 35
//etc...

int tempo=144; 
 
// change this to whichever pin you want to use
int buzzer = 11;
 

int melody[] = {
 
  //Based on the arrangement at https://www.flutetunes.com/tunes.php?id=192
  
  NOTE_E5, 4,  NOTE_B4,8,  NOTE_C5,8,  NOTE_D5,4,  NOTE_C5,8,  NOTE_B4,8,
  // etc...
 
};
 
// sizeof gives the number of bytes, each int value is composed of two bytes (16 bits)
// there are two values per note (pitch and duration), so for each note there are four bytes
int notes=sizeof(melody)/sizeof(melody[0])/2; 
 
// this calculates the duration of a whole note in ms (60s/tempo)*4 beats
int wholenote = (60000 * 4) / tempo;
 
int divider = 0, noteDuration = 0;
 
void setup() {
  // iterate over the notes of the melody. 
  // Remember, the array is twice the number of notes (notes + durations)
  for (int thisNote = 0; thisNote < notes * 2; thisNote = thisNote + 2) {
 
    // calculates the duration of each note
    divider = melody[thisNote + 1];
    if (divider > 0) {
      // regular note, just proceed
      noteDuration = (wholenote) / divider;
    } else if (divider < 0) {
      // dotted notes are represented with negative durations!!
      noteDuration = (wholenote) / abs(divider);
      noteDuration *= 1.5; // increases the duration in half for dotted notes
    }
 
    // we only play the note for 90% of the duration, leaving 10% as a pause
    tone(buzzer, melody[thisNote], noteDuration*0.9);
 
    // Wait for the specief duration before playing the next note.
    delay(noteDuration);
    
    // stop the waveform generation before the next note.
    noTone(buzzer);
  }
}

I found this code on internet but I don't know how to implement this in above code. when I try to, I get some kind of cursed sound.

unsigned long previousMillis = 0;
boolean outputTone = false;     
         
if (millis() - previousMillis >= noteDuration) {
    previousMillis = millis();
    if (outputTone) {
        noTone(buzzer);
        outputTone==false;              
    } else {
        tone(buzzer, melody[thisNote], noteDuration*0.9);
        outputTone==true;
    }
}

edit1: I tried this:

    unsigned long previousMillis = millis();
    int thisNote = 0;
    
    while (thisNote < notes * 2) {
        
        // calculates the duration of each note
        divider = lied1[thisNote + 1];
        if (divider > 0) {
            // regular note, just proceed
            noteDuration = (wholenote) / divider;
        } else if (divider < 0) {
            // dotted notes are represented with negative durations!!
            noteDuration = (wholenote) / abs(divider);
            noteDuration *= 1.5; // increases the duration in half for dotted notes
        }

        // play the note
        tone(buzzer, melody[thisNote]);
        
        
        check();//check if button is pressed, and if so, changes number to 2
        if (number ==2){
            thisNote=notes*2;
        }  
        if ((millis() - previousMillis) >= noteDuration){
           thisNote = thisNote + 2;  
           // stop the waveform generation before the next note.
           noTone(buzzer);
        }
        
     }

but it doesn't work, and I don't really understand why

edit2: I changed something, this is probably the closest I have been to a working melody without "delay()". But it is extremely slow.

    boolean outputTone = false;
    unsigned long previousMillis = millis();
    int thisNote = 0;
    
    while (thisNote < notes * 2) {
        
        // calculates the duration of each note
        divider = lied1[thisNote + 1];
        if (divider > 0) {
            // regular note, just proceed
            noteDuration = (wholenote) / divider;
        } else if (divider < 0) {
            // dotted notes are represented with negative durations!!
            noteDuration = (wholenote) / abs(divider);
            noteDuration *= 1.5; // increases the duration in half for dotted notes
        }
        
        if (millis() - previousMillis >= noteDuration) {
            previousMillis = millis();
            if (outputTone) {
                noTone(buzzer);
                outputTone==false;              
            } else {
                tone(buzzer, melody[thisNote], noteDuration*0.9);
                outputTone==true;
                thisNote = thisNote + 2; 
            }
        }

        check(); //checks if button is pressed, and if so, changes number to 2.
        if (number ==2){ 
            thisNote=notes*2;
        }  
     }

2 Answers 2

1

In essence you will need to break your for loop open and use a global variable for thisNote, incrementing it each loop (with the millis example so that proper timing is still adhered to).

This isn't the only approach to this problem. Your existing code could check for the button press within the for loop and escape the for loop when pressed.

1
  • thanks for your comment! I don't fully understood what you meant, but I tried something (I edited my post). I changed my for loop in a while loop. but it still does not work.
    – Rsoc2002
    Commented Mar 25, 2021 at 13:05
1

I don't quite understand the code snippets, that you added to your question afterwards (they are partly incomplete). But I will propose my own solution to the problem.

A melody is a list of notes and pauses. Each note has a pitch and a duration. A pause has only a duration. We can now define a struct for holding a note:

struct Note {
    unsigned int pitch;
    unsigned int duration;
};

Since a pause does not have a pitch, we can use our struct for a note by setting the pitch to an invalid value (for example zero). For building a melody we are now defining an array of Notes. But since we want multiple melodies, so that we can switch between them, we are using a 2 dimensional array for defining all melodies in one array:

#define NR_MELODIES  2
#define MELODY_LENGTH 8
Note melody[NR_MELODIES][MELODY_LENGTH] = {
    { // melody 0
        {0      , 4}, //pause
        {NOTE_E5, 4},
        {NOTE_B4, 8},
        {NOTE_C5, 8},
        {NOTE_D5, 4},
        {NOTE_C5, 8},
        {0      , 8}, // pause
        {NOTE_B4, 8}
    },
    { // melody 1
        {0      , 4}, //pause
        {NOTE_B4, 8},
        {NOTE_C5, 8},
        {NOTE_D5, 8},
        {NOTE_C5, 4},
        {NOTE_B4, 8},
        {0      , 8}, // pause
        {NOTE_E5, 4}
    }
};

Here I used the divider (part of the whole note), just like you did in your code. A 4 means a quarter note, 8 an eight note. For preventing loosing the first note, I inserted a quarter pause at the start.

We also need a variable to hold the current position in our melody, one for the current melody and a timestamp variable for the timestamp of the last played note:

unsigned int current_note = 0;
unsigned int current_melody = 0;
unsigned long last_note_timestamp = 0;

Now in void loop() we check for our timestamp and the duration of the current note. If the current note should end, we end it, increment to the next note and start playing it:

if(millis() - last_note_timestamp > wholenote/melody[current_melody][current_note].duration){
    last_note_timestamp += wholenote/melody[current_melody][current_note].duration; // increment timestamp to start of next note
    noTone(buzzer); // stop current note
    current_note++; // increment to next note
    if(melody[current_melody][current_note].pitch){ // next note is not a pause
        tone(buzzer, melody[current_melody][current_note].pitch, wholenote/melody[current_melody][current_note].duration); // start note
    }
}

And after the if statement we can add an if statement for checking the melody change button. You didn't include the button check code in your question, so I will just use a placeholder function here.

if(change_melody_pressed()){
    // incrementing current melody and wrap around, if we reached last melody
    current_melody = (current_melody + 1) % NR_MELODIES;
    // reset current note to play melody from start
    current_note = 0;
}

Now we still need to think about, what happens, when a melody finishes. 3 possibilities:

  • We could restart the current melody: We need to check for the last note in a melody, and then reset current_note:

      if(current_melody >= MELODY_LENGTH ){
          current_note = 0;
      }
    
  • We can increment to the next melody: Like above, but we add the melody incrementing line in the if statement:

      current_melody = (current_melody + 1) % NR_MELODIES;
    
  • We can stop playing: Here I would use the above if statement and set current_note to -1. Then we need to check in our first if statement, if current_note is not -1:

      if(current_note != -1 && millis() - last_note_timestamp > wholenote/melody[current_melody][current_note].duration)
    

Note: I have not tested the above code, but I compiled it to try finding syntax errors.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.