lundi 22 décembre 2014

Web Audio API - Javascript-created WAV file incorrect length and silent

Problem

Javascript-created WAV file incorrect length and silent.


Details

I've been using the JavaScript Web Audio API to create a web application that can take multiple sound files, grab a random chunk of each, and then "mix" them together into a sampler (serially, i.e. file1 + file2 + ... + fileN), like a mashup of sorts. Sounds can be successfully loaded into my custom SoundPlayer object, and they can be played. However, when you go to actually mix them together, the resulting WAV file is the wrong length and is completely silent.


The interface allows for up to 10 sounds to be loaded and played at their own volume. You click a button to make a sampler WAV of them together, and it dynamically creates a link to download it. I also have a way to see a hex dump of the resulting file, and it shows a bunch of 00s and even some NaNs at the bottom, so obviously my algorithm is flawed, but I just can't figure out how.


When you click the "Make Sampler!" button, it runs the following function (with an array of custom SoundPlayer objects, which contain Audio Buffers, as its argument):



function createSampler(sndArr) {
var numberOfChannels = _getSoundChannelsMin(sndArr);
var sndLengthSum = (function() {
var lng = 0;
for (var i = 0; i < sndArr.length; i++) {
lng += sndArr[i].audioBuffer.length;
}
return lng;
})();

var samplerBuffer = getAudioContext().createBuffer(
numberOfChannels,
sndLengthSum,
sndArr[0].audioBuffer.sampleRate
);

for (var i = 0; i < numberOfChannels; i++) {
var channel = samplerBuffer.getChannelData(i);
channel.set(sndArr[0].audioBuffer.getChannelData(i), 0);
for (var j = 1; j < sndArr.length; j++) {
channel.set(sndArr[j].audioBuffer.getChannelData(i), sndArr[j-1].audioBuffer.length);
}
}

// encode our newly made audio blob into a wav file
var dataView = _encodeWavFile(samplerBuffer, samplerBuffer.sampleRate);
var audioBlob = new Blob([dataView], { type : 'audio/wav' });

// post new wav file to download link
_enableDownload(audioBlob);
}


The number of channels (mono/stereo/etc.) is acquired with this function:



function _getSoundChannelsMin(sndArr) {
var sndChannelsArr = [];
sndArr.forEach(function(snd) {
sndChannelsArr.push(snd.audioBuffer.numberOfChannels);
});
return Math.min.apply(Math, sndChannelsArr);
}


The WAV is encoded with this function:



function _encodeWavFile(samples, sampleRate) {
var buffer = new ArrayBuffer(44 + samples.length * 2);
var view = new DataView(buffer);

// RIFF identifier
_writeString(view, 0, 'RIFF');
// file length
view.setUint32(4, 36 + samples.length * 2, true);
// RIFF type
_writeString(view, 8, 'WAVE');
// format chunk identifier
_writeString(view, 12, 'fmt ');
// format chunk length
view.setUint32(16, 16, true);
// sample format (raw)
view.setUint16(20, 1, true);
// stereo (2 channels)
view.setUint16(22, 2, true);
// sample rate
view.setUint32(24, sampleRate, true);
// byte rate (sample rate * block align)
view.setUint32(28, sampleRate * 4, true);
// block align (channels * bytes/sample)
view.setUint16(32, 4, true);
// bits/sample
view.setUint16(34, 16, true);
// data chunk identifier
_writeString(view, 36, 'data');
// data chunk length
view.setUint32(40, samples.length * 2, true);
// write the PCM samples
//_writePCMSamples(view, 44, samples);
var offset = 44;
for (var i = 0; i < view.length; i++, offset+=2){
var s = Math.max(-1, Math.min(1, view[i]));
view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
}

return view;
}


Strings in the WAV file are handled with this:



function _writeString(view, offset, string) {
for (var i = 0; i < string.length; i++){
view.setUint8(offset + i, string.charCodeAt(i));
}
}


PCM samples are handed with this:



function _writePCMSamples(output, offset, input) {
for (var i = 0; i < input.length; i++, offset+=2){
var s = Math.max(-1, Math.min(1, input[i]));
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
}
}


Finally, the WAV file is turned into a link with this:



function _enableDownload(blob, givenFilename) {
var url = (window.URL || window.webkitURL).createObjectURL(blob);
var link = document.getElementById("linkDownloadSampler");
var d = new Date();
var defaultFilename = "sampler" + d.curDateTime() + ".wav";
link.style.display = "inline";
link.href = url;
link.download = givenFilename || defaultFilename;
}


Here's a snippet of the hex dump I get:



52 49 46 46 FFFD FFFD 02 00 57 41 56 45 66 6D 74 20 RIFF....WAVEfmt
10 00 00 00 01 00 02 00 FFFD FFFD 00 00 00 FFFD 02 00 ................
04 00 10 00 64 61 74 61 FFFD FFFD 02 00 00 00 00 00 ....data........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................


If anyone can help me see the error of my ways, I'd appreciate it. Sorry for the long post, but I wanted to post all the relevant details and code up front.


Thanks!


Aucun commentaire:

Enregistrer un commentaire