How to make `setInterval` behave more in sync, or how to use `setTimeout` instead?

JavascriptSettimeoutSetintervalTiming

Javascript Problem Overview


I am working on a music program that requires multiple JavaScript elements to be in sync with another. I’ve been using setInterval, which works really well initially. However, over time the elements gradually become out of sync which is bad in a music program.

I’ve read online that setTimeout is more accurate, and you can have setTimeout loops somehow. However, I have not found a generic version that illustrates how this is possible.

Basically I have some functions like such:

//drums
setInterval(function {
  //code for the drums playing goes here
}, 8000);

//chords
setInterval(function {
  //code for the chords playing goes here
}, 1000);

//bass
setInterval(function {
  //code for the bass playing goes here
}, 500);

It works super well, initially, but over the course of about a minute, the sounds become noticeably out of sync as I’ve read happens with setInterval. I’ve read that setTimeout can be more consistently accurate.

Could someone just show me a basic example of using setTimeout to loop something indefinitely? Alternatively, if there is a way to achieve more synchronous results with setInterval or even another function, please let me know.

Javascript Solutions


Solution 1 - Javascript

You can create a setTimeout loop using recursion:

function timeout() {
    setTimeout(function () {
        // Do Something Here
        // Then recall the parent function to
        // create a recursive loop.
        timeout();
    }, 1000);
}

The problem with setInterval() and setTimeout() is that there is no guarantee your code will run in the specified time. By using setTimeout() and calling it recursively, you're ensuring that all previous operations inside the timeout are complete before the next iteration of the code begins.

Solution 2 - Javascript

Only to supplement. If you need to pass a variable and iterate it, you can do just like so:

function start(counter){
  if(counter < 10){
    setTimeout(function(){
      counter++;
      console.log(counter);
      start(counter);
    }, 1000);
  }
}
start(0);

Output:

1
2
3
...
9
10

One line per second.

Solution 3 - Javascript

Given that neither time is going to be very accurate, one way to use setTimeout to be a little more accurate is to calculate how long the delay was since the last iteration, and then adjust the next iteration as appropriate. For example:

var myDelay = 1000;
var thisDelay = 1000;
var start = Date.now();

function startTimer() {    
    setTimeout(function() {
        // your code here...
        // calculate the actual number of ms since last time
        var actual = Date.now() - start;
        // subtract any extra ms from the delay for the next cycle
        thisDelay = myDelay - (actual - myDelay);
        start = Date.now();
        // start the timer again
        startTimer();
    }, thisDelay);
}

So the first time it'll wait (at least) 1000 ms, when your code gets executed, it might be a little late, say 1046 ms, so we subtract 46 ms from our delay for the next cycle and the next delay will be only 954 ms. This won't stop the timer from firing late (that's to be expected), but helps you to stop the delays from pilling up. (Note: you might want to check for thisDelay < 0 which means the delay was more than double your target delay and you missed a cycle - up to you how you want to handle that case).

Of course, this probably won't help you keep several timers in sync, in which case you might want to figure out how to control them all with the same timer.

So looking at your code, all your delays are a multiple of 500, so you could do something like this:

var myDelay = 500;
var thisDelay = 500;
var start = Date.now();
var beatCount = 0;

function startTimer() {    
    setTimeout(function() {
        beatCount++;
        // your code here...
        //code for the bass playing goes here  

        if (count%2 === 0) {
            //code for the chords playing goes here (every 1000 ms)
        }

        if (count%16) {
            //code for the drums playing goes here (every 8000 ms)
        }

        // calculate the actual number of ms since last time
        var actual = Date.now() - start;
        // subtract any extra ms from the delay for the next cycle
        thisDelay = myDelay - (actual - myDelay);
        start = Date.now();
        // start the timer again
        startTimer();
    }, thisDelay);
}

Solution 4 - Javascript

The best way to deal with audio timing is with the Web Audio Api, it has a separate clock that is accurate regardless of what is happening in the main thread. There is a great explanation, examples, etc from Chris Wilson here:

http://www.html5rocks.com/en/tutorials/audio/scheduling/

Have a look around this site for more Web Audio API, it was developed to do exactly what you are after.

Solution 5 - Javascript

According to your requirement

> just show me a basic example of using setTimeout to loop something

we have following example which can help you

var itr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var  interval = 1000; //one second
itr.forEach((itr, index) => {

  setTimeout(() => {
    console.log(itr)
  }, index * interval)
})

Solution 6 - Javascript

Use setInterval()

setInterval(function(){
 alert("Hello"); 
}, 3000);

The above will execute alert("Hello"); every 3 seconds.

Solution 7 - Javascript

I use this way in work life: "Forget common loops" in this case and use this combination of "setInterval" includes "setTimeOut"s:

    function iAsk(lvl){
        var i=0;
        var intr =setInterval(function(){ // start the loop 
            i++; // increment it
            if(i>lvl){ // check if the end round reached.
                clearInterval(intr);
                return;
            }
            setTimeout(function(){
                $(".imag").prop("src",pPng); // do first bla bla bla after 50 millisecond
            },50);
            setTimeout(function(){
                 // do another bla bla bla after 100 millisecond.
                seq[i-1]=(Math.ceil(Math.random()*4)).toString();
                $("#hh").after('<br>'+i + ' : rand= '+(Math.ceil(Math.random()*4)).toString()+' > '+seq[i-1]);
                $("#d"+seq[i-1]).prop("src",pGif);
                var d =document.getElementById('aud');
                d.play();                   
            },100);
            setTimeout(function(){
                // keep adding bla bla bla till you done :)
                $("#d"+seq[i-1]).prop("src",pPng);
            },900);
        },1000); // loop waiting time must be >= 900 (biggest timeOut for inside actions)
    }

PS: Understand that the real behavior of (setTimeOut): they all will start in same time "the three bla bla bla will start counting down in the same moment" so make a different timeout to arrange the execution.

PS 2: the example for timing loop, but for a reaction loops you can use events, promise async await ..

Solution 8 - Javascript

> setTimeout loop problem with solution

// it will print 5 times 5.
for(var i=0;i<5;i++){
setTimeout(()=> 
console.log(i), 
2000)
}               // 5 5 5 5 5

// improved using let
for(let i=0;i<5;i++){
setTimeout(()=> 
console.log('improved using let: '+i), 
2000)
}

// improved using closure
for(var i=0;i<5;i++){
((x)=>{
setTimeout(()=> 
console.log('improved using closure: '+x), 
2000)
})(i);
} 

Solution 9 - Javascript

As someone else pointed out, the Web Audio API has a better timer.

But in general, if these events happen consistently, how about you put them all on the same timer? I'm thinking about how a step sequencer works.

Practically, could it looks something like this?

var timer = 0;
var limit = 8000; // 8000 will be the point at which the loop repeats

var drumInterval = 8000;
var chordInterval = 1000;
var bassInterval = 500;

setInterval(function {
    timer += 500;

    if (timer == drumInterval) {
        // Do drum stuff
    }

    if (timer == chordInterval) {
        // Do chord stuff
    }

    if (timer == bassInterval) {
        // Do bass stuff
    }

    // Reset timer once it reaches limit
    if (timer == limit) {
        timer = 0;
    }

}, 500); // Set the timer to the smallest common denominator

Solution 10 - Javascript


function appendTaskOnStack(task, ms, loop) {
	window.nextTaskAfter = (window.nextTaskAfter || 0) + ms;

	if (!loop) {
		setTimeout(function() {
			appendTaskOnStack(task, ms, true);
		}, window.nextTaskAfter);
	} 
    else {
		if (task) 
            task.apply(Array(arguments).slice(3,));
		window.nextTaskAfter = 0;
	}
}

for (var n=0; n < 10; n++) {
    appendTaskOnStack(function(){
        console.log(n)
    }, 100);
}

Solution 11 - Javascript

function timerCycle() {
  if (stoptime == false) {
    sec = parseInt(sec);
    min = parseInt(min);
    hr = parseInt(hr);
    sec = sec + 1;
    if (sec == 60) {
      min = min + 1;
      sec = 0;
    }

    if (min == 60) {
      hr = hr + 1;
      min = 0;
      sec = 0;
    }

    if (sec < 10 || sec == 0) {
      sec = "0" + sec;
    }
    if (min < 10 || min == 0) {
      min = "0" + min;
    }
    if (hr < 10 || hr == 0) {
      hr = "0" + hr;
    }

    timer.innerHTML = hr + " : " + min + " : " + sec;

    setTimeout(timerCycle, 1000);
  }
}

function startTimer() {
  if (stoptime == true) {
    stoptime = false;
    timerCycle();
  }
}

function stopTimer() {
  if (stoptime == false) {
    stoptime = true;
  }
}
function resetTimer() {
  hr = 0;
  min = 0;
  sec = 0;
  stopTimer();
  timer.innerHTML = "00:00:00";
}

Solution 12 - Javascript

I think it's better to timeout at the end of the function.

function main(){
    var something; 
    make=function(walkNr){
         if(walkNr===0){
           // var something for this step      
           // do something
         }
         else if(walkNr===1){
           // var something for that step 
           // do something different
         }

         // ***
         // finally
         else if(walkNr===10){
           return something;
         }
         // show progress if you like
         setTimeout(funkion(){make(walkNr)},15,walkNr++);  
   }
return make(0);
}   

This three functions are necessary because vars in the second function will be overwritten with default value each time. When the program pointer reach the setTimeout one step is already calculated. Then just the screen needs a little time.

Solution 13 - Javascript

Use let instead of var in code :

for(let i=1;i<=5;i++){setTimeout(()=>{console.log(i)},1000);}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser3084366View Question on Stackoverflow
Solution 1 - JavascriptWar10ckView Answer on Stackoverflow
Solution 2 - JavascriptJoão PauloView Answer on Stackoverflow
Solution 3 - JavascriptMatt BurlandView Answer on Stackoverflow
Solution 4 - JavascriptBingView Answer on Stackoverflow
Solution 5 - JavascriptDupinder SinghView Answer on Stackoverflow
Solution 6 - JavascriptPedro LobitoView Answer on Stackoverflow
Solution 7 - JavascriptMohamed AbulnasrView Answer on Stackoverflow
Solution 8 - JavascriptakhtarvahidView Answer on Stackoverflow
Solution 9 - JavascriptMiguel ValenciaView Answer on Stackoverflow
Solution 10 - JavascriptThe Minor EmotionView Answer on Stackoverflow
Solution 11 - JavascriptdoudnView Answer on Stackoverflow
Solution 12 - JavascriptB.F.View Answer on Stackoverflow
Solution 13 - JavascriptALoK VeRMaView Answer on Stackoverflow