js/Mobilizing/renderer/audio/BufferToWav.js
/**
* @author Matt DesLauriers for the orginal source (https://github.com/Jam3/audiobuffer-to-wav)
* @author Dominique Cunin for ES6 rewritting and insertion in Mobilizing.js
*/
export default class BufferToWav {
static makeWavFromBuffer(buffer, isFloat32) {
const numChannels = buffer.numberOfChannels;
const sampleRate = buffer.sampleRate;
const format = isFloat32 ? 3 : 1;
const bitDepth = format === 3 ? 32 : 16;
let result = null;
if (numChannels === 2) {
result = BufferToWav.interleave(buffer.getChannelData(0), buffer.getChannelData(1));
}
else {
result = buffer.getChannelData(0);
}
return BufferToWav.encodeWAV(
result,
format,
sampleRate,
numChannels,
bitDepth);
}
static downloadToWavFile(buffer, name) {
const wav = BufferToWav.makeWavFromBuffer(buffer);
console.log("wav", wav);
const blob = new Blob([new DataView(wav)], { type: 'audio/wav' });
console.log("blob", blob);
const link = document.createElement("a");
const url = URL.createObjectURL(blob);
console.log("url", url);
link.download = `${name}.wav`;
link.href = url;
link.click();
window.URL.revokeObjectURL(url);
}
static encodeWAV(samples, format, sampleRate, numChannels, bitDepth) {
const bytesPerSample = bitDepth / 8;
const blockAlign = numChannels * bytesPerSample;
const buffer = new ArrayBuffer(44 + samples.length * bytesPerSample);
const view = new DataView(buffer);
/* RIFF identifier */
BufferToWav.writeString(view, 0, 'RIFF');
/* RIFF chunk length */
view.setUint32(4, 36 + samples.length * bytesPerSample, true);
/* RIFF type */
BufferToWav.writeString(view, 8, 'WAVE');
/* format chunk identifier */
BufferToWav.writeString(view, 12, 'fmt ');
/* format chunk length */
view.setUint32(16, 16, true);
/* sample format (raw) */
view.setUint16(20, format, true);
/* channel count */
view.setUint16(22, numChannels, true);
/* sample rate */
view.setUint32(24, sampleRate, true);
/* byte rate (sample rate * block align) */
view.setUint32(28, sampleRate * blockAlign, true);
/* block align (channel count * bytes per sample) */
view.setUint16(32, blockAlign, true);
/* bits per sample */
view.setUint16(34, bitDepth, true);
/* data chunk identifier */
BufferToWav.writeString(view, 36, 'data');
/* data chunk length */
view.setUint32(40, samples.length * bytesPerSample, true)
if (format === 1) { // Raw PCM
BufferToWav.floatTo16BitPCM(view, 44, samples);
}
else {
BufferToWav.writeFloat32(view, 44, samples);
}
return buffer;
}
static interleave(inputL, inputR) {
const length = inputL.length + inputR.length;
const result = new Float32Array(length);
let index = 0;
let inputIndex = 0;
while (index < length) {
result[index++] = inputL[inputIndex];
result[index++] = inputR[inputIndex];
inputIndex++;
}
return result;
}
static writeFloat32(output, offset, input) {
for (let i = 0; i < input.length; i++, offset += 4) {
output.setFloat32(offset, input[i], true);
}
}
static floatTo16BitPCM(output, offset, input) {
for (let i = 0; i < input.length; i++, offset += 2) {
const s = Math.max(-1, Math.min(1, input[i]));
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
}
}
static writeString(view, offset, string) {
for (let i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
}
}