Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
325 views
in Technique[技术] by (71.8m points)

javascript - Web Audio API append/concatenate different AudioBuffers and play them as one song

I've been playing with the Web Audio API and I'm trying to load multiple parts of a song and append them to a new ArrayBuffer and then use that ArrayBuffer for playing all the parts as one song. In the following example I am using the same song data (which is a small loop) instead of different parts of a song.

The problem is that it still plays just once instead of two times and I don't know why.

Download song

function init() {

  /**
   * Appends two ArrayBuffers into a new one.
   * 
   * @param {ArrayBuffer} buffer1 The first buffer.
   * @param {ArrayBuffer} buffer2 The second buffer.
   */
  function appendBuffer(buffer1, buffer2) {
    var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
    tmp.set( new Uint8Array(buffer1), 0);
    tmp.set( new Uint8Array(buffer2), buffer1.byteLength);
    return tmp;
  }

  /**
   * Loads a song
   * 
   * @param {String} url The url of the song.
   */
  function loadSongWebAudioAPI(url) {
    var request = new XMLHttpRequest();
    var context = new webkitAudioContext();

    request.open('GET', url, true);
    request.responseType = 'arraybuffer';

    /**
     * Appends two ArrayBuffers into a new one.
     * 
     * @param {ArrayBuffer} data The ArrayBuffer that was loaded.
     */
    function play(data) {
      // Concatenate the two buffers into one.
      var a = appendBuffer(data, data);
      var buffer = a.buffer;
      var audioSource = context.createBufferSource();
      audioSource.connect(context.destination);

      //decode the loaded data
      context.decodeAudioData(buffer, function(buf) {
        console.log('The buffer', buf);
        audioSource.buffer = buf;
        audioSource.noteOn(0);
        audioSource.playbackRate.value = 1;
      });

    };

    // When the song is loaded asynchronously try to play it.
    request.onload = function() {
      play(request.response);
    }

    request.send();
  }


  loadSongWebAudioAPI('http://localhost:8282/loop.mp3');
}

window.addEventListener('load',init,false);
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The problem in your code is that you're copying and appending another copy of the MP3 file onto the end of itself. That copy gets effectively ignored by the decoder - it's not raw buffer data, it's just random spurious junk in the file stream, following a perfectly complete MP3 file.

What you need to do is first decode the audio data into a AudioBuffer, then append the audio buffers together into a new AudioBuffer. This requires a little bit of restructuring of your code.

What you want to do is this:

var context = new webkitAudioContext();

function init() {

  /**
   * Appends two ArrayBuffers into a new one.
   * 
   * @param {ArrayBuffer} buffer1 The first buffer.
   * @param {ArrayBuffer} buffer2 The second buffer.
   */
  function appendBuffer(buffer1, buffer2) {
    var numberOfChannels = Math.min( buffer1.numberOfChannels, buffer2.numberOfChannels );
    var tmp = context.createBuffer( numberOfChannels, (buffer1.length + buffer2.length), buffer1.sampleRate );
    for (var i=0; i<numberOfChannels; i++) {
      var channel = tmp.getChannelData(i);
      channel.set( buffer1.getChannelData(i), 0);
      channel.set( buffer2.getChannelData(i), buffer1.length);
    }
    return tmp;
  }

  /**
   * Loads a song
   * 
   * @param {String} url The url of the song.
   */
  function loadSongWebAudioAPI(url) {
    var request = new XMLHttpRequest();

    request.open('GET', url, true);
    request.responseType = 'arraybuffer';

    /**
     * Appends two ArrayBuffers into a new one.
     * 
     * @param {ArrayBuffer} data The ArrayBuffer that was loaded.
     */
    function play(data) {
      //decode the loaded data
      context.decodeAudioData(data, function(buf) {
        var audioSource = context.createBufferSource();
        audioSource.connect(context.destination);

        // Concatenate the two buffers into one.
        audioSource.buffer = appendBuffer(buf, buf);
        audioSource.noteOn(0);
        audioSource.playbackRate.value = 1;
      });

    };

    // When the song is loaded asynchronously try to play it.
    request.onload = function() {
      play(request.response);
    }

    request.send();
  }


  loadSongWebAudioAPI('loop.mp3');
}

window.addEventListener('load',init,false);

There's a slight playback gap - that's because you have nearly 50ms of silence at the beginning of your sound sample, not due to looping issues.

Hope this helps!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...