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>
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:
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>