Breaking a loop

Hi,

The following code checks the set time for an alarm clock, if true, it plays a melody. A motion detector is used (in place of a push button) to stop the alarm from playing.

However, the melody is played fully till it loops again while checking for a motion. The latter is done is series with the melody code so the time has to be correct for the melody to stop, otherwise, it loops again.

For the sound to stop at will, the checkMotion() function should run in parallel with the playMelody() function. How can it be done?

TIA

#include <TimeLib.h>

bool motion = false;

void setup() {
  // put your setup code here, to run once:
}

void loop() {
  if (hour() == 8 && minute() == 0) playSound();  //no seconds used so it plays for approx a minute
}

void playSound() {
  while (!motion) {
    playMelody();
    checkMotion();  //check for motion if exists set motion variable to true
  }
}

void playMelody(){} //la la la 
void checkMotion(){}  //

As you have not posted the code for the playMelody() function I cannot help.

Have a look at the demo Several Things at a Time

...R

if your playMelody() function has blocking code, you may have a problem polling the motion sensor.

In that case, you may want to unblock that code, or instead use an interrupt on your motion sensor. It can toggle the boolean you set up and be ready once your playMelody() function gets through its blocky bits.

Thank you but I cannot alter the melody code as it will affect the tempo

void playMelody() {
  for (int i = 0; i < length; i++) {
    if (notes[i] == ' ') {
      delay(beats[i] * tempo); // rest
    } else {
      playNote(notes[i], beats[i] * tempo);
    }
    // pause between notes
    delay(tempo / 2);
  }
}

Thank you but I cannot alter the melody code as it will affect the tempo

You are quite wrong about that. You must modify the playMelody function, or it will be impossible to fix your problem.

You’ve got a for loop in there. Get rid of it. Also the delays, because that will make you wait for a note to finish before the motion sensor gets checked.

The proper way to do nonblocking code is to ask “What must be kept track of so that I can go away from this task and pick up where I left off when I come back to it?” You already know the entire sequence of notes, you just need to store 1) the current index in the sequence you are playing and 2) when you started playing the note. Then, when you call the function to check on what you’re playing, you check millis() to see if it’s time to move to the next note. If it’s not, return and go do something else (like check the motion sensor). If it is, move to the next index and start playing the new note.

You know back when you were a kid, during a long car ride you’d piss off your parents “Are we there yet? Are we there yet? Are we there yet? Are we there yet…” Microcontrollers don’t get pissed off. In trying to do non-blocking code, you want to think of yourself as an annoying kid with ADHD.

It looks like you’ve attempted to follow some of the non-blocking patterns, but you failed miserably with the delays and for loop in playMelody.

ebolisa:
Thank you but I cannot alter the melody code as it will affect the tempo

Sure you can!! You can insert opportunities to stop the function.

void playMelody() {
  for (int i = 0; i < length; i++) {
    if (notes[i] == ' ') {
      if(gotInterruptFlag) return;  //<<<<<<<<<<<<<<<<<<<<<<<<<<<<
      delay(beats[i] * tempo); // rest
    } else {
      playNote(notes[i], beats[i] * tempo);
    }
    // pause between notes
    if(gotInterruptFlag) return;    //<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    delay(tempo / 2);
  }
}

@Jiggy Thanks but every time I play with the delay/millis in the melody function, the music sounds like a broken record. So I used @Bulldog suggestion and I came up with this:

#include <TimeLib.h>

const byte interruptPin = 3;  //I'm using Nano
volatile byte motion = LOW;


void setup() {
  pinMode(interruptPin, INPUT);   //connected to the motion sensor normally high
  attachInterrupt(digitalPinToInterrupt(interruptPin), checkMotion, FALLING);
}

void loop() {
  if (hour() == 8 && minute() == 0) playSound();  //no seconds used so it plays for approx a minute
  else motion = LOW; //best to reset just in case
}

void checkMotion() {
  motion = HIGH;
}

void playSound() {
  while (motion == LOW) {
    playMelody();
  }
}

void playMelody() {} //la la la

So I used @Bulldog suggestion and I came up with this

So, checkMotion() doesn't check anything. I can see why you chose that name, then.

ebolisa:
@Jiggy Thanks but every time I play with the delay/millis in the melody function, the music sounds like a broken record. So I used @Bulldog suggestion and I came up with this:

#include <TimeLib.h>

const byte interruptPin = 3;  //I’m using Nano
volatile byte motion = LOW;

void setup() {
 pinMode(interruptPin, INPUT);   //connected to the motion sensor normally high
 attachInterrupt(digitalPinToInterrupt(interruptPin), checkMotion, FALLING);
}

void loop() {
 if (hour() == 8 && minute() == 0) playSound();  //no seconds used so it plays for approx a minute
 else motion = LOW; //best to reset just in case
}

void checkMotion() {
 motion = HIGH;
}

void playSound() {
 while (motion == LOW) {
   playMelody();
 }
}

void playMelody() {} //la la la

I didn’t have a problem when I did it.

unsigned long note_duration = 0;
void run_play_menu()
{
  if( b_center.edge() && b_center.pressed() )
  {
    noTone( outPin );
    note = 0;
    play_intro = 1;
  }
  
  play_notes();
}

unsigned long note_begin_ms;
void play_notes()
{
  // Get current time
  unsigned long t_now_ms = millis();
  
  // If the current note has timed out...
  if( t_now_ms-note_begin_ms > note_duration )
  {
    noTone( outPin );
    unsigned int pitch;
    unsigned int length;
    
    pitch = pgm_read_word_near( (play_intro?intro:melody) + note );
    length = pgm_read_word_near( (play_intro?intro:melody) + note+1 );
    
    // The end of the song is marked by a sentinal value of 0 length.
    // When this is reached, end the intro and restart the melody.
    if( length!= 0 )
    {
      unsigned long time = (length*beat)/6;
      tone( outPin, pitch, time );
      note_duration = time + time/10;
      note_begin_ms = t_now_ms;
      note += 2;
    }
    else
    {
      play_intro = 0;
      note = 0;
    }
  }
}

run_play_menu() is called in loop as fast as possible, and includes an additional feature to restart the song from the intro when a button is pressed. It is able to detect the button press even in the middle of a note.

Full sketch attached if you want something to reference.

Song3.zip (7.69 KB)

It's actually been a while since I touched that sketch, it won't compile since I'm using the old PROGMEM style and also my own button library that I actually got rid of a long time ago.