Simultaneity...

...is that a word?

Anyway, my dumb noob question is this:

Is it possible to make two things happen (or at least appear to happen) simultaneously if they both involve functions that happen over time intervals that are perceptible on a human scale? I'm trying to build a little box controlled by a Nano that will simultaneously (a) play "Happy Birthday" and (b) scroll a " HAPPY BIRTHDAY" message across a 7-segment 8-digit MAX72XX display.

I've got two sketches working. The first (thank you Mr. Nick Gammon!) scrolls the "Happy Birthday" message:

//http://www.gammon.com.au/forum/?id=11516&reply=5#reply5

#include <SPI.h>
#include <bitBangedSPI.h>
#include <MAX7219.h>

const byte chips = 1;

// 1 chip, bit banged SPI on pins 6, 7, 8
MAX7219 display (chips, 6, 7, 8);  // Chips / LOAD / DIN / CLK

const char message [] = "HAPPY   BIRTHDAY    ";

void setup ()
  {
  display.begin ();
  }  // end of setup

unsigned long lastMoved = 0;
unsigned long MOVE_INTERVAL = 150;  // mS
unsigned int  messageOffset;

void updateDisplay ()
  {
  const unsigned int messageLength = strlen (message);
  
  // get each byte required
  for (byte i = 0; i < (8 * chips); i++)
    {
    // find the offset in the message array
    unsigned int charOffset = messageOffset + i;
    // if we have passed the end, go back to the start
    if (charOffset >= messageLength)
       charOffset -=  messageLength;
    // send that character
    display.sendChar (i, message [charOffset]);
    }
  // next time show one character on
  if (messageOffset++ >= messageLength)
    messageOffset = 0;
  }  // end of updateDisplay

void loop () 
  { 
    
  // update display if time is up
  if (millis () - lastMoved >= MOVE_INTERVAL)
    {
    updateDisplay ();
    lastMoved = millis ();
    }

  // do other stuff here    
     
  }  // end of loop

and the second (based on a sketch posted as a link in a thread by Jurs--thanks! ):

#include "musical_notes.h"

int speakerPin = 9; // speaker connected to digital pin 9 
      
void setup()    
{   
      pinMode(speakerPin, OUTPUT); // sets the speakerPin to be an output 
}    
      
void loop()
{    
      happyBirthday();
      delay(500);
         }     
     
void beep (int speakerPin, float noteFrequency, long noteDuration)
{    
  int x;
  // Convert the frequency to microseconds
  float microsecondsPerWave = 1000000/noteFrequency;
  // Calculate how many HIGH/LOW cycles there are per millisecond
  float millisecondsPerCycle = 1000/(microsecondsPerWave * 2);
  // Multiply noteDuration * number or cycles per millisecond
  float loopTime = noteDuration * millisecondsPerCycle;
  // Play the note for the calculated loopTime.
  for (x=0;x<loopTime;x++)   
          {   
              digitalWrite(speakerPin,HIGH); 
              delayMicroseconds(microsecondsPerWave); 
              digitalWrite(speakerPin,LOW); 
              delayMicroseconds(microsecondsPerWave); 
          } 
}     
     

void happyBirthday(){
  beep(speakerPin, note_Bb4,400); //B b  
           delay(25);
  beep(speakerPin, note_Bb4,200); //B b  
           delay(25);
  beep(speakerPin, note_C5,600); //C
           delay(25);
  beep(speakerPin, note_Bb4,600); //B b  
           delay(100);
  beep(speakerPin, note_Eb5,600); //E b      
           delay(100);
  beep(speakerPin, note_D5,900); //D
           delay(400);


  beep(speakerPin, note_Bb4,400); //B b  
           delay(25);
  beep(speakerPin, note_Bb4,200); //B b  
           delay(25);
  beep(speakerPin, note_C5,600); //C
           delay(25);
  beep(speakerPin, note_Bb4,600); //B b  
           delay(100);
  beep(speakerPin, note_F5,600); //F
           delay(100);
  beep(speakerPin, note_Eb5,900); //E b      
           delay(400);
          

  beep(speakerPin, note_Bb4,400); //B b  
           delay(25);
  beep(speakerPin, note_Bb4,200); //B b  
          delay(25);
  beep(speakerPin, note_Bb5,600); //B b up an octave
           delay(25);
  beep(speakerPin, note_G5,600); //G
            delay(100);
  beep(speakerPin, note_Eb5,600); //Eb   
          delay(100);
  beep(speakerPin, note_D5,800); //D
              delay(100);
  beep(speakerPin, note_C5,1000); //C
                delay(400);
                
                
  beep(speakerPin, note_Ab5,400); //A b  
           delay(25);
  beep(speakerPin, note_Ab5,200); //A b  
           delay(25);
  beep(speakerPin, note_G5,600); //G
           delay(100);
  beep(speakerPin, note_Eb5,600); //E b  
           delay(100);
  beep(speakerPin, note_F5,600); //F
           delay(100);
  beep(speakerPin, note_Eb5,1000); //E b      
           delay(2500);

          }

plays the song.

I haven't figured out how to merge the two and I wonder if it's even possible to do without disrupting the rhythm of the music or making the scrolling seem jerky. Have any of you more experienced coders got any ideas you'd be willing to share?

Thanks!

You can make things to appear happen simultaneously.
Using delays in the second sketch will be a problem.
There is a technique that uses millis() that can be used to remove delays, (see the blink without delay sketch)
Also see Demonstration code for several things at the same time - Project Guidance - Arduino Forum

Thanks, LarryD, I read through that thread and didn't see (or at least comprehend) how it might apply to my situation. The need to use delays to make the birthday song sound right (it's for my son) is what makes me wonder if the project is feasible. I've considered putting two nano's in the box wired to a common switched power source to avoid the problem--one to run the display and the other to play the song. They don't cost anything (I'm using the Chinese knockoffs for this project, about $2 apiece), but it seems so ...brute force. I was hoping for a more elegant solution.

Do you understand how you can use the BWD technique to replace delay() ?

  // update display if time is up
  if (millis () - lastMoved >= MOVE_INTERVAL)
    {
    updateDisplay ();
    lastMoved = millis ();
    }

  // do other stuff here

This code checks to see if it is time to proceed with the display section.
You would need something similar to do the music.

In this topic, I used a '1284P to create up to 13 simultaneous tones based on 13 buttons.
Uno ('328P) can do the same for 10 notes (fewer IO pins).
http://forum.arduino.cc/index.php?topic=179761.0
Code, schematic, youtube video too.

Thanks, gents!

Good to hear that it's feasible, I just need to upgrade my skill level. I'm new to coding C++, I've only started messing around with Arduinos a few months ago. The last programming I did was back in the '70's using BASIC and FORTRAN. Yes, I'm that old... ;-}

LarryD, I think I get the concept, but I need to tinker with the code "hands-on" to see if I can make it work. I'll keep trying.

OK, I built the circuit and ran the sketch that Robin2 posted, it works like he says it should. I think I can figure out how to modify my sketch in that fashion.

So, if I rewrite my sound sketch to use the millis () function instead of delays, can the arduino play the entire song, which lasts about 15 seconds, while continually scrolling the message across the led arrays? Or will I need to suspend scrolling while the song is playing? I can imagine using a counter or a "while" loop to alternate the two halves of the sketch, but I'd rather the whole thing works simultaneously.

OK, using the BWD sketch as a guide, I wrote a sketch that plays the song without using any delays, and I've got it working. My wife has advised me that if I ever sing "Happy Birthday" in the house again, I'm a dead man...

Now I need to incorporate my new sketch into Nick Gammon's scrolling sketch and I'll be home free.

Thanks for the help!

I've got it almost working. It will play the song one time, with the display blank, then begins scrolling the message continuously, but doesn't repeat the song.

I tried to post the code, but it's apparently too long, I get an error message saying that I have exceeded the 9,000 character limit. Is there a way around that?

Show us your current sketch.
You can zip it

Add your scrolling display code to this sketch:

#include "musical_notes.h"

int speakerPin = 9; // speaker connected to digital pin 9 

unsigned long currentMillis2;
unsigned long delayTime;
byte note = 0;

void setup()    
{   
  pinMode(speakerPin, OUTPUT); // sets the speakerPin to be an output 
  currentMillis2 = millis();
  delayTime = 0;
}    

void loop()
{    
  if( millis() - currentMillis2 >= delayTime)
  {
    happyBirthday();
  }
}     

void beep (int speakerPin, float noteFrequency, long noteDuration)
{    
  int x;
  // Convert the frequency to microseconds
  float microsecondsPerWave = 1000000/noteFrequency;
  // Calculate how many HIGH/LOW cycles there are per millisecond
  float millisecondsPerCycle = 1000/(microsecondsPerWave * 2);
  // Multiply noteDuration * number or cycles per millisecond
  float loopTime = noteDuration * millisecondsPerCycle;
  // Play the note for the calculated loopTime.
  for (x=0;x<loopTime;x++)   
  {   
    digitalWrite(speakerPin,HIGH); 
    delayMicroseconds(microsecondsPerWave); 
    digitalWrite(speakerPin,LOW); 
    delayMicroseconds(microsecondsPerWave); 
  } 
}     


void happyBirthday(){
  switch(note)
  {
  case 0:
    beep(speakerPin, note_Bb4,200); //B b  
    currentMillis2 = millis();
    delayTime = 25;
    note++;
    break;

  case 1:
    beep(speakerPin, note_Bb4,100); //B b  
    currentMillis2 = millis();
    delayTime = 25;
    note++;
    break;

  case 2:
    beep(speakerPin, note_C5,300); //C
    currentMillis2 = millis();
    delayTime = 25;
    note++;
    break;

  case 3:
    beep(speakerPin, note_Bb4,300); //B b  
    currentMillis2 = millis();
    delayTime = 100;
    note++;
    break;

  case 4:
    beep(speakerPin, note_Eb5,300); //E b      
    currentMillis2 = millis();
    delayTime = 100;
    note++;
    break;

  case 5:
    beep(speakerPin, note_D5,450); //D
    currentMillis2 = millis();
    delayTime = 400;
    note++;
    break;

  case 6:
    beep(speakerPin, note_Bb4,200); //B b  
    currentMillis2 = millis();
    delayTime = 25;
    note++;
    break;

  case 7:
    beep(speakerPin, note_Bb4,100); //B b  
    currentMillis2 = millis();
    delayTime = 25;
    note++;
    break;

  case 8:
    beep(speakerPin, note_C5,300); //C
    currentMillis2 = millis();
    delayTime = 25;
    note++;
    break;

  case 9:
    beep(speakerPin, note_Bb4,300); //B b  
    currentMillis2 = millis();
    delayTime = 100;
    note++;
    break;

  case 10:
    beep(speakerPin, note_F5,300); //F
    currentMillis2 = millis();
    delayTime = 100;
    note++;
    break;

  case 11:
    beep(speakerPin, note_Eb5,450); //E b      
    currentMillis2 = millis();
    delayTime = 400;
    note++;
    break;

  case 12:
    beep(speakerPin, note_Bb4,200); //B b  
    currentMillis2 = millis();
    delayTime = 25;
    note++;
    break;

  case 13:
    beep(speakerPin, note_Bb4,100); //B b  
    currentMillis2 = millis();
    delayTime = 25;
    note++;
    break;

  case 14:
    beep(speakerPin, note_Bb5,300); //B b up an octave
    currentMillis2 = millis();
    delayTime = 25;
    note++;
    break;

  case 15:
    beep(speakerPin, note_G5,300); //G
    currentMillis2 = millis();
    delayTime = 100;
    note++;
    break;

  case 16:
    beep(speakerPin, note_Eb5,300); //Eb   
    currentMillis2 = millis();
    delayTime = 100;
    note++;
    break;

  case 17:
    beep(speakerPin, note_D5,400); //D
    currentMillis2 = millis();
    delayTime = 100;
    note++;
    break;

  case 18:
    beep(speakerPin, note_C5,500); //C
    currentMillis2 = millis();
    delayTime = 400;
    note++;
    break;

  case 19:
    beep(speakerPin, note_Ab5,200); //A b  
    currentMillis2 = millis();
    delayTime = 25;
    note++;
    break;

  case 20:
    beep(speakerPin, note_Ab5,100); //A b  
    currentMillis2 = millis();
    delayTime = 25;
    note++;
    break;

  case 21:
    beep(speakerPin, note_G5,300); //G
    currentMillis2 = millis();
    delayTime = 100;
    note++;
    break;

  case 22:
    beep(speakerPin, note_Eb5,300); //E b  
    currentMillis2 = millis();
    delayTime = 100;
    note++;
    break;

  case 23:
    beep(speakerPin, note_F5,300); //F
    currentMillis2 = millis();
    delayTime = 100;
    note++;
    break;

  case 24:
    beep(speakerPin, note_Eb5,500); //E b      
    currentMillis2 = millis();
    delayTime = 2500;
    note++;
    break;

  case 25:
    note = 0;
    break;

  }
}

Here's what I've got so far. I cut out the last three stanzas of the song so it'll fit the forum. You probably know how it goes, anyway...

//http://www.gammon.com.au/forum/?id=11516&reply=5#reply5

#include <SPI.h>
#include <bitBangedSPI.h>
#include <MAX7219.h>
#include "musical_notes.h"

const byte chips = 1; // 1 chip, bit banged SPI on pins 6, 7, 8
MAX7219 display (chips, 6, 7, 8);  // Chips / LOAD / DIN / CLK

const char message [] = "HAPPY   BIRTHDAY   OTIS            ";

int speakerPin = 9; // speaker connected to digital pin 9 
      
void setup()    
{   
      pinMode(speakerPin, OUTPUT); // sets the speakerPin to be an output 
        display.begin ();
          happyBirthday();
  } 

unsigned long lastMoved = 0;
unsigned long MOVE_INTERVAL = 150;  // mS
unsigned int  messageOffset;

void updateDisplay ()
  {
  const unsigned int messageLength = strlen (message);
  
  // get each byte required
  for (byte i = 0; i < (8 * chips); i++)
    {
    // find the offset in the message array
    unsigned int charOffset = messageOffset + i;
    // if we have passed the end, go back to the start
    if (charOffset >= messageLength)
       charOffset -=  messageLength;
    // send that character
    display.sendChar (i, message [charOffset]);
    }
  // next time show one character on
  if (messageOffset++ >= messageLength)
    messageOffset = 0;
  }  // end of updateDisplay

void loop () 
  { 
    
  // update display if time is up
  if (millis () - lastMoved >= MOVE_INTERVAL)
    {
    updateDisplay ();
    lastMoved = millis ();
    }
         }     
     
void beep (int speakerPin, float noteFrequency, long noteDuration)
{    
  int x;
  // Convert the frequency to microseconds
  float microsecondsPerWave = 1000000/noteFrequency;
  // Calculate how many HIGH/LOW cycles there are per millisecond
  float millisecondsPerCycle = 1000/(microsecondsPerWave * 2);
  // Multiply noteDuration * number or cycles per millisecond
  float loopTime = noteDuration * millisecondsPerCycle;
  // Play the note for the calculated loopTime.
  for (x=0;x<loopTime;x++)   
          {   
              digitalWrite(speakerPin,HIGH); 
              delayMicroseconds(microsecondsPerWave); 
              digitalWrite(speakerPin,LOW); 
              delayMicroseconds(microsecondsPerWave); 
          } 
}     
     void happyBirthday(){
   
    unsigned long currentMillis = millis();  //initialize currentMillis
    unsigned long previousMillis = millis(); //initialize previousMillis
    
    beep(speakerPin, note_Bb4,400); //B b for 400 ms 
    if(currentMillis - previousMillis > 400) {
    previousMillis = currentMillis; }  
  
    beep(speakerPin, note_Silence,25); //Silence for 25 ms
    if(currentMillis - previousMillis > 25) {
    previousMillis = currentMillis;  } 
  
    beep(speakerPin, note_Bb4,200); //B b  for 200 ms 
    if(currentMillis - previousMillis > 200) {
    previousMillis = currentMillis;   }
    
    beep(speakerPin, note_Silence,25); //Silence for 25 ms
    if(currentMillis - previousMillis > 25) {
    previousMillis = currentMillis;   }
   
    beep(speakerPin, note_C5,600); //C for 600 ms
    if(currentMillis - previousMillis > 600) {
    previousMillis = currentMillis;}  
  
    beep(speakerPin, note_Silence,25); // Silence for 25 ms
    if(currentMillis - previousMillis > 25) {
    previousMillis = currentMillis; }  
    
    beep(speakerPin, note_Bb4,600); //B b for 600 ms
    if(currentMillis - previousMillis > 600) {
    previousMillis = currentMillis;}
        
  
   
 }

Did you try the offering in reply #13 ?

LarryD: Not yet, but I will try it--thanks!

That appears to play the song and then move the display. I though you wanted to do both at once?

You're still using delays inside the beep() function. They need to go. What is wrong with using the tone() function? That will play a sound and it doesn't make the processor wait while it's playing.

Then your main loop should look like:

void loop ()
{
  happyBirthday();  //This should check if the note needs to be changed based on its own timer and then return immediately
  // update display if time is up
  if (millis () - lastMoved >= MOVE_INTERVAL)
  {
    updateDisplay ();
    lastMoved = millis ();
  }
}

This requires that updateDisplay() has a short duration, as it may upset the timing of the song.

MorganS: I see what you mean--I didn't notice those delays. Thanks!

If I use tone(), can I do away with all this:

void beep (int speakerPin, float noteFrequency, long noteDuration)
{    
  int x;
  // Convert the frequency to microseconds
  float microsecondsPerWave = 1000000/noteFrequency;
  // Calculate how many HIGH/LOW cycles there are per millisecond
  float millisecondsPerCycle = 1000/(microsecondsPerWave * 2);
  // Multiply noteDuration * number or cycles per millisecond
  float loopTime = noteDuration * millisecondsPerCycle;
  // Play the note for the calculated loopTime.
  for (x=0;x<loopTime;x++)   
          {   
              digitalWrite(speakerPin,HIGH); 
              delayMicroseconds(microsecondsPerWave); 
              digitalWrite(speakerPin,LOW); 
              delayMicroseconds(microsecondsPerWave);

and just call tone(pin, frequency,duration) for each note, with notone() in between to separate the notes?

tone() is self contained.
You tell it the fequency you want and the duration.
It is none blocking.

The section of code I posted in #13 has the delays removed form happybirthday () there are still delays in beep().
However, you will now have your display and song both happening at the same time in a reasonable fashion.

I can't get the sound right using the notes in the pitches.h file, but the sketch LarryD posted in #13 sounds perfect.

How is the best way to nest the loops for the two sketches to make them run simultaneously? Do I need a separate void() loop for the scrolling sketch or does it nest in the same loop with the song?

(Dumb questions, I know, but I'm real new at this--thanks for your help and patience!)