Search code examples
javascriptaudioarraybufferaudiocontext

Concatenating preloaded audio files into one using Javascript AudioContext


I have an array of preloaded base64 encoded audio files, such as the two found in the javascript object below ("hello" and "world"). I wish to concatenate both audio and add a 1 second space between them. I think the output should be an ArrayBuffer, or something that I can then use to either concatenate more audio, or export / play as desired.

NB: I do not wish to play them with a 1 second delay (i.e. I am aware of how to independently load the first file, play it, timeout for a second, and then load and play the second file. I attempt this, rather poorly, in the code for Just Play. But this is not what I am looking for since I want to construct a combined audio file with both samples separated by a one second interval).

To complete this answer, what would be the best way to generalize this concept when providing an array of multiple short audio bits (such as a sentence) which should be concatenated together? I feel that recursive calls is tempting but I am unsure of what the impact would be memory-wise?

I have tried so many things to get this to work, it's hard to say where I started and where I'm at right now... But I guess the closest post I've found is this one: Download File from Bytes in JavaScript

I am also somewhat confused as to why some snippets use UInt8Arrays while others use Float32s or Int16s... The below code "works" to listen to the code. On Chrome, it also plays "hello" for the concatenated version, but not on Firefox. Either way, it does not play "hello [1s] world" :(

  const sampleRate = 48000;
  const ctx = new (window.AudioContext || window.webkitAudioContext)( {"sampleRate": sampleRate} );

  function concatAudio( buffer1, pause, buffer2 ) {
    let silenceLength = pause*sampleRate;
    let a = new Uint8Array( buffer1.length + silenceLength + buffer2.length );
    for( let i=0; i<buffer1.length; i++ )
      a[i]=buffer1[i];
    for( let i=0; i<silenceLength; i++ )
      a[buffer1.length+i]=0;
    for( let i=0; i<buffer2.length; i++ )
      a[buffer1.length+silenceLength+i]=buffer2[i];
    return a.buffer;
  }
  
  function concatAndPlay() {
    let concatenatedBuffer = concatAudio( base64ToArrayBuffer( ogg48k.hello ), 1000, base64ToArrayBuffer( ogg48k.world ) );
    
    ctx.decodeAudioData( concatenatedBuffer ).then( function( decodedData ) {
      // following code copied from: https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode
      var source = ctx.createBufferSource();
      // set the buffer in the AudioBufferSourceNode
      source.buffer = decodedData;
      // connect the AudioBufferSourceNode to the
      // destination so we can hear the sound
      source.connect(ctx.destination);
      // start the source playing
      source.start();
    });
  }
  
  function justListen() {
    var hello = base64ToArrayBuffer( ogg48k.hello );
    console.log(hello.length); // I was hoping to use this for the sample duration, but somehow length/sampleRate is way lower than the duration...
    var d = playByteArray( hello );
    setTimeout( function(){playByteArray( base64ToArrayBuffer( ogg48k.world ) )}, 1000+800 ); // this is imperfect since I would need to retrieve the duration of the first audio, instead of manually inputing 800ms
  }

  // following code copied from: https://stackoverflow.com/questions/35038884/download-file-from-bytes-in-javascript
  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++) {
      bytes[i] = binary_string.charCodeAt(i);
    }
    return bytes;
  }

  // following code copied from: https://stackoverflow.com/questions/24151121/how-to-play-wav-audio-byte-array-via-javascript-html5
  function playByteArray( bytes ) {
    var buffer = new Uint8Array( bytes.length );
    buffer.set( new Uint8Array(bytes), 0 );
    ctx.decodeAudioData(buffer.buffer, play);
  }
  function play( audioBuffer ) {
    var source = ctx.createBufferSource();
    source.buffer = audioBuffer;
    source.connect( ctx.destination );
    source.start(0);
  }

  const ogg48k = {
    "hello": "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",
    "world": "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",
  };
<!DOCTYPE html>
<html>
<body>
<button onclick="concatAndPlay()">Concatenate and Play</button> (does not work)<br>
<button onclick="justListen()">just listen</button>("works", sort of, but not what I want)<br>
</body>
</html>


Solution

  • So I was able to adapt code from an answer which repeats a piece of audio and it seems to work. Here is the answer which helped: Web Audio API append/concatenate different AudioBuffers and play them as one song

    And below is a snippet which "solves" my problem, though I am still a bit unsure why it works with respect to the conversion to UInt8 rather than Int16 or Float32.

    If this ends up being the "best" (or "only") answer here I'll accept my own answer to help others down the line, but to my eyes this answer is still incomplete:

    1. It does not offer guidance as to how to generalize with an array of "arbitrary" length, say about 20, small audio snippets (given that the total audio duration should stay within 3 minutes or so). Especially with respect to memory management (e.g. is recursivity a good option)?
    2. I am wary of what may happen if two audio files do not have the same number of channels (i.e. one is mono and one is stereo), and/or if they do not use the same sampling rates... I will try to see what happens in these cases by trial and error...

          const sampleRate = 48000;
          const ctx = new (window.AudioContext || window.webkitAudioContext)( {"sampleRate": sampleRate} );
    
          function concatAndPlay() {
            b1 = base64ToArrayBuffer( ogg48k.hello ).buffer;
            b2 = base64ToArrayBuffer( ogg48k.world ).buffer;
            ctx.decodeAudioData(b1, x=> ctx.decodeAudioData(b2, y=> {
              var audioSource = ctx.createBufferSource();
              audioSource.connect(ctx.destination);
    
              // Concatenate the two buffers into one.
              audioSource.buffer = appendBuffer(x, 1, y);
              audioSource.connect( ctx.destination );
              audioSource.start(0);
            } ) );
            
            // following code adapted from: https://stackoverflow.com/questions/14143652/web-audio-api-append-concatenate-different-audiobuffers-and-play-them-as-one-son
            function appendBuffer(buffer1, pause, buffer2) {
              var numberOfChannels = Math.min( buffer1.numberOfChannels, buffer2.numberOfChannels );
              var tmp = ctx.createBuffer( numberOfChannels, (buffer1.length + buffer2.length + pause*buffer1.sampleRate), 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+pause*buffer1.sampleRate);
              }
              return tmp;
            }
          }
          
          function justListen() {
            var hello = base64ToArrayBuffer( ogg48k.hello );
            console.log(hello.length); // I was hoping to use this for the sample duration, but somehow length/sampleRate is way lower than the duration...
            var d = playByteArray( hello );
            setTimeout( function(){playByteArray( base64ToArrayBuffer( ogg48k.world ) )}, 1000+800 ); // this is imperfect since I would need to retrieve the duration of the first audio, instead of manually inputing 800ms
          }
    
          // following code copied from: https://stackoverflow.com/questions/35038884/download-file-from-bytes-in-javascript
          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++) {
              bytes[i] = binary_string.charCodeAt(i);
            }
            return bytes;
          }
    
          // following code copied from: https://stackoverflow.com/questions/24151121/how-to-play-wav-audio-byte-array-via-javascript-html5
          function playByteArray( bytes ) {
            var buffer = new Uint8Array( bytes.length );
            buffer.set( new Uint8Array(bytes), 0 );
            ctx.decodeAudioData(buffer.buffer, play);
          }
          function play( audioBuffer ) {
            var source = ctx.createBufferSource();
            source.buffer = audioBuffer;
            source.connect( ctx.destination );
            source.start(0);
          }
    
          const ogg48k = {
            "hello": "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",
            "world": "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",
          };
    <!DOCTYPE html>
    <html>
    <body>
    <button onclick="concatAndPlay()">Concatenate and Play</button> (now works)<br>
    <button onclick="justListen()">just listen</button>("works", sort of, though I cannot get the correct spacing programatically)<br>
    </body>
    </html>