2

I am developing a Piano application. I have a json array with note names and their duration of playing time.

var data= [{"duration":300,"value":"2C"},{"duration":400,"value":"2D"},{"duration":420,"value":"2E"},{"duration":600,"value":"2F"},{"duration":400,"value":"2G"}];

I need to play 2C note for 300 microseconds, 2D note for 400 microseconds, 2E for 420 and so on in a sequence, that is playing next note after completion of previous note.

I have audio files in .ogg format for all notes and all notes have same duration of 1018.776 microseconds.

To play the notes of above json data, I have tried setTimeout function of javascript:

$.each( data, function( key, value ) {
    setTimeout(function(){
        var audioElement = document.createElement('audio');
        audioElement.setAttribute('src', './audio/'+value.value+'.ogg');
        audioElement.play();

    }, value.duration*key); 
});

But this is not working. The main problem is on duration. When I use console.log(value.value), the result was 2C, 2D, 2E, 2G, 2F. Here the sequence of 2F and 2G is not in correct order. So, How to play these notes in correct sequence with their respective duration?

Sandesh Sharma
  • 1,064
  • 5
  • 16
  • 32
  • 2
    Just a heads up: "I have tried setTimeout function of jQuery" setTimeout is a function of native javascript and not jQuery. – Cjmarkham Oct 16 '13 at 13:53
  • Thanx Carl Markham, I have edited that, but can u please help me to solve this problem? – Sandesh Sharma Oct 16 '13 at 13:57
  • 1
    You don't get anywhere _near_ microsecond precision with JavaScript timers. You're not even guaranteed millisecond precision, since your callback will be delayed if the JS event thread is saturated. – Matt Ball Oct 16 '13 at 13:58
  • @MattBall, so what will be the solution? any libraries?? any suggestions? How to compose a music on piano? I have stored all notes, their sequences and duration in database. when user cliks on the composed music name, the music plays. Code I have tried is in above question. So, how to fix it?? Thanx in advance.. – Sandesh Sharma Oct 16 '13 at 14:03
  • @CarlMarkham - `setTimeout` is a DOM method. It comes from the browser, not JavaScript or jQuery. This becomes apparent when you try to write JavaScript outside of the browser. For example, in Windows Script Host there is no `setTimeout` function. – gilly3 Oct 16 '13 at 14:09
  • 1
    You might try using an `ended` event listener instead of `setTimeout`. http://stackoverflow.com/a/9376544/139010 – Matt Ball Oct 16 '13 at 14:12

4 Answers4

4

You need to use a recursive function instead of loop:

function playNotes(notes) {
    var i = 0;
    playNextNote();
    function playNextNote() {
        if (i < notes.length) {
            var value = notes[i];
            var audioElement = document.createElement('audio');
            audioElement.setAttribute('src', './audio/'+value.value+'.ogg');
            audioElement.play();
            i++;
            setTimeout(playNextNote, value.duration);
        }
    }
}

In this way, the next note isn't triggered to start playing until the current note has completed.

gilly3
  • 87,962
  • 25
  • 144
  • 176
0

prefer to call other value after the timeout.

function callAudio (index) {
    var
    value = this[i],
    audioElement = document.createElement('audio');

    if (!value) return;

    audioElement.setAttribute('src', './audio/'+value.value+'.ogg');
    audioElement.play();
    setTimeout(callAudio.bind(this, index++), value.duration);
};

callAudio.call(data, 0);
peernohell
  • 808
  • 4
  • 12
0

You can use a function that receives the array and index and then call itself with the next index after the delay.

var data= [{"duration":300,"value":"2C"},{"duration":400,"value":"2D"},{"duration":420,"value":"2E"},{"duration":600,"value":"2F"},{"duration":400,"value":"2G"}];

function playNote(data, index) {
    var audioElement = document.createElement('audio');
    audioElement.setAttribute('src', './audio/'+data[index].value+'.ogg');
    audioElement.play();

    if (index + 1 < data.length) {
        setTimeout(function() {
            playNote(data, index + 1);
        }, data[index].duration);
    }
}

playNote(data, 0);
Guilherme Sehn
  • 6,727
  • 18
  • 35
0

There's a couple assumptions you're making with this code - the first one I see is that the sound file loads instantly. The issue you're probably having is that the loop is not keeping track of the delay so far - so basically you're calling setTimeout({play}, 400) and immediately after setTimeout({play}, 500) so they end up overlapping after 500ms until 800 ms.

The way I would write what I think you're looking for is like this:

var audio = $.map(data, function(key,val) {return $("<audio>",{src:val.value})});
audio.each(function(indx, $ele) {
      if(indx !=0){
          audio[indx-1].on("ended", function(){$ele.get().play()});//play next sound when previous one finishes playing
      }
});
megawac
  • 10,953
  • 5
  • 40
  • 61