How could I merge or combine two audio.wav loaded from url into a new .wav and play it in the web browser and download it?
The HTML and JS code:
NOTE: To avoid cross-origin load wavs from your local or server or allow it in your .htaccess
<!DOCTYPE html>
<html>
<meta content="text/html;" http-equiv="content-type" charset="utf-8">
<head>
<script>
window.onload = function() {
//Original Wav
//https://www.wavsource.com/snds_2020-10-01_3728627494378403/video_games/pacman/pacman_intro.wav
var audioFileUrl1 = 'pacman_intro.wav'; //Atention to cross-origin** !!!
var audioFileUrl2 = 'pacman_intro.wav'; //I love PacMan intro :P
loadWav(audioFileUrl1, audioFileUrl2); //Load wavs from url
function loadWav(url1,url2){
var arrBytesWav1, arrBytesWav2;
fetch(url1)
.then(function(response) { return response.blob(); })
.then(function(blob) {
var reader = new FileReader();
reader.readAsArrayBuffer(blob);
reader.addEventListener("loadend", function() {
arrBytesWav1 = reader.result;
});
return fetch(url2); //Return the second url as a Promise.
})
//Second load
.then(function(response) { return response.blob(); })
.then(function(blob) {
var reader = new FileReader();
reader.readAsArrayBuffer(blob);
reader.addEventListener("loadend", function() {
arrBytesWav2 = reader.result;
combineWavsBuffers( arrBytesWav1, arrBytesWav2 ); //Combine original wav buffer and play
});
})
.catch( function(error) {
alert('Error wav loading: ' + error.message);
});
}
//Combine two audio .wav buffers and assign to audio control and play it.
function combineWavsBuffers(buffer1, buffer2) {
//Combine array bytes of original wavs buffers
var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
tmp.set( new Uint8Array(buffer1), 0 );
tmp.set( new Uint8Array(buffer2), buffer1.byteLength );
//Get buffer1 audio data to create the new combined wav
var audioData = getAudioData.WavHeader.readHeader(new DataView(buffer1));
console.log('Audio Data: ', audioData);
//Send combined buffer and send audio data to create the audio data of combined
var arrBytesFinal = getWavBytes( tmp, {
isFloat: false, // floating point or 16-bit integer
numChannels: audioData.channels,
sampleRate: audioData.sampleRate,
})
//Create a Blob as Base64 Raw data with audio/wav type
var myBlob = new Blob( [arrBytesFinal] , { type : 'audio/wav; codecs=MS_PCM' });
var combineBase64Wav;
var readerBlob = new FileReader();
readerBlob.addEventListener("loadend", function() {
combineBase64Wav = readerBlob.result.toString();
//Assign to audiocontrol to play the new combined wav.
var audioControl = document.getElementById('audio');
audioControl.src = combineBase64Wav;
audioControl.play();
});
readerBlob.readAsDataURL(myBlob);
console.log( "Buffer1 Size: " + buffer1.byteLength );
console.log( "Buffer2 Size: " + buffer1.byteLength );
console.log( "Combined Size: " + arrBytesFinal.byteLength );
return combineBase64Wav;
}
//Other functions //////////////////////////////////////////////////////////////
// Returns Uint8Array of WAV bytes
function getWavBytes(buffer, options) {
const type = options.isFloat ? Float32Array : Uint16Array
const numFrames = buffer.byteLength / type.BYTES_PER_ELEMENT
const headerBytes = getWavHeader(Object.assign({}, options, { numFrames }))
const wavBytes = new Uint8Array(headerBytes.length + buffer.byteLength);
// prepend header, then add pcmBytes
wavBytes.set(headerBytes, 0)
wavBytes.set(new Uint8Array(buffer), headerBytes.length)
return wavBytes
}
// adapted from https://gist.github.com/also/900023
// returns Uint8Array of WAV header bytes
function getWavHeader(options) {
const numFrames = options.numFrames
const numChannels = options.numChannels || 2
const sampleRate = options.sampleRate || 44100
const bytesPerSample = options.isFloat? 4 : 2
const format = options.isFloat? 3 : 1
const blockAlign = numChannels * bytesPerSample
const byteRate = sampleRate * blockAlign
const dataSize = numFrames * blockAlign
const buffer = new ArrayBuffer(44)
const dv = new DataView(buffer)
let p = 0
function writeString(s) {
for (let i = 0; i < s.length; i++) {
dv.setUint8(p + i, s.charCodeAt(i))
}
p += s.length
}
function writeUint32(d) {
dv.setUint32(p, d, true)
p += 4
}
function writeUint16(d) {
dv.setUint16(p, d, true)
p += 2
}
writeString('RIFF') // ChunkID
writeUint32(dataSize + 36) // ChunkSize
writeString('WAVE') // Format
writeString('fmt ') // Subchunk1ID
writeUint32(16) // Subchunk1Size
writeUint16(format) // AudioFormat
writeUint16(numChannels) // NumChannels
writeUint32(sampleRate) // SampleRate
writeUint32(byteRate) // ByteRate
writeUint16(blockAlign) // BlockAlign
writeUint16(bytesPerSample * 8) // BitsPerSample
writeString('data') // Subchunk2ID
writeUint32(dataSize) // Subchunk2Size
return new Uint8Array(buffer)
}
function getAudioData(){
function WavHeader() {
this.dataOffset = 0;
this.dataLen = 0;
this.channels = 0;
this.sampleRate = 0;
}
function fourccToInt(fourcc) {
return fourcc.charCodeAt(0) << 24 | fourcc.charCodeAt(1) << 16 | fourcc.charCodeAt(2) << 8 | fourcc.charCodeAt(3);
}
WavHeader.RIFF = fourccToInt("RIFF");
WavHeader.WAVE = fourccToInt("WAVE");
WavHeader.fmt_ = fourccToInt("fmt ");
WavHeader.data = fourccToInt("data");
WavHeader.readHeader = function (dataView) {
var w = new WavHeader();
var header = dataView.getUint32(0, false);
if (WavHeader.RIFF != header) {
return;
}
var fileLen = dataView.getUint32(4, true);
if (WavHeader.WAVE != dataView.getUint32(8, false)) {
return;
}
if (WavHeader.fmt_ != dataView.getUint32(12, false)) {
return;
}
var fmtLen = dataView.getUint32(16, true);
var pos = 16 + 4;
switch (fmtLen) {
case 16:
case 18:
w.channels = dataView.getUint16(pos + 2, true);
w.sampleRate = dataView.getUint32(pos + 4, true);
break;
default:
throw 'extended fmt chunk not implemented';
}
pos += fmtLen;
var data = WavHeader.data;
var len = 0;
while (data != header) {
header = dataView.getUint32(pos, false);
len = dataView.getUint32(pos + 4, true);
if (data == header) {
break;
}
pos += (len + 8);
}
w.dataLen = len;
w.dataOffset = pos + 8;
return w;
};
getAudioData.WavHeader = WavHeader;
}
getAudioData();
};//Window onLoad
</script>
</head>
<body>
<!--The audio control HTML element -->
<audio id="audio" src="" controls></audio>
</body>
</html>