Search code examples
node.jsmongodbexpressweb-audio-api

Loading Audio In Web Audio API From Node API


I'm trying to load audio from the server using Web Audio API. So far, all of my attempts to return data using a Node/Express API have failed to return anything that would play in my browser.

My current attempt is to store the contents of the file into a buffer within Mongo. When requested, the audio gets converted to an ArrayBuffer, and then encoded into Base64. The client then decodes the string and passes it into the Web Audio API Buffer Source.

I'm sure I'm doing this the hard way, but this the only thing I've come up with. I've posted my code below. If you know of a better way, I would appreciate the guidance.

Thanks

Load Data Into MongoDB

var buffer = fs.readFileSync('C4.mp3');
Sound.create({
  instrument: 'Piano',
  audio: buffer,
  note: 'C4'
});

Controller Converts Audio

var Sound = require('./sound.model');
var base64 = require('base64-arraybuffer');

// Get list of sounds
exports.index = function(req, res) {
  Sound.find(function (err, sounds) {
    if(err) { return handleError(res, err); }

    var soundsToReturn = [];
    for (var i = 0; i < sounds.length; i++) {
      soundsToReturn.push( {instrument: sounds[i].instrument,
                            audio: base64.encode(toArrayBuffer(sounds[i].audio)),
                            note: sounds[i].note});
    };

    return res.status(200).json(soundsToReturn);
  });
};

function toArrayBuffer(buffer) {
  var ab = new ArrayBuffer(buffer.length);
  var view = new Uint8Array(ab);
  for (var i = 0; i < buffer.length; ++i) {
      view[i] = buffer[i];
  }
  return ab;
 }

Client Decodes Audio

  $.ajax({
      url: url,
      dataType: 'json',
      success: function(data) {
        audioCtx.decodeAudioData(_base64ToArrayBuffer(data[0].audio), function(b){
            buffer = b;
        })
      }
    });


function _base64ToArrayBuffer(base64) {
  var binary_string =  window.atob(base64);
  var len = binary_string.length;
  var bytes = new Uint8Array( len );
  for (var i = 0; i < len; i++)        {
    var ascii = binary_string.charCodeAt(i);
    bytes[i] = ascii;
  }
  return bytes.buffer;
}

Solution

  • Since your sound data is already in Buffer format, you should be able to reduce your controller code to just:

    var Sound = require('./sound.model');
    
    // Get list of sounds
    exports.index = function(req, res) {
      Sound.find(function (err, sounds) {
        if (err)
          return handleError(res, err);
    
        var soundsToReturn = [];
        for (var i = 0; i < sounds.length; i++) {
          soundsToReturn.push( {instrument: sounds[i].instrument,
                                audio: sounds[i].audio.toString('base64'),
                                note: sounds[i].note});
        };
    
        return res.status(200).json(soundsToReturn);
      });
    };
    

    As far as the client side goes, about the only thing you might look into there is testing base64 decoding performance. Other than that I think everything else looks ok.