Home Reference Source

src/morse-pro-util-riffwave.js

/*
* RIFFWAVE adapted from RIFFWAVE.js v0.03 - Audio encoder for HTML5 <audio> elements.
* Copyleft 2011 by Pedro Ladaria <pedro.ladaria at Gmail dot com>
* Public Domain
*/

/*
* Adaptation by Stephen C. Phillips, 2013-2017.
* Email: [email protected]
* Public Domain
*/

/**
 * Utility to create RIFF WAVE file data.
 *
 * @example
 * import MorseCWWave from 'morse-pro-cw-wave';
 * import * as RiffWave from 'morse-pro-util-riffwave';
 * var morseCWWave = new MorseCWWave();
 * morseCWWave.translate("abc");
 * var wav = RiffWave.getData(morseCWWave.getSample());  // returns byte array of WAV file
  */
var u32ToArray = function(i) {
    return [i&0xFF, (i>>8)&0xFF, (i>>16)&0xFF, (i>>24)&0xFF];
};

var u16ToArray = function(i) {
    return [i&0xFF, (i>>8)&0xFF];
};

var split16bitArray = function(data) {
    var r = [];
    var j = 0;
    var len = data.length;
    for (var i=0; i<len; i++) {
        r[j++] = data[i] & 0xFF;
        r[j++] = (data[i]>>8) & 0xFF;
    }
    return r;
};

var fToU8 = function(data) {
    var r = [];
    for (var i = 0; i < data.length; i++) {
        r[i] = Math.max(Math.min(128 + Math.round(127 * data[i]), 255), 0);
    }
    return r;
}

/**
 * Convert PCM data to WAV file data.
 * @param {number[]} data - waveform data, expected to be in (and clamped to) range [-1,1]
 * @param {number} [sampleRate=8000] - the sample rate of the waveform in Hz
 * @param {number} [bitsPerSample=8] - number of bits to store each data point (8 or 16)
 * @return {number[]} - array of bytes representing the WAV file
 */
export function getData(data, sampleRate = 8000, bitsPerSample = 8) {
    data = fToU8(data);

    var header = {                            // OFFS SIZE NOTES
        chunkId      : [0x52,0x49,0x46,0x46], // 0    4    "RIFF" = 0x52494646
        chunkSize    : 0,                     // 4    4    36+SubChunk2Size = 4+(8+SubChunk1Size)+(8+SubChunk2Size)
        format       : [0x57,0x41,0x56,0x45], // 8    4    "WAVE" = 0x57415645
        subChunk1Id  : [0x66,0x6d,0x74,0x20], // 12   4    "fmt " = 0x666d7420
        subChunk1Size: 16,                    // 16   4    16 for PCM
        audioFormat  : 1,                     // 20   2    PCM = 1
        numChannels  : 1,                     // 22   2    Mono = 1, Stereo = 2...
        sampleRate   : sampleRate,            // 24   4    8000, 44100...
        byteRate     : 0,                     // 28   4    SampleRate*NumChannels*BitsPerSample/8
        blockAlign   : 0,                     // 32   2    NumChannels*BitsPerSample/8
        bitsPerSample: bitsPerSample,         // 34   2    8 bits = 8, 16 bits = 16
        subChunk2Id  : [0x64,0x61,0x74,0x61], // 36   4    "data" = 0x64617461
        subChunk2Size: 0                      // 40   4    data size = NumSamples*NumChannels*BitsPerSample/8
    };

    header.blockAlign = (header.numChannels * header.bitsPerSample) >> 3;
    header.byteRate = header.blockAlign * header.sampleRate;
    header.subChunk2Size = data.length * (header.bitsPerSample >> 3);
    header.chunkSize = 36 + header.subChunk2Size;

    return header.chunkId.concat(
        u32ToArray(header.chunkSize),
        header.format,
        header.subChunk1Id,
        u32ToArray(header.subChunk1Size),
        u16ToArray(header.audioFormat),
        u16ToArray(header.numChannels),
        u32ToArray(header.sampleRate),
        u32ToArray(header.byteRate),
        u16ToArray(header.blockAlign),
        u16ToArray(header.bitsPerSample),
        header.subChunk2Id,
        u32ToArray(header.subChunk2Size),
        (header.bitsPerSample == 16) ? split16bitArray(data) : data
    );
}

export function getMIMEType() {
    return "audio/wav";
}