1

How can I assign the buffer created in decodeAudioData to soundBuffer for playback later?

Note the call to playSound() within the decodeAudioData function call plays the buffer successfully, but calls from the Play button return "value not of type ArrayBuffer" and when tested, soundBuffer is still undefined.

I'm assuming the deeply nested function has lost scope to soundBuffer in the outermost function, but I can seem to wrap soundBuffer in a closure such that it is passed in successfully.

$(function () {
  var soundBuffer, context;
  try {
      context = new webkitAudioContext();
  }
  catch (e) {     
      console.log("Error setting up webaudiocontext: " + e);
  }    

  loadSound("https://dl.dropboxusercontent.com/u/9780255/counting-coins-3.mp3",soundBuffer);

  $("#playSound").click(function () {
     playSound(soundBuffer);
  });

  function loadSound(url, buffer) {
      var rq = new XMLHttpRequest();
      rq.open("GET", url, true);
      rq.responseType = "arraybuffer";
      rq.onload = function () {
          context.decodeAudioData(rq.response,
              function (b) {
                  buffer = b;
                  console.log("buffer loaded...");
                  playSound(buffer);
              });
      };

      rq.onerror = function (e) {
          console.log("error loading audio:"+e);
      };
      rq.send();
  }   


  function playSound(s) {
     var sn= context.createBufferSource();
     sn.buffer = s;
     sn.connect(context.destination);
     sn.start(0);
  }         

});

This source is on jsfiddle: http://jsfiddle.net/karasutengu/qA5Nb/8/ It will likely only work with chrome which recognizes webkitAudioContext.

Kenji
  • 379
  • 3
  • 10

2 Answers2

1

You'll need to provide a callback to set soundBuffer in your current scope.

Change loadSound to:

loadSound("https://dl.dropboxusercontent.com/u/9780255/counting-coins-3.mp3", function(buffer) {
    soundBuffer = buffer;
});

The function def of load sound to:

function loadSound(url, callback) {

and the success function to:

function (b) {
  buffer = b;
  console.log("buffer loaded...");
  callback(buffer);
});

http://jsfiddle.net/karasutengu/qA5Nb/8

Josh B
  • 1,748
  • 16
  • 19
  • I would like not to hardcode which buffer I'm loading, so I'm trying to assign it by reference in order to reuse the routine for other sound files. – Kenji Aug 23 '13 at 06:23
  • that could be confusing, I should have probably made the calls playSound(s); – Kenji Aug 23 '13 at 06:25
  • Right, misunderstood the question. – Josh B Aug 23 '13 at 06:25
  • Updated to provide a callback that will set soundBuffer in your current scope. http://jsfiddle.net/karasutengu/qA5Nb/8/ – Josh B Aug 23 '13 at 06:33
  • very creative. so just: function (b) { console.log("buffer loaded..."); callback(b); – Kenji Aug 23 '13 at 06:58
1

It is tricky but read Is JavaScript a pass-by-reference or pass-by-value language? to understand why the soundBuffer does not retain its value.

Here is your code with a callback function which uses closures correctly to keep the value of buffer in the PlaySound function http://jsfiddle.net/qA5Nb/9/

var Player = $(function () {
   var soundBuffer, context;

    try {
        context = new webkitAudioContext();
    }
    catch (e) {     
        console.log("Error setting up webaudiocontext: " + e);
    }    


    var registerPlayButton = function(buffer){    
       $("#playSound").click(function () {
           playSound(buffer);
       });
    }


   loadSound("https://dl.dropboxusercontent.com/u/9780255/counting-coins-3.mp3",soundBuffer, registerPlayButton);

  function loadSound(url, buffer, callbackFn) {
        var rq = new XMLHttpRequest();
        rq.open("GET", url, true);
        rq.responseType = "arraybuffer";
        rq.onload = function () {
            context.decodeAudioData(rq.response,
                function (b) {
                    buffer = b;
                    console.log("buffer loaded...");
                    playSound(buffer);
                    callbackFn(buffer);
                });
        };

        rq.onerror = function (e) {
            console.log("error loading audio:"+e);
        };
        rq.send();
    }   


   function playSound(soundBuffer) {
       var sn= context.createBufferSource();
       sn.buffer = soundBuffer;
       sn.connect(context.destination);
       sn.start(0);
    }     



});

ADDITION:

A brief explanation is that soundBuffer is a reference type and is passed into the loadSound by value (so the value of the reference, which is an uninitialized pointer).

If your soundBuffer were initialised as an object like var soundBuffer ={b:null};, and were passed to the loadSound, and you were to assign the buffer to the b property of the object {b}; your code would work.

You would still be passing soundBuffer by value, the value of this pointer, which is a pointer to the same memory location where {buffer:null} is stored, to which the outer soundBuffer is also pointing to.

So, if you were to assign within the loadSound, a buffer to the soundBuffer like

        context.decodeAudioData(rq.response,
            function (b) {
                buffer.b = b;
                console.log("buffer loaded...");
                playSound(buffer.b);

            });

you would in effect assign the decoded buffer to the same memory location that both the outer soundBuffer and inner buffer variables point to and you playSound would work.

Community
  • 1
  • 1
Giuseppe Romagnuolo
  • 3,362
  • 2
  • 30
  • 38