Repository

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));
        }
    }
}