using millis to get round delays inside an if statement (xylophone servo)

hello , for a project I'm currently working on I'm using servo (atm may switch to solenoids) to play notes on a physical instrument. I tried using midi as a way of storing songs and telling the arduino which notes to play (which servos to write to what positions) But i didn't progress very far with the tutorials and I am quite pushed for time.

my solution was to laboriously transcribe the songs I had written into a long list of servo.write commands. however another part of the project is that I want users to be able to control one other servo independantly while the other servo's play the songs. the problem with this I've just realised is my code is obviously full of delays.

the code below is representative of the way tracks will be played by the servos but does not feature the control structures I will be using to initiate these sequences , these are in another sketch which is still in development.

#include <Servo.h> 
 
Servo C3;  // create servo object to control a servo 
Servo D3;  // each servo object correpsonds to it's note
Servo E3;
Servo G3;
Servo B3;
Servo D4;
Servo F3;// a maximum of eight servo objects can be created 
 
   
 
void setup() 
{ 
   C3.attach(9);  // attaches the servos to different pins on the arduino 
   D3.attach(8);
   E3.attach(7);
   G3.attach(6);
   B3.attach(5);
   D4.attach(4); 
   F3.attach(3);
  }
 
 
void loop() 
{

B3.write (110);
G3.write (110);
D3.write (110);
delay (150);
B3.write (60);
G3.write (60);
D3.write (60);
delay (300);
B3.write (110);
delay (150);
B3.write (60);
delay (300);
B3.write (110);
F3.write (110);
delay (150);
B3.write (60);
F3.write (60);
delay (150);
D4.write (110);
delay (150);
D4.write (60);
delay(300);
B3.write (110);
G3.write (110);
D3.write (110);
delay (150);
B3.write (60);
G3.write (60);
D3.write (60);
delay (300);
B3.write (110);
delay (150);
B3.write (60);
delay (300);
B3.write (110);
F3.write (110);
C3.write (110);
delay (150);
B3.write (60);
F3.write (60);
C3.write (60);
delay (300);
B3.write (110);



}

I looked a bit at the mills function and I thought I could try something like

if (condition to start song sequence:this will be a string name being altered by the user via a website)
millis =0
if mills = (the time in the track where the first note is play)
servo.write(110)
ect ect

I thought that this way mills would always be set to zero at the beginning of any song and i then I could use the time in micro seconds to count when the servos need to strike the instrument ?
I can't test this properly yet as I don't have my servos. is this a plausible way round my problem or am I way of the mark , I have never used mills before ?

for a little extra clarity the user will see on the webpage an interface allowing them to select track but also a live camera and audio stream the mic and cam of which I want to mount on a servo or d.c motor to allow them to rotate their perspective , This is also important because the audio stream will be binaural.

thanks very much
sam

just tested with this sketch

unsigned long time;

void setup(){
  Serial.begin(9600);
}
void loop(){
  Serial.print("Time: ");
  time = millis();
  //prints time since program started
  
  // wait a second so as not to send massive amounts of data
  delay(1000);
  
  
  int sensorValue = analogRead(A0);
  Serial.println(sensorValue);
  delay(1);        // delay in between reads for stability


if (sensorValue > 500) {
time=0;

}
Serial.println(time);
}

it seems millis can be set to zero but when the condition is removed it reverts back to it the number in it's sequence? or to put it simply is doesn't lose count?

Could i maybe try

if (time = time + (the amount of time in microseconds when the first note is played) {
servo.write (110);
ect
ect

?

cheers
sam

Don't bother setting millis() to zero, even if you can. Just play the next note when enough time has elapsed

start of loop
  if start signal received and nowPlaying is false
    set timeStarted = millis()
    set nowPlaying to true
  end if
  
  if millis() - timeStarted > = interval
    play the next note
    set timeStarted = millis()
  end if
end of loop

ok cheers not quite sure I understand though

start of loop
  if start signal received and nowPlaying is false
    set timeStarted = millis()
    set nowPlaying to true
  end if

is now playing a boolean value ?

I found this code online for controlling the blinks of an LED

void loop()
{
  currentTime = millis();
  if(currentTime >= (loopTime + 1000)){  
    digitalWrite(ledPin, !digitalRead(ledPin));   // toggles the LED on/off
    loopTime = currentTime;  // Updates loopTime
  }

which I though I could replicate for each note (inside an if statement) changing the +1000 to correspond to the times of the notes?

I wrote a demo sketch recently that uses the "Blink without delay" concept to control 3 leds, a switch and a servo. It might give you an idea of how to deal with your project. It could be modified to manage lots more things
http://forum.arduino.cc/index.php?topic=223286.0

...R

samhallett:
ok cheers not quite sure I understand though

start of loop

if start signal received and nowPlaying is false
   set timeStarted = millis()
   set nowPlaying to true
 end if




is now playing a boolean value ?

I found this code online for controlling the blinks of an LED



void loop()
{
 currentTime = millis();
 if(currentTime >= (loopTime + 1000)){  
   digitalWrite(ledPin, !digitalRead(ledPin));   // toggles the LED on/off
   loopTime = currentTime;  // Updates loopTime
 }




which I though I could replicate for each note (inside an if statement) changing the +1000 to correspond to the times of the notes?

In my pseudo code nowPlaying is a boolean. I put it in to prevent any problems if the start signal occurred again or persisted when the note was already playing.

There is no reason for interval in my code to be a fixed value. You could have two arrays, one with the note to be played and the other the duration of the note. Using a common index into the array would allow a note to be played for the appropriate duration.

I like things that play music.

Let me see if I get this properly.

write(110) must be hitting the note, and write(60) pulling them back up to their rest position, correct? Based on that assumption, we can do this.

First off is how to store the notes in an easily accessible way. Since you have 7 Servos, a packed byte is perfect for the job. To that end, we can put the following #defines in your code:

#define C3_BIT 0
#define D3_BIT 1
#define E3_BIT 2
#define G3_BIT 3
#define B3_BIT 4
#define D4_BIT 5
#define F3_BIT 6

This way, you can make an array of bytes, with each bit in the byte corresponding to whether the servo is played that time or not. For a rest, just leave everything blank.

Creating the packed byte is easy with the _BV() macro. Here's the first few notes from the example you gave:

byte notes[] = 
{
  _BV(B3_BIT) | _BV(G3_BIT) | _BV(D3_BIT),
  _BV(B3_BIT),
  _BV(B3_BIT) | _BV(F3_BIT),
  _BV(D4_BIT)
}

// Number of notes
#define NO_NOTES 4

You can extend that as long as you need for the song you want to play, so long as you update the note value appropriately. Then, when you want to test the byte to see if you need to drop the servo, you can use the bit_is_set() macro like this:

if( bit_is_set(notes[note_index], B3_BIT) )
  B3.write( 110 );

if( bit_is_set(notes[note_index], C3_BIT) )
  C3.write( 110 );

I haven't used the Servo library before, but there's probably no penalty to writing a position that the servo's already in. So you can create a reset_servos() function that writes all the servos to 60 to reset them, without having to keep track of which ones you put down.

You can use UKBob's advice on creating a parallel array for the duration of the note as well. This should be enough to get you a good start.

thanks jiggly ninja that is such a better way to arrange my servos !

cheers
sam

and yeah write (110) is strike note and write (60) is retract.

ok been having a few problems with this , me and my lecturer were trying to get it working using an LED to represent the note playing and a potentiometer as the start signal.

heres the code

boolean nowplaying=false;
unsigned long timestarted;
int led = 7;
//boolean ison =false;

void setup() {
  // put your setup code here, to run once:
  pinMode(led, OUTPUT);
  Serial.begin(9600);
}

void loop() {

  int sensorValue = analogRead(A0); 



  if (nowplaying = false && sensorValue <300) {
    timestarted=millis();
    nowplaying = true;
  }


  if (millis() - timestarted >= 400) {
    digitalWrite(led, HIGH);
    timestarted=millis();
    Serial.println("shouldbeon1");




  }


  if (millis() - timestarted >= 800) {
    digitalWrite(led, LOW);
    timestarted=millis();
    Serial.println("shouldbeon2");
  }


}

I firstly saw the led was not switching off so I then added the Serial.println("shouldbeon2"); to check if that if ever ran and it doesn't ?

cheers
sam

This will never be true

  if (millis() - timestarted >= 800) 
  {
    digitalWrite(led, LOW);
    timestarted = millis();
    Serial.println("shouldbeon2");
  }

because timeStarted gets reset to the current millis() value when
  if (millis() - timestarted >= 400)is true.

There is no reason why you cannot have 2 variables for different start times or stick with one variable and flip the state of the LED each time the period ends.

ok I understand now why it doesn't work but how would I change it in terms of telling my servo's when to play notes , if I had a different variable for every start time (every note play?) would I not end up with hundreds of variables

sorry If I am being dense

Cheers sam

Read the note to be played from an array and the duration that it is to be played from a second array using the same index, as previously suggested. Play the appropriate note for the appropriate duration using the millis() timer technique. When the play period is over stop playing the note.

Now read the next note and duration and play it.

Repeat until you run out of notes, perhaps by having a special value in the note array to indicate that the notes have all been played.

ok i'm really sorry but I still don't understand. what I am aiming to create is a condition that when it becomes true initiates a timed sequence of servo.write commands using the millis timer method. what your saying is I should have all my servo's in an array and all my durations in another array
so
if condition for starting //leaving out the boolean an for a moment
if (millis-timestarted =< myintervals [6]
my servos[3].write (110)
timestarted = millis();

what I don't understand is how I get to the next note in the sequence without

This will never be true
Code:
if (millis() - timestarted >= 800)
{
digitalWrite(led, LOW);
timestarted = millis();
Serial.println("shouldbeon2");
}
because timeStarted gets reset to the current millis() value when
Code:
if (millis() - timestarted >= 400)
is true.

There is no reason why you cannot have 2 variables for different start times or stick with one variable and flip the state of the LED each time the period ends

this problem meaning that millis never becomes greater than the second interval ?

See what you make of this

const byte notesToPlay[] = {1, 5, 2, 99};
const unsigned long durations[] = {5000, 2000, 3000};
unsigned long timeStartedPlaying;

byte noteIndex = 0;
boolean playing = false;

void setup() 
{
  Serial.begin(115200);
}

void loop() 
{
  if (!playing)
  {
    Serial.print("Playing note\t");
    Serial.print(notesToPlay[noteIndex]);
    Serial.print("\tfor ");
    Serial.print(durations[noteIndex]);
    Serial.println(" milliseconds");
    timeStartedPlaying = millis();
    playing = true;
  }

  if (playing && millis() - timeStartedPlaying >= durations[noteIndex])
  {
    playing = false;
    noteIndex++;
    timeStartedPlaying = millis();
  }
  if (notesToPlay[noteIndex] == 99)
  {
    Serial.println("Finished playing");
    while(1); 
  }
}

As it stands the program simply loops for ever when the notes have been played but this could be changed.

ok , I will try to understand that programme , thanks for helping. I uploaded it and checked the serial monitor but got only this

\?-Û¶zÛ¶ ?

I am struck by the fact that the coding is getting quite out of my depth and i'm wondering whether I might be better off trying to use threads to remove the delays from my programme. also am I even right in thinking I'll be able to create a linear sequence of timed events that corresponds to a pre-composed melody?

i'll go through the code you posted last and research any terms I don't understand.

cheers
sam

okay Ive gone through and annotated your code and I think I have a much better idea about the advice your were trying to give me.I think I was thinking about it in terms of coding a song in a linear load of code and using arrays to make the job a bit qiuker but this way is more like the sequence is stored and read inside the arrays themselves.

const byte notesToPlay[] = {1, 5, 2, 99}; // an index of notes to play
const unsigned long durations[] = {5000, 2000, 3000}; // 3 varible durations 
unsigned long timeStartedPlaying; // a varible 

byte noteIndex = 0; // a byte which eqauls zero 
boolean playing = false; //  boolean

void setup() 
{
  Serial.begin(115200);
}

void loop() 
{
  if (!playing)// if the boolean varible is false which it is then
  {
    Serial.print("Playing note\t");
    Serial.print(notesToPlay[noteIndex]);//print the 1st note (as is the one thats 0 indexed and note index is 0 at this first point)
    Serial.print("\tfor ");
    Serial.print(durations[noteIndex]); // print the 1st duration
    Serial.println(" milliseconds");
    timeStartedPlaying = millis(); //print these 
    playing = true; // then set the boolean to true
  }

  if (playing && millis() - timeStartedPlaying >= durations[noteIndex]) // if boolean is true (which it is
 // and millis - time start playing (which is eqaul to millis) so I would say that is like saying x-x is greater than or eqaul to I would assumed 5000 as it is the 0 indexed number in durations , so it is really saying way for 0 to become larger than 5000 or wait for 5 seconds , or the next amount of time depending one what note index number where on
  {
    playing = false; //start the printing section again
    noteIndex++; // but add 1 to note indext so that the terms used will be the second indexted points 
    timeStartedPlaying = millis(); //timestartes playing = millis 
  }
  if (notesToPlay[noteIndex] == 99) // if the last indexed term in notes to play is reached (the fourth time this code repeats)
  {
    Serial.println("Finished playing"); // then print finished playing
    while(1); // don't understand this bit.
  }
}

I think I'm mostly with you now although I still don't understand what the while (1) is for. also when I need to trigger to servos at the same time is there a way to do this inside the array's ? also I am now wondering what it just printed those weird characters when I uploaded it?

cheers for helping me
sam

Your comments are just about right. You need to adjust the baud rate in the program to match that of the serial monitor, or vice versa, to see the serial output. while(1);is an endless loop to stop the program doing anything else. It is only there for demonstration purposes. In practice you would probably make the playing of a note sequence a function and return to the main program to get more user input when it finishes.

You could play 2 notes at once in a number of ways. One way would be (yet) another array for the second note. There would need to be an entry in each array even if the second note would not always be played. An entry of zero could serve for this.

A second way would be to have double entries in one array so you would play note 0 and note 1, then 2 and 3, 4 and 5 etc. As before you would need dummy entries when no second note was to be played.

A third way would be to use a 2 dimensional array to hold the notes. Dimension 0 holds the first note and dimension 1 holds the second.

Any of these could get complicated so I suggest that you get one note at a time working first. Get my code working to print output to the serial monitor first then explore ways to use the principals to move servos. Only then experiment with multiple notes at the same time. What is the maximum number of notes needed to play at the same time ?

cheers helibob , I get that while (1) now , the 2 dimensional array sounds like the best solution but I would expect as you say the most complicated , I suppose 3 would be the most I'd need at any time , for chords.

I'll do what you say now and get experimenting with the principals of the programme

thanks allot
sam

just did this as a test for making one servo strike then retract and then another servo do the same.

#include <Servo.h> 
const byte notesToPlay[] = {1, 5, 2, 4, 99}; // an index of notes to play
const unsigned long durations[] = {1500, 1500,1500, 3000}; // 3 varible durations 
unsigned long timeStartedPlaying; // a varible 

byte noteIndex = 0; // a byte which eqauls zero 
boolean playing = false; //  boolean
Servo d3;
Servo b3;

void setup() 
{
  Serial.begin(115200);
  b3.attach(6);
  d3.attach(5);
}

void loop() 
{
  if (!playing)// if the boolean varible is false which it is then
  {
    Serial.print("Playing note\t");
    Serial.print(notesToPlay[noteIndex]);//print the 1st note (as is the one thats 0 indexed and note index is 0 at this first point)
    Serial.print("\tfor ");
    Serial.print(durations[noteIndex]); // print the 1st duration
    Serial.println(" milliseconds");
    timeStartedPlaying = millis(); //print these 
    playing = true; // then set the boolean to true
    
   
    
    if (notesToPlay[noteIndex]==1) {
    d3.write(110);
    }
    if (notesToPlay[noteIndex]==5) {
    d3.write (60);
    }
    
    if (notesToPlay[noteIndex]==2){
      b3.write (110);
    }
    
    if (notesToPlay[noteIndex]==4)
    b3.write (60);
      
  }

  if (playing && millis() - timeStartedPlaying >= durations[noteIndex]) // if boolean is true (which it is
 // and millis - time start playing (which is eqaul to millis) so I would say that is like saying x-x is greater than or eqaul to I would assumed 5000 as it is the 0 indexed number in durations 
  {
    playing = false; //start the printing section again
    noteIndex++; // but add 1 to note indext so that the terms used will be the second indexted points 
    timeStartedPlaying = millis(); //timestartes playing = millis 
  }
  if (notesToPlay[noteIndex] == 99) // if the last indexed term in notes to play is reached (the fourth time this code repeats)
  {
    Serial.println("Finished playing"); // then print finished playing
    while(1); // don't understand this bit.
  }
}

I know this probably isn't a very good solution but I thought this way I could do

 if (notesToPlay[noteIndex]==4)
    b3.write (60);
    g3write  (60);

for getting 2 notes to play virtually simultaneously. should I be looking at storing the servos and maybe their positions in the arrays instead of using notes played as a timed control structure?

cheers
sam