flocking
Version:
Creative audio synthesis for the Web
279 lines (227 loc) • 8.56 kB
JavaScript
/*
* Flocking Audio File Utilities
* https://github.com/continuing-creativity/flocking
*
* Copyright 2011-2014, Colin Clark
* Dual licensed under the MIT and GPL Version 2 licenses.
*/
/*global require, ArrayBuffer, Uint8Array, File, FileReader */
/*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";
/**
* Applies the specified function in the next round of the event loop.
*/
// TODO: Replace this and the code that depends on it with a good Promise implementation.
flock.applyDeferred = function (fn, args, delay) {
if (!fn) {
return;
}
delay = typeof (delay) === "undefined" ? 0 : delay;
setTimeout(function () {
fn.apply(null, args);
}, delay);
};
/*********************
* Network utilities *
*********************/
fluid.registerNamespace("flock.net");
/**
* Loads an ArrayBuffer into memory using XMLHttpRequest.
*/
flock.net.readBufferFromUrl = function (options) {
var src = options.src,
xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (flock.net.isXHRSuccessful(xhr)) {
options.success(xhr.response,
flock.file.parseFileExtension(src));
} else {
if (!options.error) {
throw new Error(xhr.statusText);
}
options.error(xhr.statusText);
}
}
};
xhr.open(options.method || "GET", src, true);
xhr.responseType = options.responseType || "arraybuffer";
xhr.send(options.data);
};
flock.net.isXHRSuccessful = function (xhr) {
return xhr.status === 200 ||
(xhr.responseURL.indexOf("file://") === 0 && xhr.status === 0 &&
xhr.response);
};
/*****************
* File Utilties *
*****************/
fluid.registerNamespace("flock.file");
flock.file.mimeTypes = {
"audio/wav": "wav",
"audio/x-wav": "wav",
"audio/wave": "wav",
"audio/x-aiff": "aiff",
"audio/aiff": "aiff",
"sound/aiff": "aiff"
};
flock.file.typeAliases = {
"aif": "aiff",
"wave": "wav"
};
flock.file.parseFileExtension = function (fileName) {
var lastDot = fileName.lastIndexOf("."),
ext,
alias;
// TODO: Better error handling in cases where we've got unrecognized file extensions.
// i.e. we should try to read the header instead of relying on extensions.
if (lastDot < 0) {
return undefined;
}
ext = fileName.substring(lastDot + 1);
ext = ext.toLowerCase();
alias = flock.file.typeAliases[ext];
return alias || ext;
};
flock.file.parseMIMEType = function (mimeType) {
return flock.file.mimeTypes[mimeType];
};
/**
* Converts a binary string to an ArrayBuffer, suitable for use with a DataView.
*
* @param {String} s the raw string to convert to an ArrayBuffer
*
* @return {Uint8Array} the converted buffer
*/
flock.file.stringToBuffer = function (s) {
var len = s.length,
b = new ArrayBuffer(len),
v = new Uint8Array(b),
i;
for (i = 0; i < len; i++) {
v[i] = s.charCodeAt(i);
}
return v.buffer;
};
/**
* Asynchronously parses the specified data URL into an ArrayBuffer.
*/
flock.file.readBufferFromDataUrl = function (options) {
var url = options.src,
delim = url.indexOf(","),
header = url.substring(0, delim),
data = url.substring(delim + 1),
base64Idx = header.indexOf(";base64"),
isBase64 = base64Idx > -1,
mimeTypeStartIdx = url.indexOf("data:") + 5,
mimeTypeEndIdx = isBase64 ? base64Idx : delim,
mimeType = url.substring(mimeTypeStartIdx, mimeTypeEndIdx);
if (isBase64) {
data = atob(data);
}
flock.applyDeferred(function () {
var buffer = flock.file.stringToBuffer(data);
options.success(buffer, flock.file.parseMIMEType(mimeType));
});
};
/**
* Asynchronously reads the specified File into an ArrayBuffer.
*/
flock.file.readBufferFromFile = function (options) {
var reader = new FileReader();
reader.onload = function (e) {
options.success(e.target.result, flock.file.parseFileExtension(options.src.name));
};
reader.readAsArrayBuffer(options.src);
return reader;
};
fluid.registerNamespace("flock.audio");
/**
* Asychronously loads an ArrayBuffer into memory.
*
* Options:
* - src: the URL to load the array buffer from
* - method: the HTTP method to use (if applicable)
* - data: the data to be sent as part of the request (it's your job to query string-ize this if it's an HTTP request)
* - success: the success callback, which takes the ArrayBuffer response as its only argument
* - error: a callback that will be invoked if an error occurs, which takes the error message as its only argument
*/
flock.audio.loadBuffer = function (options) {
var src = options.src || options.url;
if (!src) {
return;
}
if (src instanceof ArrayBuffer) {
flock.applyDeferred(options.success, [src, options.type]);
}
var reader = flock.audio.loadBuffer.readerForSource(src);
reader(options);
};
flock.audio.loadBuffer.readerForSource = function (src) {
return (typeof (File) !== "undefined" && src instanceof File) ? flock.file.readBufferFromFile :
src.indexOf("data:") === 0 ? flock.file.readBufferFromDataUrl : flock.net.readBufferFromUrl;
};
/**
* Loads and decodes an audio file. By default, this is done asynchronously in a Web Worker.
* This decoder currently supports WAVE and AIFF file formats.
*/
flock.audio.decode = function (options) {
var success = options.success;
var wrappedSuccess = function (rawData, type) {
var strategies = flock.audio.decoderStrategies,
strategy = strategies[type] || strategies["default"];
if (options.decoder) {
strategy = typeof (options.decoder) === "string" ?
fluid.getGlobalValue(options.decoder) : options.decoder;
}
strategy({
rawData: rawData,
type: type,
success: success,
error: options.error,
sampleRate: options.sampleRate
});
};
options.success = wrappedSuccess;
flock.audio.loadBuffer(options);
};
/**
* Asynchronously decodes the specified ArrayBuffer rawData using
* the browser's Web Audio Context.
*/
flock.audio.decode.webAudio = function (o) {
// TODO: Raw reference to the Web Audio context singleton.
var ctx = flock.webAudio.audioSystem.audioContextSingleton,
success = function (audioBuffer) {
var bufDesc = flock.bufferDesc.fromAudioBuffer(audioBuffer);
o.success(bufDesc);
};
ctx.decodeAudioData(o.rawData, success, o.error);
};
flock.audio.decoderStrategies = {
"default": flock.audio.decode.webAudio
};
flock.audio.registerDecoderStrategy = function (type, strategy) {
if (!type) {
return;
}
if (typeof type === "object") {
for (var key in type) {
flock.audio.decoderStrategies[key] = type[key];
}
return;
}
if (typeof strategy === "string") {
strategy = fluid.getGlobalValue(strategy);
}
flock.audio.decoderStrategies[type] = strategy;
};
}());