It's getting late here, so I will post the code tomorrow because I would like to give a reasonable explanation of the algorithm and programming (right or wrong) behind it.
I think the algorithm is ok, it's just a matter of using the tools at hand (Arduino) to get the job done.
The closest to doing it "in the background" that I can think of would be to use one of the pins associated with a particular hardware timer. That way you could configure the processor to emit a tone without any software intervention. But to play a melody you would still have to step in at the appropriate time to change what the timer is outputting. To do that you could use a second timer and an interrupt routine to step through a sequence of tones of specified duration. The interruptions would be relatively brief and infrequent.
That way you could play a melody similarly to how you can write a string with Serial. That is, you could send the melody "string" and it would play while your code continued on its merry way.
Not very pretty, but this will check for events during rests and between notes - The "song" is in Flash to save space.
byte playTune(int *tune)
{
//Non-blocking tune player that waits for a press of the mode button.
// returns 0 if normal return, -1 if mode button hit.
int curr_note = 0;
unsigned long curr_time = 0;
unsigned long noteDuration = 0;
while ( pgm_read_word_near(tune + curr_note)!= -1)
{
// to calculate the note duration, take one second
// divided by the note type.
//e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
noteDuration = 1000/pgm_read_word_near(tune + curr_note+1); //+1 to get following duration
if (pgm_read_word_near(tune + curr_note) == REST_00) {
curr_time = millis();
while ((millis() - curr_time) < noteDuration)
{
// if mode is pressed, quit
if (digitalRead(butMode) == LOW)
{
return -1;
}
}
}
else {
tone(Speaker, pgm_read_word_near(tune + curr_note), noteDuration);
}
// to distinguish the notes, set a minimum time between them.
// the note's duration + 30% seems to work well:
int pauseBetweenNotes = noteDuration * 130 / 100;
curr_time = millis();
while ((millis() - curr_time) < pauseBetweenNotes)
{
// if mode is pressed, quit
if (digitalRead(butMode) == LOW)
{
return -1;
}
}
curr_note = curr_note + 2; // To skip the duration
}
return 0;
}