flocking
Version:
Creative audio synthesis for the Web
216 lines (172 loc) • 7.71 kB
JavaScript
/*
* Flocking Audio Encoders
* https://github.com/continuing-creativity/flocking
*
* Copyright 2015, Colin Clark
* Dual licensed under the MIT and GPL Version 2 licenses.
*/
/*global require, ArrayBuffer, Uint8Array */
/*jshint white: false, newcap: true, regexp: true, browser: true,
forin: false, nomen: true, bitwise: false, maxerr: 100,
indent: 4, plusplus: false, curly: true, eqeqeq: true,
freeze: true, latedef: true, noarg: true, nonew: true, quotmark: double, undef: true,
unused: true, strict: true, asi: false, boss: false, evil: false, expr: false,
funcscope: false*/
var fluid = fluid || require("infusion"),
flock = fluid.registerNamespace("flock");
(function () {
"use strict";
fluid.registerNamespace("flock.audio.encode");
flock.audio.interleave = function (bufDesc) {
var numFrames = bufDesc.format.numSampleFrames,
chans = bufDesc.data.channels,
numChans = bufDesc.format.numChannels,
numSamps = numFrames * numChans,
out = new Float32Array(numSamps),
outIdx = 0,
frame,
chan;
for (frame = 0; frame < numFrames; frame++) {
for (chan = 0; chan < numChans; chan++) {
out[outIdx] = chans[chan][frame];
outIdx++;
}
}
return out;
};
flock.audio.encode = function (bufDesc, type, format) {
type = type || "wav";
if (type.toLowerCase() !== "wav") {
flock.fail("Flocking currently only supports encoding WAVE files.");
}
return flock.audio.encode.wav(bufDesc, format);
};
flock.audio.encode.writeFloat32Array = function (offset, dv, buf) {
for (var i = 0; i < buf.length; i++) {
dv.setFloat32(offset, buf[i], true);
offset += 4;
}
return dv;
};
flock.audio.encode.setString = function (dv, offset, str){
for (var i = 0; i < str.length; i++){
dv.setUint8(offset + i, str.charCodeAt(i));
}
};
flock.audio.encode.setBytes = function (dv, offset, bytes) {
for (var i = 0; i < bytes.length; i++) {
dv.setUint8(offset + i, bytes[i]);
}
};
flock.audio.encode.writeAsPCM = function (convertSpec, offset, dv, buf) {
if (convertSpec.setter === "setFloat32" && buf instanceof Float32Array) {
return flock.audio.encode.writeFloat32Array(offset, dv, buf);
}
for (var i = 0; i < buf.length; i++) {
var s = flock.audio.convert.floatToInt(buf[i], convertSpec);
// Write the sample to the DataView.
dv[convertSpec.setter](offset, s, true);
offset += convertSpec.width;
}
return dv;
};
flock.audio.encode.wav = function (bufDesc, format) {
format = format || flock.audio.convert.pcm.int16;
var convertSpec = flock.audio.convert.specForPCMType(format),
interleaved = flock.audio.interleave(bufDesc),
numChans = bufDesc.format.numChannels,
sampleRate = bufDesc.format.sampleRate,
isPCM = convertSpec.setter !== "setFloat32",
riffHeaderSize = 8,
formatHeaderSize = 12,
formatBodySize = 16,
formatTag = 1,
dataHeaderSize = 8,
dataBodySize = interleaved.length * convertSpec.width,
dataChunkSize = dataHeaderSize + dataBodySize,
bytesPerFrame = convertSpec.width * numChans,
bitsPerSample = 8 * convertSpec.width;
if (numChans > 2 || !isPCM) {
var factHeaderSize = 8,
factBodySize = 4,
factChunkSize = factHeaderSize + factBodySize;
formatBodySize += factChunkSize;
if (numChans > 2) {
formatBodySize += 24;
formatTag = 0xFFFE; // Extensible.
} else {
formatBodySize += 2;
formatTag = 3; // Two-channel IEEE float.
}
}
var formatChunkSize = formatHeaderSize + formatBodySize,
riffBodySize = formatChunkSize + dataChunkSize,
numBytes = riffHeaderSize + riffBodySize,
out = new ArrayBuffer(numBytes),
dv = new DataView(out);
// RIFF chunk header.
flock.audio.encode.setString(dv, 0, "RIFF"); // ckID
dv.setUint32(4, riffBodySize, true); // cksize
// Format Header
flock.audio.encode.setString(dv, 8, "WAVE"); // WAVEID
flock.audio.encode.setString(dv, 12, "fmt "); // ckID
dv.setUint32(16, formatBodySize, true); // cksize, length of the format chunk.
// Format Body
dv.setUint16(20, formatTag, true); // wFormatTag
dv.setUint16(22, numChans, true); // nChannels
dv.setUint32(24, sampleRate, true); // nSamplesPerSec
dv.setUint32(28, sampleRate * bytesPerFrame, true); // nAvgBytesPerSec (sample rate * byte width * channels)
dv.setUint16(32, bytesPerFrame, true); //nBlockAlign (channel count * bytes per sample)
dv.setUint16(34, bitsPerSample, true); // wBitsPerSample
var offset = 36;
if (formatTag === 3) {
// IEEE Float. Write out a fact chunk.
dv.setUint16(offset, 0, true); // cbSize: size of the extension
offset += 2;
offset = flock.audio.encode.wav.writeFactChunk(dv, offset, bufDesc.format.numSampleFrames);
} else if (formatTag === 0xFFFE) {
// Extensible format (i.e. > 2 channels).
// Write out additional format fields and fact chunk.
dv.setUint16(offset, 22, true); // cbSize: size of the extension
offset += 2;
// Additional format fields.
offset = flock.audio.encode.wav.additionalFormat(offset, dv, bitsPerSample, isPCM);
// Fact chunk.
offset = flock.audio.encode.wav.writeFactChunk(dv, offset, bufDesc.format.numSampleFrames);
}
flock.audio.encode.wav.writeDataChunk(convertSpec, offset, dv, interleaved, dataBodySize);
return dv.buffer;
};
flock.audio.encode.wav.subformats = {
pcm: new Uint8Array([1, 0, 0, 0, 0, 0, 16, 0, 128, 0, 0, 170, 0, 56, 155, 113]),
float: new Uint8Array([3, 0, 0, 0, 0, 0, 16, 0, 128, 0, 0, 170, 0, 56, 155, 113])
};
flock.audio.encode.wav.additionalFormat = function (offset, dv, bitsPerSample, isPCM) {
dv.setUint16(offset, bitsPerSample, true); // wValidBitsPerSample
offset += 2;
dv.setUint32(offset, 0x80000000, true); // dwChannelMask, hardcoded to SPEAKER_RESERVED
offset += 4;
// Subformat GUID.
var subformat = flock.audio.encode.wav.subformats[isPCM ? "pcm" : "float"];
flock.audio.encode.setBytes(dv, offset, subformat);
offset += 16;
return offset;
};
flock.audio.encode.wav.writeFactChunk = function (dv, offset, numSampleFrames) {
flock.audio.encode.setString(dv, offset, "fact"); // ckID
offset += 4;
dv.setUint32(offset, 4, true); //cksize
offset += 4;
dv.setUint32(offset, numSampleFrames, true); // dwSampleLength
offset += 4;
return offset;
};
flock.audio.encode.wav.writeDataChunk = function (convertSpec, offset, dv, interleaved, numSampleBytes) {
// Data chunk Header
flock.audio.encode.setString(dv, offset, "data");
offset += 4;
dv.setUint32(offset, numSampleBytes, true); // Length of the datahunk.
offset += 4;
flock.audio.encode.writeAsPCM(convertSpec, offset, dv, interleaved);
};
}());