Resolving latency with setTimeout for Drum Machine

161 views Asked by At

I am currently working on a drum machine and I am using setTimeout to make it run. Here is the heart of the code:

      var sequencerRun = function(){    
      var currentTime = 0 
      var starting = 200;

      for(var k = 0; k < 16; k++){
        $(".instrument td .beat" + k).each(function(){
          setTimeout(blinker, currentTime,$(this));
        })
        currentTime += starting;
        }
      }

      var timerId, setInt;

      var runSeq = function(){
        setInt = setInterval(sequencerRun,3200);
      }

      $('.play').click(function(){
        stopped = false
        sequencerRun();
        runSeq();
      })

      $('.stop').click(function(){
        clearInterval(setInt);
        stopped = true;
      })

The drum machine has a matrix HTML structure built using a table. When .play is clicked a scheduling process occurs, which is encapsulated in sequencerRun. This involves a run through the columns of my matrix to determine whether there should be a drum hit or not. This is done through blinker. The scheduling creates a check on each column 1 - 16 at times 0,200,...,3200 respectively. This is what creates the effect of a sequencer. I also have a setInterval that reruns this process every 3200, which is how it takes for a run to finish.

Programmatically my code seems to make sense and my hope was that it would execute on time. The thing is that my actual app tends to stutter a lot and is stuttering even more since I deployed it. Here is a deployed version of my app.

This stuttering side effect can be best heard when you click on a full row. My question here is can anyone tell if this side effect is a result of setTimeout's timing inconsistency and if so how could I go about fixing this? Or is this related to something else that I am missing?

1

There are 1 answers

4
Jan On

I think the stuttering issue has more to do with you not preloading the instruments but rather loading them on every hit, more than it has to do with settimeout.

In any case, I think I would have solved this differently. Rather than setting a fresh timeout for each beat, create one beat timeout and put the logic in there. Something like (pseudo-code-ish, lots of stuff missing just the general idea):

var MAX_BEATS = 16; // I think you had 16 beats in your example?
var BPM = 200; 

var preloadedInstruments = [];
function preloadInstruments(){
  for(i of myInstruments) { // myInstruments would be a list of your instruments (probably just strings with a filename)
     preloadedInstruments.push(new instrument(i)); // You need an instrument class, that would preload the sound and provide a play function
  }
}
var currentbeat = 1;
function beat() {
  var activeInstruments = getActiveInstruments(currentbeat); // You could cache this also for every beat, but I think a simple loop here would be quick enough
  for(instrument of activeInstruments) {
     preloadedInstruments[instrument].play(); // play method of the instrument class called
  }
  currentbeat++;
  if (currentbeat > MAX_BEATS) { 
    currentbeat = 1;
  }
}

setInterval(beat, 60e3 / BPM);