summaryrefslogtreecommitdiff
path: root/public/js/vendor
diff options
context:
space:
mode:
Diffstat (limited to 'public/js/vendor')
-rw-r--r--public/js/vendor/Audiolet.js7281
-rw-r--r--public/js/vendor/Audiolet.min.js252
2 files changed, 7533 insertions, 0 deletions
diff --git a/public/js/vendor/Audiolet.js b/public/js/vendor/Audiolet.js
new file mode 100644
index 0000000..b7be12e
--- /dev/null
+++ b/public/js/vendor/Audiolet.js
@@ -0,0 +1,7281 @@
+/**
+ * A variable size multi-channel audio buffer.
+ *
+ * @constructor
+ * @param {Number} numberOfChannels The initial number of channels.
+ * @param {Number} length The length in samples of each channel.
+ */
+var AudioletBuffer = function(numberOfChannels, length) {
+ this.numberOfChannels = numberOfChannels;
+ this.length = length;
+
+ this.channels = [];
+ for (var i = 0; i < this.numberOfChannels; i++) {
+ this.channels.push(new Float32Array(length));
+ }
+
+ this.unslicedChannels = [];
+ for (var i = 0; i < this.numberOfChannels; i++) {
+ this.unslicedChannels.push(this.channels[i]);
+ }
+
+ this.isEmpty = false;
+ this.channelOffset = 0;
+};
+
+/**
+ * Get a single channel of data
+ *
+ * @param {Number} channel The index of the channel.
+ * @return {Float32Array} The requested channel.
+ */
+AudioletBuffer.prototype.getChannelData = function(channel) {
+ return (this.channels[channel]);
+};
+
+/**
+ * Set the data in the buffer by copying data from a second buffer
+ *
+ * @param {AudioletBuffer} buffer The buffer to copy data from.
+ */
+AudioletBuffer.prototype.set = function(buffer) {
+ var numberOfChannels = buffer.numberOfChannels;
+ for (var i = 0; i < numberOfChannels; i++) {
+ this.channels[i].set(buffer.getChannelData(i));
+ }
+};
+
+/**
+ * Set the data in a section of the buffer by copying data from a second buffer
+ *
+ * @param {AudioletBuffer} buffer The buffer to copy data from.
+ * @param {Number} length The number of samples to copy.
+ * @param {Number} [inputOffset=0] An offset to read data from.
+ * @param {Number} [outputOffset=0] An offset to write data to.
+ */
+AudioletBuffer.prototype.setSection = function(buffer, length, inputOffset,
+ outputOffset) {
+ inputOffset = inputOffset || 0;
+ outputOffset = outputOffset || 0;
+ var numberOfChannels = buffer.numberOfChannels;
+ for (var i = 0; i < numberOfChannels; i++) {
+ // Begin subarray-of-subarray fix
+ inputOffset += buffer.channelOffset;
+ outputOffset += this.channelOffset;
+ var channel1 = this.unslicedChannels[i].subarray(outputOffset,
+ outputOffset +
+ length);
+ var channel2 = buffer.unslicedChannels[i].subarray(inputOffset,
+ inputOffset +
+ length);
+ // End subarray-of-subarray fix
+ // Uncomment the following lines when subarray-of-subarray is fixed
+ /*!
+ var channel1 = this.getChannelData(i).subarray(outputOffset,
+ outputOffset +
+ length);
+ var channel2 = buffer.getChannelData(i).subarray(inputOffset,
+ inputOffset +
+ length);
+ */
+ channel1.set(channel2);
+ }
+};
+
+/**
+ * Add the data from a second buffer to the data in this buffer
+ *
+ * @param {AudioletBuffer} buffer The buffer to add data from.
+ */
+AudioletBuffer.prototype.add = function(buffer) {
+ var length = this.length;
+ var numberOfChannels = buffer.numberOfChannels;
+ for (var i = 0; i < numberOfChannels; i++) {
+ var channel1 = this.getChannelData(i);
+ var channel2 = buffer.getChannelData(i);
+ for (var j = 0; j < length; j++) {
+ channel1[j] += channel2[j];
+ }
+ }
+};
+
+/**
+ * Add the data from a section of a second buffer to the data in this buffer
+ *
+ * @param {AudioletBuffer} buffer The buffer to add data from.
+ * @param {Number} length The number of samples to add.
+ * @param {Number} [inputOffset=0] An offset to read data from.
+ * @param {Number} [outputOffset=0] An offset to write data to.
+ */
+AudioletBuffer.prototype.addSection = function(buffer, length, inputOffset,
+ outputOffset) {
+ inputOffset = inputOffset || 0;
+ outputOffset = outputOffset || 0;
+ var numberOfChannels = buffer.numberOfChannels;
+ for (var i = 0; i < numberOfChannels; i++) {
+ var channel1 = this.getChannelData(i);
+ var channel2 = buffer.getChannelData(i);
+ for (var j = 0; j < length; j++) {
+ channel1[j + outputOffset] += channel2[j + inputOffset];
+ }
+ }
+};
+
+/**
+ * Resize the buffer. This operation can optionally be lazy, which is
+ * generally faster but doesn't necessarily result in an empty buffer.
+ *
+ * @param {Number} numberOfChannel The new number of channels.
+ * @param {Number} length The new length of each channel.
+ * @param {Boolean} [lazy=false] If true a resized buffer may not be empty.
+ * @param {Number} [offset=0] An offset to resize from.
+ */
+AudioletBuffer.prototype.resize = function(numberOfChannels, length, lazy,
+ offset) {
+ offset = offset || 0;
+ // Local variables
+ var channels = this.channels;
+ var unslicedChannels = this.unslicedChannels;
+
+ var oldLength = this.length;
+ var channelOffset = this.channelOffset + offset;
+
+ for (var i = 0; i < numberOfChannels; i++) {
+ // Get the current channels
+ var channel = channels[i];
+ var unslicedChannel = unslicedChannels[i];
+
+ if (length > oldLength) {
+ // We are increasing the size of the buffer
+ var oldChannel = channel;
+
+ if (!lazy ||
+ !unslicedChannel ||
+ unslicedChannel.length < length) {
+ // Unsliced channel is not empty when it needs to be,
+ // does not exist, or is not large enough, so needs to be
+ // (re)created
+ unslicedChannel = new Float32Array(length);
+ }
+
+ channel = unslicedChannel.subarray(0, length);
+
+ if (!lazy && oldChannel) {
+ channel.set(oldChannel, offset);
+ }
+
+ channelOffset = 0;
+ }
+ else {
+ // We are decreasing the size of the buffer
+ if (!unslicedChannel) {
+ // Unsliced channel does not exist
+ // We can assume that we always have at least one unsliced
+ // channel, so we can copy its length
+ var unslicedLength = unslicedChannels[0].length;
+ unslicedChannel = new Float32Array(unslicedLength);
+ }
+ // Begin subarray-of-subarray fix
+ offset = channelOffset;
+ channel = unslicedChannel.subarray(offset, offset + length);
+ // End subarray-of-subarray fix
+ // Uncomment the following lines when subarray-of-subarray is
+ // fixed.
+ // TODO: Write version where subarray-of-subarray is used
+ }
+ channels[i] = channel;
+ unslicedChannels[i] = unslicedChannel;
+ }
+
+ this.channels = channels.slice(0, numberOfChannels);
+ this.unslicedChannels = unslicedChannels.slice(0, numberOfChannels);
+ this.length = length;
+ this.numberOfChannels = numberOfChannels;
+ this.channelOffset = channelOffset;
+};
+
+/**
+ * Append the data from a second buffer to the end of the buffer
+ *
+ * @param {AudioletBuffer} buffer The buffer to append to this buffer.
+ */
+AudioletBuffer.prototype.push = function(buffer) {
+ var bufferLength = buffer.length;
+ this.resize(this.numberOfChannels, this.length + bufferLength);
+ this.setSection(buffer, bufferLength, 0, this.length - bufferLength);
+};
+
+/**
+ * Remove data from the end of the buffer, placing it in a second buffer.
+ *
+ * @param {AudioletBuffer} buffer The buffer to move data into.
+ */
+AudioletBuffer.prototype.pop = function(buffer) {
+ var bufferLength = buffer.length;
+ var offset = this.length - bufferLength;
+ buffer.setSection(this, bufferLength, offset, 0);
+ this.resize(this.numberOfChannels, offset);
+};
+
+/**
+ * Prepend data from a second buffer to the beginning of the buffer.
+ *
+ * @param {AudioletBuffer} buffer The buffer to prepend to this buffer.
+ */
+AudioletBuffer.prototype.unshift = function(buffer) {
+ var bufferLength = buffer.length;
+ this.resize(this.numberOfChannels, this.length + bufferLength, false,
+ bufferLength);
+ this.setSection(buffer, bufferLength, 0, 0);
+};
+
+/**
+ * Remove data from the beginning of the buffer, placing it in a second buffer.
+ *
+ * @param {AudioletBuffer} buffer The buffer to move data into.
+ */
+AudioletBuffer.prototype.shift = function(buffer) {
+ var bufferLength = buffer.length;
+ buffer.setSection(this, bufferLength, 0, 0);
+ this.resize(this.numberOfChannels, this.length - bufferLength,
+ false, bufferLength);
+};
+
+/**
+ * Make all values in the buffer 0
+ */
+AudioletBuffer.prototype.zero = function() {
+ var numberOfChannels = this.numberOfChannels;
+ for (var i = 0; i < numberOfChannels; i++) {
+ var channel = this.getChannelData(i);
+ var length = this.length;
+ for (var j = 0; j < length; j++) {
+ channel[j] = 0;
+ }
+ }
+};
+
+/**
+ * Copy the buffer into a single Float32Array, with each channel appended to
+ * the end of the previous one.
+ *
+ * @return {Float32Array} The combined array of data.
+ */
+AudioletBuffer.prototype.combined = function() {
+ var channels = this.channels;
+ var numberOfChannels = this.numberOfChannels;
+ var length = this.length;
+ var combined = new Float32Array(numberOfChannels * length);
+ for (var i = 0; i < numberOfChannels; i++) {
+ combined.set(channels[i], i * length);
+ }
+ return combined;
+};
+
+/**
+ * Copy the buffer into a single Float32Array, with the channels interleaved.
+ *
+ * @return {Float32Array} The interleaved array of data.
+ */
+AudioletBuffer.prototype.interleaved = function() {
+ var channels = this.channels;
+ var numberOfChannels = this.numberOfChannels;
+ var length = this.length;
+ var interleaved = new Float32Array(numberOfChannels * length);
+ for (var i = 0; i < length; i++) {
+ for (var j = 0; j < numberOfChannels; j++) {
+ interleaved[numberOfChannels * i + j] = channels[j][i];
+ }
+ }
+ return interleaved;
+};
+
+/**
+ * Return a new copy of the buffer.
+ *
+ * @return {AudioletBuffer} The copy of the buffer.
+ */
+AudioletBuffer.prototype.copy = function() {
+ var buffer = new AudioletBuffer(this.numberOfChannels, this.length);
+ buffer.set(this);
+ return buffer;
+};
+
+/**
+ * Load a .wav or .aiff file into the buffer using audiofile.js
+ *
+ * @param {String} path The path to the file.
+ * @param {Boolean} [async=true] Whether to load the file asynchronously.
+ * @param {Function} [callback] Function called if the file loaded sucessfully.
+ */
+AudioletBuffer.prototype.load = function(path, async, callback) {
+ var request = new AudioFileRequest(path, async);
+ request.onSuccess = function(decoded) {
+ this.length = decoded.length;
+ this.numberOfChannels = decoded.channels.length;
+ this.unslicedChannels = decoded.channels;
+ this.channels = decoded.channels;
+ this.channelOffset = 0;
+ if (callback) {
+ callback();
+ }
+ }.bind(this);
+
+ request.onFailure = function() {
+ console.error('Could not load', path);
+ }.bind(this);
+
+ request.send();
+};
+
+/**
+ * A container for collections of connected AudioletNodes. Groups make it
+ * possible to create multiple copies of predefined networks of nodes,
+ * without having to manually create and connect up each individual node.
+ *
+ * From the outside groups look and behave exactly the same as nodes.
+ * Internally you can connect nodes directly to the group's inputs and
+ * outputs, allowing connection to nodes outside of the group.
+ *
+ * @constructor
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} numberOfInputs The number of inputs.
+ * @param {Number} numberOfOutputs The number of outputs.
+ */
+var AudioletGroup = function(audiolet, numberOfInputs, numberOfOutputs) {
+ this.audiolet = audiolet;
+
+ this.inputs = [];
+ for (var i = 0; i < numberOfInputs; i++) {
+ this.inputs.push(new PassThroughNode(this.audiolet, 1, 1));
+ }
+
+ this.outputs = [];
+ for (var i = 0; i < numberOfOutputs; i++) {
+ this.outputs.push(new PassThroughNode(this.audiolet, 1, 1));
+ }
+};
+
+/**
+ * Connect the group to another node or group
+ *
+ * @param {AudioletNode|AudioletGroup} node The node to connect to.
+ * @param {Number} [output=0] The index of the output to connect from.
+ * @param {Number} [input=0] The index of the input to connect to.
+ */
+AudioletGroup.prototype.connect = function(node, output, input) {
+ this.outputs[output || 0].connect(node, 0, input);
+};
+
+/**
+ * Disconnect the group from another node or group
+ *
+ * @param {AudioletNode|AudioletGroup} node The node to disconnect from.
+ * @param {Number} [output=0] The index of the output to disconnect.
+ * @param {Number} [input=0] The index of the input to disconnect.
+ */
+AudioletGroup.prototype.disconnect = function(node, output, input) {
+ this.outputs[output || 0].disconnect(node, 0, input);
+};
+
+/**
+ * Remove the group completely from the processing graph, disconnecting all
+ * of its inputs and outputs
+ */
+AudioletGroup.prototype.remove = function() {
+ var numberOfInputs = this.inputs.length;
+ for (var i = 0; i < numberOfInputs; i++) {
+ this.inputs[i].remove();
+ }
+
+ var numberOfOutputs = this.outputs.length;
+ for (var i = 0; i < numberOfOutputs; i++) {
+ this.outputs[i].remove();
+ }
+};
+
+/*!
+ * @depends AudioletGroup.js
+ */
+
+/**
+ * Group containing all of the components for the Audiolet output chain. The
+ * chain consists of:
+ *
+ * Input => Scheduler => UpMixer => Output
+ *
+ * **Inputs**
+ *
+ * - Audio
+ *
+ * @constructor
+ * @extends AudioletGroup
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [sampleRate=44100] The sample rate to run at.
+ * @param {Number} [numberOfChannels=2] The number of output channels.
+ * @param {Number} [bufferSize=8192] A fixed buffer size to use.
+ */
+var AudioletDestination = function(audiolet, sampleRate, numberOfChannels,
+ bufferSize) {
+ AudioletGroup.call(this, audiolet, 1, 0);
+
+ this.device = new AudioletDevice(audiolet, sampleRate,
+ numberOfChannels, bufferSize);
+ audiolet.device = this.device; // Shortcut
+ this.scheduler = new Scheduler(audiolet);
+ audiolet.scheduler = this.scheduler; // Shortcut
+
+ this.upMixer = new UpMixer(audiolet, this.device.numberOfChannels);
+
+ this.inputs[0].connect(this.scheduler);
+ this.scheduler.connect(this.upMixer);
+ this.upMixer.connect(this.device);
+};
+extend(AudioletDestination, AudioletGroup);
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+AudioletDestination.prototype.toString = function() {
+ return 'Destination';
+};
+
+/**
+ * The basic building block of Audiolet applications. Nodes are connected
+ * together to create a processing graph which governs the flow of audio data.
+ * AudioletNodes can contain any number of inputs and outputs which send and
+ * receive one or more channels of audio data. Audio data is created and
+ * processed using the generate function, which is called whenever new data is
+ * needed.
+ *
+ * @constructor
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} numberOfInputs The number of inputs.
+ * @param {Number} numberOfOutputs The number of outputs.
+ * @param {Function} [generate] A replacement for the generate function.
+ */
+var AudioletNode = function(audiolet, numberOfInputs, numberOfOutputs,
+ generate) {
+ this.audiolet = audiolet;
+
+ this.inputs = [];
+ for (var i = 0; i < numberOfInputs; i++) {
+ this.inputs.push(new AudioletInput(this, i));
+ }
+
+ this.outputs = [];
+ for (var i = 0; i < numberOfOutputs; i++) {
+ this.outputs.push(new AudioletOutput(this, i));
+ }
+
+ if (generate) {
+ this.generate = generate;
+ }
+};
+
+/**
+ * Connect the node to another node or group.
+ *
+ * @param {AudioletNode|AudioletGroup} node The node to connect to.
+ * @param {Number} [output=0] The index of the output to connect from.
+ * @param {Number} [input=0] The index of the input to connect to.
+ */
+AudioletNode.prototype.connect = function(node, output, input) {
+ if (node instanceof AudioletGroup) {
+ // Connect to the pass-through node rather than the group
+ node = node.inputs[input || 0];
+ input = 0;
+ }
+ var outputPin = this.outputs[output || 0];
+ var inputPin = node.inputs[input || 0];
+ outputPin.connect(inputPin);
+ inputPin.connect(outputPin);
+
+ this.audiolet.device.needTraverse = true;
+};
+
+/**
+ * Disconnect the node from another node or group
+ *
+ * @param {AudioletNode|AudioletGroup} node The node to disconnect from.
+ * @param {Number} [output=0] The index of the output to disconnect.
+ * @param {Number} [input=0] The index of the input to disconnect.
+ */
+AudioletNode.prototype.disconnect = function(node, output, input) {
+ if (node instanceof AudioletGroup) {
+ node = node.inputs[input || 0];
+ input = 0;
+ }
+
+ var outputPin = this.outputs[output || 0];
+ var inputPin = node.inputs[input || 0];
+ inputPin.disconnect(outputPin);
+ outputPin.disconnect(inputPin);
+
+ this.audiolet.device.needTraverse = true;
+};
+
+/**
+ * Force an output to contain a fixed number of channels.
+ *
+ * @param {Number} output The index of the output.
+ * @param {Number} numberOfChannels The number of channels.
+ */
+AudioletNode.prototype.setNumberOfOutputChannels = function(output,
+ numberOfChannels) {
+ this.outputs[output].numberOfChannels = numberOfChannels;
+};
+
+/**
+ * Link an output to an input, forcing the output to always contain the
+ * same number of channels as the input.
+ *
+ * @param {Number} output The index of the output.
+ * @param {Number} input The index of the input.
+ */
+AudioletNode.prototype.linkNumberOfOutputChannels = function(output, input) {
+ this.outputs[output].linkNumberOfChannels(this.inputs[input]);
+};
+
+/**
+ * Process samples a from each channel. This function should not be called
+ * manually by users, who should instead rely on automatic ticking from
+ * connections to the AudioletDevice.
+ */
+AudioletNode.prototype.tick = function() {
+ this.createInputSamples();
+ this.createOutputSamples();
+
+ this.generate();
+};
+
+/**
+ * Traverse the audio graph, adding this and any parent nodes to the nodes
+ * array.
+ *
+ * @param {AudioletNode[]} nodes Array to add nodes to.
+ */
+AudioletNode.prototype.traverse = function(nodes) {
+ if (nodes.indexOf(this) == -1) {
+ nodes.push(this);
+ nodes = this.traverseParents(nodes);
+ }
+ return nodes;
+};
+
+/**
+ * Call the traverse function on nodes which are connected to the inputs.
+ */
+AudioletNode.prototype.traverseParents = function(nodes) {
+ var numberOfInputs = this.inputs.length;
+ for (var i = 0; i < numberOfInputs; i++) {
+ var input = this.inputs[i];
+ var numberOfStreams = input.connectedFrom.length;
+ for (var j = 0; j < numberOfStreams; j++) {
+ nodes = input.connectedFrom[j].node.traverse(nodes);
+ }
+ }
+ return nodes;
+};
+
+/**
+ * Process a sample for each channel, reading from the inputs and putting new
+ * values into the outputs. Override me!
+ */
+AudioletNode.prototype.generate = function() {
+};
+
+/**
+ * Create the input samples by grabbing data from the outputs of connected
+ * nodes and summing it. If no nodes are connected to an input, then
+ * give an empty array
+ */
+AudioletNode.prototype.createInputSamples = function() {
+ var numberOfInputs = this.inputs.length;
+ for (var i = 0; i < numberOfInputs; i++) {
+ var input = this.inputs[i];
+
+ var numberOfInputChannels = 0;
+
+ for (var j = 0; j < input.connectedFrom.length; j++) {
+ var output = input.connectedFrom[j];
+ for (var k = 0; k < output.samples.length; k++) {
+ var sample = output.samples[k];
+ if (k < numberOfInputChannels) {
+ input.samples[k] += sample;
+ }
+ else {
+ input.samples[k] = sample;
+ numberOfInputChannels += 1;
+ }
+ }
+ }
+
+ if (input.samples.length > numberOfInputChannels) {
+ input.samples = input.samples.slice(0, numberOfInputChannels);
+ }
+ }
+};
+
+
+/**
+* Create output samples for each channel.
+*/
+AudioletNode.prototype.createOutputSamples = function() {
+ var numberOfOutputs = this.outputs.length;
+ for (var i = 0; i < numberOfOutputs; i++) {
+ var output = this.outputs[i];
+ var numberOfChannels = output.getNumberOfChannels();
+ if (output.samples.length == numberOfChannels) {
+ continue;
+ }
+ else if (output.samples.length > numberOfChannels) {
+ output.samples = output.samples.slice(0, numberOfChannels);
+ continue;
+ }
+
+ for (var j = output.samples.length; j < numberOfChannels; j++) {
+ output.samples[j] = 0;
+ }
+ }
+};
+
+/**
+ * Remove the node completely from the processing graph, disconnecting all
+ * of its inputs and outputs.
+ */
+AudioletNode.prototype.remove = function() {
+ // Disconnect inputs
+ var numberOfInputs = this.inputs.length;
+ for (var i = 0; i < numberOfInputs; i++) {
+ var input = this.inputs[i];
+ var numberOfStreams = input.connectedFrom.length;
+ for (var j = 0; j < numberOfStreams; j++) {
+ var outputPin = input.connectedFrom[j];
+ var output = outputPin.node;
+ output.disconnect(this, outputPin.index, i);
+ }
+ }
+
+ // Disconnect outputs
+ var numberOfOutputs = this.outputs.length;
+ for (var i = 0; i < numberOfOutputs; i++) {
+ var output = this.outputs[i];
+ var numberOfStreams = output.connectedTo.length;
+ for (var j = 0; j < numberOfStreams; j++) {
+ var inputPin = output.connectedTo[j];
+ var input = inputPin.node;
+ this.disconnect(input, i, inputPin.index);
+ }
+ }
+};
+
+
+/*!
+ * @depends AudioletNode.js
+ */
+
+/**
+ * Audio output device. Uses sink.js to output to a range of APIs.
+ *
+ * @constructor
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [sampleRate=44100] The sample rate to run at.
+ * @param {Number} [numberOfChannels=2] The number of output channels.
+ * @param {Number} [bufferSize=8192] A fixed buffer size to use.
+ */
+function AudioletDevice(audiolet, sampleRate, numberOfChannels, bufferSize) {
+ AudioletNode.call(this, audiolet, 1, 0);
+
+ this.sink = Sink(this.tick.bind(this), numberOfChannels, bufferSize,
+ sampleRate);
+
+ // Re-read the actual values from the sink. Sample rate especially is
+ // liable to change depending on what the soundcard allows.
+ this.sampleRate = this.sink.sampleRate;
+ this.numberOfChannels = this.sink.channelCount;
+ this.bufferSize = this.sink.preBufferSize;
+
+ this.writePosition = 0;
+ this.buffer = null;
+ this.paused = false;
+
+ this.needTraverse = true;
+ this.nodes = [];
+}
+extend(AudioletDevice, AudioletNode);
+
+/**
+* Overridden tick function. Pulls data from the input and writes it to the
+* device.
+*
+* @param {Float32Array} buffer Buffer to write data to.
+* @param {Number} numberOfChannels Number of channels in the buffer.
+*/
+AudioletDevice.prototype.tick = function(buffer, numberOfChannels) {
+ if (!this.paused) {
+ var input = this.inputs[0];
+
+ var samplesNeeded = buffer.length / numberOfChannels;
+ for (var i = 0; i < samplesNeeded; i++) {
+ if (this.needTraverse) {
+ this.nodes = this.traverse([]);
+ this.needTraverse = false;
+ }
+
+ // Tick in reverse order up to, but not including this node
+ for (var j = this.nodes.length - 1; j > 0; j--) {
+ this.nodes[j].tick();
+ }
+ // Cut down tick to just sum the input samples
+ this.createInputSamples();
+
+ for (var j = 0; j < numberOfChannels; j++) {
+ buffer[i * numberOfChannels + j] = input.samples[j];
+ }
+
+ this.writePosition += 1;
+ }
+ }
+};
+
+/**
+ * Get the current output position
+ *
+ * @return {Number} Output position in samples.
+ */
+AudioletDevice.prototype.getPlaybackTime = function() {
+ return this.sink.getPlaybackTime();
+};
+
+/**
+ * Get the current write position
+ *
+ * @return {Number} Write position in samples.
+ */
+AudioletDevice.prototype.getWriteTime = function() {
+ return this.writePosition;
+};
+
+/**
+ * Pause the output stream, and stop everything from ticking. The playback
+ * time will continue to increase, but the write time will be paused.
+ */
+AudioletDevice.prototype.pause = function() {
+ this.paused = true;
+};
+
+/**
+ * Restart the output stream.
+ */
+AudioletDevice.prototype.play = function() {
+ this.paused = false;
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+AudioletDevice.prototype.toString = function() {
+ return 'Audio Output Device';
+};
+
+/**
+ * Class representing a single input of an AudioletNode
+ *
+ * @constructor
+ * @param {AudioletNode} node The node which the input belongs to.
+ * @param {Number} index The index of the input.
+ */
+var AudioletInput = function(node, index) {
+ this.node = node;
+ this.index = index;
+ this.connectedFrom = [];
+ // Minimum sized buffer, which we can resize from accordingly
+ this.samples = [];
+};
+
+/**
+ * Connect the input to an output
+ *
+ * @param {AudioletOutput} output The output to connect to.
+ */
+AudioletInput.prototype.connect = function(output) {
+ this.connectedFrom.push(output);
+};
+
+/**
+ * Disconnect the input from an output
+ *
+ * @param {AudioletOutput} output The output to disconnect from.
+ */
+AudioletInput.prototype.disconnect = function(output) {
+ var numberOfStreams = this.connectedFrom.length;
+ for (var i = 0; i < numberOfStreams; i++) {
+ if (output == this.connectedFrom[i]) {
+ this.connectedFrom.splice(i, 1);
+ break;
+ }
+ }
+ if (this.connectedFrom.length == 0) {
+ this.samples = [];
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+AudioletInput.prototype.toString = function() {
+ return this.node.toString() + 'Input #' + this.index;
+};
+
+
+/**
+ * The base audiolet object. Contains an output node which pulls data from
+ * connected nodes.
+ *
+ * @constructor
+ * @param {Number} [sampleRate=44100] The sample rate to run at.
+ * @param {Number} [numberOfChannels=2] The number of output channels.
+ * @param {Number} [bufferSize] Block size. If undefined uses a sane default.
+ */
+var Audiolet = function(sampleRate, numberOfChannels, bufferSize) {
+ this.output = new AudioletDestination(this, sampleRate,
+ numberOfChannels, bufferSize);
+};
+
+
+/**
+ * Class representing a single output of an AudioletNode
+ *
+ * @constructor
+ * @param {AudioletNode} node The node which the input belongs to.
+ * @param {Number} index The index of the input.
+ */
+var AudioletOutput = function(node, index) {
+ this.node = node;
+ this.index = index;
+ this.connectedTo = [];
+ this.samples = [];
+
+ this.linkedInput = null;
+ this.numberOfChannels = 1;
+};
+
+/**
+ * Connect the output to an input
+ *
+ * @param {AudioletInput} input The input to connect to.
+ */
+AudioletOutput.prototype.connect = function(input) {
+ this.connectedTo.push(input);
+};
+
+/**
+ * Disconnect the output from an input
+ *
+ * @param {AudioletInput} input The input to disconnect from.
+ */
+AudioletOutput.prototype.disconnect = function(input) {
+ var numberOfStreams = this.connectedTo.length;
+ for (var i = 0; i < numberOfStreams; i++) {
+ if (input == this.connectedTo[i]) {
+ this.connectedTo.splice(i, 1);
+ break;
+ }
+ }
+};
+
+/**
+ * Link the output to an input, forcing the output to always contain the
+ * same number of channels as the input.
+ *
+ * @param {AudioletInput} input The input to link to.
+ */
+AudioletOutput.prototype.linkNumberOfChannels = function(input) {
+ this.linkedInput = input;
+};
+
+/**
+ * Unlink the output from its linked input
+ */
+AudioletOutput.prototype.unlinkNumberOfChannels = function() {
+ this.linkedInput = null;
+};
+
+/**
+ * Get the number of output channels, taking the value from the input if the
+ * output is linked.
+ *
+ * @return {Number} The number of output channels.
+ */
+AudioletOutput.prototype.getNumberOfChannels = function() {
+ if (this.linkedInput && this.linkedInput.connectedFrom.length) {
+ return (this.linkedInput.samples.length);
+ }
+ return (this.numberOfChannels);
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+AudioletOutput.prototype.toString = function() {
+ return this.node.toString() + 'Output #' + this.index + ' - ';
+};
+
+
+/**
+ * AudioletParameters are used to provide either constant or varying values to
+ * be used inside AudioletNodes. AudioletParameters hold a static value, and
+ * can also be linked to an AudioletInput. If a node or group is connected to
+ * the linked input, then the dynamic value taken from the node should be
+ * prioritised over the stored static value. If no node is connected then the
+ * static value should be used.
+ *
+ * @constructor
+ * @param {AudioletNode} node The node which the parameter is associated with.
+ * @param {Number} [inputIndex] The index of the AudioletInput to link to.
+ * @param {Number} [value=0] The initial static value to store.
+ */
+var AudioletParameter = function(node, inputIndex, value) {
+ this.node = node;
+ if (typeof inputIndex != 'undefined' && inputIndex != null) {
+ this.input = node.inputs[inputIndex];
+ }
+ else {
+ this.input = null;
+ }
+ this.value = value || 0;
+};
+
+/**
+ * Check whether the static value should be used.
+ *
+ * @return {Boolean} True if the static value should be used.
+ */
+AudioletParameter.prototype.isStatic = function() {
+ return (this.input.samples.length == 0);
+};
+
+/**
+ * Check whether the dynamic values should be used.
+ *
+ * @return {Boolean} True if the dynamic values should be used.
+ */
+AudioletParameter.prototype.isDynamic = function() {
+ return (this.input.samples.length > 0);
+};
+
+/**
+ * Set the stored static value
+ *
+ * @param {Number} value The value to store.
+ */
+AudioletParameter.prototype.setValue = function(value) {
+ this.value = value;
+};
+
+/**
+ * Get the stored static value
+ *
+ * @return {Number} The stored static value.
+ */
+AudioletParameter.prototype.getValue = function() {
+ if (this.input != null && this.input.samples.length > 0) {
+ return this.input.samples[0];
+ }
+ else {
+ return this.value;
+ }
+};
+
+/*
+ * A method for extending a javascript pseudo-class
+ * Taken from
+ * http://peter.michaux.ca/articles/class-based-inheritance-in-javascript
+ *
+ * @param {Object} subclass The class to extend.
+ * @param {Object} superclass The class to be extended.
+ */
+function extend(subclass, superclass) {
+ function Dummy() {}
+ Dummy.prototype = superclass.prototype;
+ subclass.prototype = new Dummy();
+ subclass.prototype.constructor = subclass;
+}
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * A type of AudioletNode designed to allow AudioletGroups to exactly replicate
+ * the behaviour of AudioletParameters. By linking one of the group's inputs
+ * to the ParameterNode's input, and calling `this.parameterName =
+ * parameterNode` in the group's constructor, `this.parameterName` will behave
+ * as if it were an AudioletParameter contained within an AudioletNode.
+ *
+ * **Inputs**
+ *
+ * - Parameter input
+ *
+ * **Outputs**
+ *
+ * - Parameter value
+ *
+ * **Parameters**
+ *
+ * - parameter The contained parameter. Linked to input 0.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} value The initial static value of the parameter.
+ */
+var ParameterNode = function(audiolet, value) {
+ AudioletNode.call(this, audiolet, 1, 1);
+ this.parameter = new AudioletParameter(this, 0, value);
+};
+extend(ParameterNode, AudioletNode);
+
+/**
+ * Process samples
+ */
+ParameterNode.prototype.generate = function() {
+ this.outputs[0].samples[0] = this.parameter.getValue();
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+ParameterNode.prototype.toString = function() {
+ return 'Parameter Node';
+};
+
+/*!
+ * @depends AudioletNode.js
+ */
+
+/**
+ * A specialized type of AudioletNode where values from the inputs are passed
+ * straight to the corresponding outputs in the most efficient way possible.
+ * PassThroughNodes are used in AudioletGroups to provide the inputs and
+ * outputs, and can also be used in analysis nodes where no modifications to
+ * the incoming audio are made.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} numberOfInputs The number of inputs.
+ * @param {Number} numberOfOutputs The number of outputs.
+ */
+var PassThroughNode = function(audiolet, numberOfInputs, numberOfOutputs) {
+ AudioletNode.call(this, audiolet, numberOfInputs, numberOfOutputs);
+};
+extend(PassThroughNode, AudioletNode);
+
+/**
+ * Create output samples for each channel, copying any input samples to
+ * the corresponding outputs.
+ */
+PassThroughNode.prototype.createOutputSamples = function() {
+ var numberOfOutputs = this.outputs.length;
+ // Copy the inputs buffers straight to the output buffers
+ for (var i = 0; i < numberOfOutputs; i++) {
+ var input = this.inputs[i];
+ var output = this.outputs[i];
+ if (input && input.samples.length != 0) {
+ // Copy the input buffer straight to the output buffers
+ output.samples = input.samples;
+ }
+ else {
+ // Create the correct number of output samples
+ var numberOfChannels = output.getNumberOfChannels();
+ if (output.samples.length == numberOfChannels) {
+ continue;
+ }
+ else if (output.samples.length > numberOfChannels) {
+ output.samples = output.samples.slice(0, numberOfChannels);
+ continue;
+ }
+
+ for (var j = output.samples.length; j < numberOfChannels; j++) {
+ output.samples[j] = 0;
+ }
+ }
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+PassThroughNode.prototype.toString = function() {
+ return 'Pass Through Node';
+};
+
+/**
+ * Priority Queue based on python heapq module
+ * http://svn.python.org/view/python/branches/release27-maint/Lib/heapq.py
+ *
+ * @constructor
+ * @param {Object[]} [array] Initial array of values to store.
+ * @param {Function} [compare] Compare function.
+ */
+var PriorityQueue = function(array, compare) {
+ if (compare) {
+ this.compare = compare;
+ }
+
+ if (array) {
+ this.heap = array;
+ for (var i = 0; i < Math.floor(this.heap.length / 2); i++) {
+ this.siftUp(i);
+ }
+ }
+ else {
+ this.heap = [];
+ }
+};
+
+/**
+ * Add an item to the queue
+ *
+ * @param {Object} item The item to add.
+ */
+PriorityQueue.prototype.push = function(item) {
+ this.heap.push(item);
+ this.siftDown(0, this.heap.length - 1);
+};
+
+/**
+ * Remove and return the top item from the queue.
+ *
+ * @return {Object} The top item.
+ */
+PriorityQueue.prototype.pop = function() {
+ var lastElement, returnItem;
+ lastElement = this.heap.pop();
+ if (this.heap.length) {
+ var returnItem = this.heap[0];
+ this.heap[0] = lastElement;
+ this.siftUp(0);
+ }
+ else {
+ returnItem = lastElement;
+ }
+ return (returnItem);
+};
+
+/**
+ * Return the top item from the queue, without removing it.
+ *
+ * @return {Object} The top item.
+ */
+PriorityQueue.prototype.peek = function() {
+ return (this.heap[0]);
+};
+
+/**
+ * Check whether the queue is empty.
+ *
+ * @return {Boolean} True if the queue is empty.
+ */
+PriorityQueue.prototype.isEmpty = function() {
+ return (this.heap.length == 0);
+};
+
+
+/**
+ * Sift item down the queue.
+ *
+ * @param {Number} startPosition Queue start position.
+ * @param {Number} position Item position.
+ */
+PriorityQueue.prototype.siftDown = function(startPosition, position) {
+ var newItem = this.heap[position];
+ while (position > startPosition) {
+ var parentPosition = (position - 1) >> 1;
+ var parent = this.heap[parentPosition];
+ if (this.compare(newItem, parent)) {
+ this.heap[position] = parent;
+ position = parentPosition;
+ continue;
+ }
+ break;
+ }
+ this.heap[position] = newItem;
+};
+
+/**
+ * Sift item up the queue.
+ *
+ * @param {Number} position Item position.
+ */
+PriorityQueue.prototype.siftUp = function(position) {
+ var endPosition = this.heap.length;
+ var startPosition = position;
+ var newItem = this.heap[position];
+ var childPosition = 2 * position + 1;
+ while (childPosition < endPosition) {
+ var rightPosition = childPosition + 1;
+ if (rightPosition < endPosition &&
+ !this.compare(this.heap[childPosition],
+ this.heap[rightPosition])) {
+ childPosition = rightPosition;
+ }
+ this.heap[position] = this.heap[childPosition];
+ position = childPosition;
+ childPosition = 2 * position + 1;
+ }
+ this.heap[position] = newItem;
+ this.siftDown(startPosition, position);
+};
+
+/**
+ * Default compare function.
+ *
+ * @param {Number} a First item.
+ * @param {Number} b Second item.
+ * @return {Boolean} True if a < b.
+ */
+PriorityQueue.prototype.compare = function(a, b) {
+ return (a < b);
+};
+
+/*!
+ * @depends PassThroughNode.js
+ */
+
+/**
+ * A sample-accurate scheduler built as an AudioletNode. The scheduler works
+ * by storing a queue of events, and running callback functions when the
+ * correct sample is being processed. All timing and events are handled in
+ * beats, which are converted to sample positions using a master tempo.
+ *
+ * **Inputs**
+ *
+ * - Audio
+ *
+ * **Outputs**
+ *
+ * - Audio
+ *
+ * @constructor
+ * @extends PassThroughNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [bpm=120] Initial tempo.
+ */
+var Scheduler = function(audiolet, bpm) {
+ PassThroughNode.call(this, audiolet, 1, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+ this.bpm = bpm || 120;
+ this.queue = new PriorityQueue(null, function(a, b) {
+ return (a.time < b.time);
+ });
+
+ this.time = 0;
+ this.beat = 0;
+ this.beatInBar = 0;
+ this.bar = 0;
+ this.seconds = 0;
+ this.beatsPerBar = 0;
+
+ this.lastBeatTime = 0;
+ this.beatLength = 60 / this.bpm * this.audiolet.device.sampleRate;
+};
+extend(Scheduler, PassThroughNode);
+
+/**
+ * Set the tempo of the scheduler.
+ *
+ * @param {Number} bpm The tempo in beats per minute.
+ */
+Scheduler.prototype.setTempo = function(bpm) {
+ this.bpm = bpm;
+ this.beatLength = 60 / this.bpm * this.audiolet.device.sampleRate;
+};
+
+/**
+ * Add an event relative to the current write position
+ *
+ * @param {Number} beats How many beats in the future to schedule the event.
+ * @param {Function} callback A function called when it is time for the event.
+ * @return {Object} The event object.
+ */
+Scheduler.prototype.addRelative = function(beats, callback) {
+ var event = {};
+ event.callback = callback;
+ event.time = this.time + beats * this.beatLength;
+ this.queue.push(event);
+ return event;
+};
+
+/**
+ * Add an event at an absolute beat position
+ *
+ * @param {Number} beat The beat at which the event should take place.
+ * @param {Function} callback A function called when it is time for the event.
+ * @return {Object} The event object.
+ */
+Scheduler.prototype.addAbsolute = function(beat, callback) {
+ if (beat < this.beat ||
+ beat == this.beat && this.time > this.lastBeatTime) {
+ // Nah
+ return null;
+ }
+ var event = {};
+ event.callback = callback;
+ event.time = this.lastBeatTime + (beat - this.beat) * this.beatLength;
+ this.queue.push(event);
+ return event;
+};
+
+/**
+ * Schedule patterns to play, and provide the values generated to a callback.
+ * The durationPattern argument can be either a number, giving a constant time
+ * between each event, or a pattern, allowing varying time difference.
+ *
+ * @param {Pattern[]} patterns An array of patterns to play.
+ * @param {Pattern|Number} durationPattern The number of beats between events.
+ * @param {Function} callback Function called with the generated pattern values.
+ * @return {Object} The event object.
+ */
+Scheduler.prototype.play = function(patterns, durationPattern, callback) {
+ var event = {};
+ event.patterns = patterns;
+ event.durationPattern = durationPattern;
+ event.callback = callback;
+ // TODO: Quantizing start time
+ event.time = this.audiolet.device.getWriteTime();
+ this.queue.push(event);
+ return event;
+};
+
+/**
+ * Schedule patterns to play starting at an absolute beat position,
+ * and provide the values generated to a callback.
+ * The durationPattern argument can be either a number, giving a constant time
+ * between each event, or a pattern, allowing varying time difference.
+ *
+ * @param {Number} beat The beat at which the event should take place.
+ * @param {Pattern[]} patterns An array of patterns to play.
+ * @param {Pattern|Number} durationPattern The number of beats between events.
+ * @param {Function} callback Function called with the generated pattern values.
+ * @return {Object} The event object.
+ */
+Scheduler.prototype.playAbsolute = function(beat, patterns, durationPattern,
+ callback) {
+ if (beat < this.beat ||
+ beat == this.beat && this.time > this.lastBeatTime) {
+ // Nah
+ return null;
+ }
+ var event = {};
+ event.patterns = patterns;
+ event.durationPattern = durationPattern;
+ event.callback = callback;
+ event.time = this.lastBeatTime + (beat - this.beat) * this.beatLength;
+ this.queue.push(event);
+ return event;
+};
+
+
+/**
+ * Remove a scheduled event from the scheduler
+ *
+ * @param {Object} event The event to remove.
+ */
+Scheduler.prototype.remove = function(event) {
+ var idx = this.queue.heap.indexOf(event);
+ if (idx != -1) {
+ this.queue.heap.splice(idx, 1);
+ // Recreate queue with event removed
+ this.queue = new PriorityQueue(this.queue.heap, function(a, b) {
+ return (a.time < b.time);
+ });
+ }
+};
+
+/**
+ * Alias for remove, so for simple events we have add/remove, and for patterns
+ * we have play/stop.
+ *
+ * @param {Object} event The event to remove.
+ */
+Scheduler.prototype.stop = function(event) {
+ this.remove(event);
+};
+
+/**
+ * Overridden tick method. Process any events which are due to take place
+ * either now or previously.
+ */
+Scheduler.prototype.tick = function() {
+ PassThroughNode.prototype.tick.call(this);
+ this.tickClock();
+
+ while (!this.queue.isEmpty() &&
+ this.queue.peek().time <= this.time) {
+ var event = this.queue.pop();
+ this.processEvent(event);
+ }
+};
+
+/**
+ * Update the various representations of time within the scheduler.
+ */
+Scheduler.prototype.tickClock = function() {
+ this.time += 1;
+ this.seconds = this.time / this.audiolet.device.sampleRate;
+ if (this.time >= this.lastBeatTime + this.beatLength) {
+ this.beat += 1;
+ this.beatInBar += 1;
+ if (this.beatInBar == this.beatsPerBar) {
+ this.bar += 1;
+ this.beatInBar = 0;
+ }
+ this.lastBeatTime += this.beatLength;
+ }
+};
+
+/**
+ * Process a single event, grabbing any necessary values, calling the event's
+ * callback, and rescheduling it if necessary.
+ *
+ * @param {Object} event The event to process.
+ */
+Scheduler.prototype.processEvent = function(event) {
+ var durationPattern = event.durationPattern;
+ if (durationPattern) {
+ // Pattern event
+ var args = [];
+ var patterns = event.patterns;
+ var numberOfPatterns = patterns.length;
+ for (var i = 0; i < numberOfPatterns; i++) {
+ var pattern = patterns[i];
+ var value = pattern.next();
+ if (value != null) {
+ args.push(value);
+ }
+ else {
+ // Null value for an argument, so don't process the
+ // callback or add any further events
+ return;
+ }
+ }
+ event.callback.apply(null, args);
+
+ var duration;
+ if (durationPattern instanceof Pattern) {
+ duration = durationPattern.next();
+ }
+ else {
+ duration = durationPattern;
+ }
+
+ if (duration) {
+ // Beats -> time
+ event.time += duration * this.beatLength;
+ this.queue.push(event);
+ }
+ }
+ else {
+ // Regular event
+ event.callback();
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Scheduler.prototype.toString = function() {
+ return 'Scheduler';
+};
+
+/**
+ * Bidirectional shim for the renaming of slice to subarray. Provides
+ * backwards compatibility with old browser releases
+ */
+var Int8Array, Uint8Array, Int16Array, Uint16Array;
+var Int32Array, Uint32Array, Float32Array, Float64Array;
+var types = [Int8Array, Uint8Array, Int16Array, Uint16Array,
+ Int32Array, Uint32Array, Float32Array, Float64Array];
+var original, shim;
+for (var i = 0; i < types.length; ++i) {
+ if (types[i]) {
+ if (types[i].prototype.slice === undefined) {
+ original = 'subarray';
+ shim = 'slice';
+ }
+ else if (types[i].prototype.subarray === undefined) {
+ original = 'slice';
+ shim = 'subarray';
+ }
+ Object.defineProperty(types[i].prototype, shim, {
+ value: types[i].prototype[original],
+ enumerable: false
+ });
+ }
+}
+
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * A generic envelope consisting of linear transitions of varying duration
+ * between a series of values.
+ *
+ * **Inputs**
+ *
+ * - Gate
+ *
+ * **Outputs**
+ *
+ * - Envelope
+ *
+ * **Parameters**
+ *
+ * - gate Gate controlling the envelope. Values should be 0 (off) or 1 (on).
+ * Linked to input 0.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [gate=1] Initial gate value.
+ * @param {Number[]} levels An array (of length n) of values to move between.
+ * @param {Number[]} times An array of n-1 durations - one for each transition.
+ * @param {Number} [releaseStage] Sustain at this stage until the the gate is 0.
+ * @param {Function} [onComplete] Function called as the envelope finishes.
+ */
+var Envelope = function(audiolet, gate, levels, times, releaseStage,
+ onComplete) {
+ AudioletNode.call(this, audiolet, 1, 1);
+ this.gate = new AudioletParameter(this, 0, gate || 1);
+
+ this.levels = [];
+ for (var i=0; i<levels.length; i++) {
+ this.levels.push(new AudioletParameter(this, null, levels[i]));
+ }
+
+ this.times = [];
+ for (var i=0; i<times.length; i++) {
+ this.times.push(new AudioletParameter(this, null, times[i]));
+ }
+
+ this.releaseStage = releaseStage;
+ this.onComplete = onComplete;
+
+ this.stage = null;
+ this.time = null;
+ this.changeTime = null;
+
+ this.level = this.levels[0].getValue();
+ this.delta = 0;
+ this.gateOn = false;
+};
+extend(Envelope, AudioletNode);
+
+/**
+ * Process samples
+ */
+Envelope.prototype.generate = function() {
+ var gate = this.gate.getValue();
+
+ var stageChanged = false;
+
+ if (gate && !this.gateOn) {
+ // Key pressed
+ this.gateOn = true;
+ this.stage = 0;
+ this.time = 0;
+ this.delta = 0;
+ this.level = this.levels[0].getValue();
+ if (this.stage != this.releaseStage) {
+ stageChanged = true;
+ }
+ }
+
+ if (this.gateOn && !gate) {
+ // Key released
+ this.gateOn = false;
+ if (this.releaseStage != null) {
+ // Jump to the release stage
+ this.stage = this.releaseStage;
+ stageChanged = true;
+ }
+ }
+
+ if (this.changeTime) {
+ // We are not sustaining, and we are playing, so increase the
+ // time
+ this.time += 1;
+ if (this.time >= this.changeTime) {
+ // Need to go to the next stage
+ this.stage += 1;
+ if (this.stage != this.releaseStage) {
+ stageChanged = true;
+ }
+ else {
+ // If we reach the release stage then sustain the value
+ // until the gate is released rather than moving on
+ // to the next level.
+ this.changeTime = null;
+ this.delta = 0;
+ }
+ }
+ }
+
+ if (stageChanged) {
+// level = this.levels[stage];
+ if (this.stage != this.times.length) {
+ // Actually update the variables
+ this.delta = this.calculateDelta(this.stage, this.level);
+ this.changeTime = this.calculateChangeTime(this.stage, this.time);
+ }
+ else {
+ // Made it to the end, so finish up
+ if (this.onComplete) {
+ this.onComplete();
+ }
+ this.stage = null;
+ this.time = null;
+ this.changeTime = null;
+
+ this.delta = 0;
+ }
+ }
+
+ this.level += this.delta;
+ this.outputs[0].samples[0] = this.level;
+};
+
+/**
+ * Calculate the change in level needed each sample for a section
+ *
+ * @param {Number} stage The index of the current stage.
+ * @param {Number} level The current level.
+ * @return {Number} The change in level.
+ */
+Envelope.prototype.calculateDelta = function(stage, level) {
+ var delta = this.levels[stage + 1].getValue() - level;
+ var stageTime = this.times[stage].getValue() *
+ this.audiolet.device.sampleRate;
+ return (delta / stageTime);
+};
+
+/**
+ * Calculate the time in samples at which the next stage starts
+ *
+ * @param {Number} stage The index of the current stage.
+ * @param {Number} time The current time.
+ * @return {Number} The change time.
+ */
+Envelope.prototype.calculateChangeTime = function(stage, time) {
+ var stageTime = this.times[stage].getValue() *
+ this.audiolet.device.sampleRate;
+ return (time + stageTime);
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Envelope.prototype.toString = function() {
+ return 'Envelope';
+};
+
+/*!
+ * @depends Envelope.js
+ */
+
+/**
+ * Linear attack-decay-sustain-release envelope
+ *
+ * **Inputs**
+ *
+ * - Gate
+ *
+ * **Outputs**
+ *
+ * - Envelope
+ *
+ * **Parameters**
+ *
+ * - gate The gate turning the envelope on and off. Value changes from 0 -> 1
+ * trigger the envelope. Value changes from 1 -> 0 make the envelope move to
+ * its release stage. Linked to input 0.
+ *
+ * @constructor
+ * @extends Envelope
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} gate The initial gate value.
+ * @param {Number} attack The attack time in seconds.
+ * @param {Number} decay The decay time in seconds.
+ * @param {Number} sustain The sustain level (between 0 and 1).
+ * @param {Number} release The release time in seconds.
+ * @param {Function} onComplete A function called after the release stage.
+ */
+var ADSREnvelope = function(audiolet, gate, attack, decay, sustain, release,
+ onComplete) {
+ var levels = [0, 1, sustain, 0];
+ var times = [attack, decay, release];
+ Envelope.call(this, audiolet, gate, levels, times, 2, onComplete);
+
+ this.attack = this.times[0];
+ this.decay = this.times[1];
+ this.sustain = this.levels[2];
+ this.release = this.levels[2];
+};
+extend(ADSREnvelope, Envelope);
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+ADSREnvelope.prototype.toString = function() {
+ return 'ADSR Envelope';
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Generic biquad filter. The coefficients (a0, a1, a2, b0, b1 and b2) are set
+ * using the calculateCoefficients function, which should be overridden and
+ * will be called automatically when new values are needed.
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Filter frequency
+ *
+ * **Outputs**
+ *
+ * - Filtered audio
+ *
+ * **Parameters**
+ *
+ * - frequency The filter frequency. Linked to input 1.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} frequency The initial frequency.
+ */
+var BiquadFilter = function(audiolet, frequency) {
+ AudioletNode.call(this, audiolet, 2, 1);
+
+ // Same number of output channels as input channels
+ this.linkNumberOfOutputChannels(0, 0);
+
+ this.frequency = new AudioletParameter(this, 1, frequency || 22100);
+ this.lastFrequency = null; // See if we need to recalculate coefficients
+
+ // Delayed values
+ this.xValues = [];
+ this.yValues = [];
+
+ // Coefficients
+ this.b0 = 0;
+ this.b1 = 0;
+ this.b2 = 0;
+ this.a0 = 0;
+ this.a1 = 0;
+ this.a2 = 0;
+};
+extend(BiquadFilter, AudioletNode);
+
+/**
+ * Calculate the biquad filter coefficients. This should be overridden.
+ *
+ * @param {Number} frequency The filter frequency.
+ */
+BiquadFilter.prototype.calculateCoefficients = function(frequency) {
+};
+
+/**
+ * Process samples
+ */
+BiquadFilter.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0]
+ var xValueArray = this.xValues;
+ var yValueArray = this.yValues;
+
+ var frequency = this.frequency.getValue();
+
+ if (frequency != this.lastFrequency) {
+ // Recalculate the coefficients
+ this.calculateCoefficients(frequency);
+ this.lastFrequency = frequency;
+ }
+
+ var a0 = this.a0;
+ var a1 = this.a1;
+ var a2 = this.a2;
+ var b0 = this.b0;
+ var b1 = this.b1;
+ var b2 = this.b2;
+
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ if (i >= xValueArray.length) {
+ xValueArray.push([0, 0]);
+ yValueArray.push([0, 0]);
+ }
+
+ var xValues = xValueArray[i];
+ var x1 = xValues[0];
+ var x2 = xValues[1];
+ var yValues = yValueArray[i];
+ var y1 = yValues[0];
+ var y2 = yValues[1];
+
+ var x0 = input.samples[i];
+ var y0 = (b0 / a0) * x0 +
+ (b1 / a0) * x1 +
+ (b2 / a0) * x2 -
+ (a1 / a0) * y1 -
+ (a2 / a0) * y2;
+
+ output.samples[i] = y0;
+
+ xValues[0] = x0;
+ xValues[1] = x1;
+ yValues[0] = y0;
+ yValues[1] = y1;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+BiquadFilter.prototype.toString = function() {
+ return 'Biquad Filter';
+};
+
+/*!
+ * @depends BiquadFilter.js
+ */
+
+/**
+ * All-pass filter
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Filter frequency
+ *
+ * **Outputs**
+ *
+ * - Filtered audio
+ *
+ * **Parameters**
+ *
+ * - frequency The filter frequency. Linked to input 1.
+ *
+ * @constructor
+ * @extends BiquadFilter
+ *
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} frequency The initial frequency.
+ */
+var AllPassFilter = function(audiolet, frequency) {
+ BiquadFilter.call(this, audiolet, frequency);
+};
+extend(AllPassFilter, BiquadFilter);
+
+/**
+ * Calculate the biquad filter coefficients using maths from
+ * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
+ *
+ * @param {Number} frequency The filter frequency.
+ */
+AllPassFilter.prototype.calculateCoefficients = function(frequency) {
+ var w0 = 2 * Math.PI * frequency /
+ this.audiolet.device.sampleRate;
+ var cosw0 = Math.cos(w0);
+ var sinw0 = Math.sin(w0);
+ var alpha = sinw0 / (2 / Math.sqrt(2));
+
+ this.b0 = 1 - alpha;
+ this.b1 = -2 * cosw0;
+ this.b2 = 1 + alpha;
+ this.a0 = 1 + alpha;
+ this.a1 = -2 * cosw0;
+ this.a2 = 1 - alpha;
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+AllPassFilter.prototype.toString = function() {
+ return 'All Pass Filter';
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Amplitude envelope follower
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Attack time
+ * - Release time
+ *
+ * **Outputs**
+ *
+ * - Amplitude envelope
+ *
+ * **Parameters**
+ *
+ * - attack The attack time of the envelope follower. Linked to input 1.
+ * - release The release time of the envelope follower. Linked to input 2.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [attack=0.01] The initial attack time in seconds.
+ * @param {Number} [release=0.01] The initial release time in seconds.
+ */
+var Amplitude = function(audiolet, attack, release) {
+ AudioletNode.call(this, audiolet, 3, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+
+ this.followers = [];
+
+ this.attack = new AudioletParameter(this, 1, attack || 0.01);
+ this.release = new AudioletParameter(this, 2, release || 0.01);
+};
+extend(Amplitude, AudioletNode);
+
+/**
+ * Process samples
+ */
+Amplitude.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var followers = this.followers;
+ var numberOfFollowers = followers.length;
+
+ var sampleRate = this.audiolet.device.sampleRate;
+
+ // Local processing variables
+ var attack = this.attack.getValue();
+ attack = Math.pow(0.01, 1 / (attack * sampleRate));
+ var release = this.release.getValue();
+ release = Math.pow(0.01, 1 / (release * sampleRate));
+
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ if (i >= numberOfFollowers) {
+ followers.push(0);
+ }
+ var follower = followers[i];
+
+ var value = Math.abs(input.samples[i]);
+ if (value > follower) {
+ follower = attack * (follower - value) + value;
+ }
+ else {
+ follower = release * (follower - value) + value;
+ }
+ output.samples[i] = follower;
+ followers[i] = follower;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Amplitude.prototype.toString = function() {
+ return ('Amplitude');
+};
+
+/*!
+ * @depends ../core/PassThroughNode.js
+ */
+
+/**
+ * Detect potentially hazardous values in the audio stream. Looks for
+ * undefineds, nulls, NaNs and Infinities.
+ *
+ * **Inputs**
+ *
+ * - Audio
+ *
+ * **Outputs**
+ *
+ * - Audio
+ *
+ * @constructor
+ * @extends PassThroughNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Function} [callback] Function called if a bad value is detected.
+ */
+var BadValueDetector = function(audiolet, callback) {
+ PassThroughNode.call(this, audiolet, 1, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+
+ if (callback) {
+ this.callback = callback;
+ }
+};
+extend(BadValueDetector, PassThroughNode);
+
+/**
+ * Default callback. Logs the value and position of the bad value.
+ *
+ * @param {Number|Object|'undefined'} value The value detected.
+ * @param {Number} channel The index of the channel the value was found in.
+ * @param {Number} index The sample index the value was found at.
+ */
+BadValueDetector.prototype.callback = function(value, channel) {
+ console.error(value + ' detected at channel ' + channel);
+};
+
+/**
+ * Process samples
+ */
+BadValueDetector.prototype.generate = function() {
+ var input = this.inputs[0];
+
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ var value = input.samples[i];
+ if (typeof value == 'undefined' ||
+ value == null ||
+ isNaN(value) ||
+ value == Infinity ||
+ value == -Infinity) {
+ this.callback(value, i);
+ }
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+BadValueDetector.prototype.toString = function() {
+ return 'Bad Value Detector';
+};
+
+/*!
+ * @depends BiquadFilter.js
+ */
+
+/**
+ * Band-pass filter
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Filter frequency
+ *
+ * **Outputs**
+ *
+ * - Filtered audio
+ *
+ * **Parameters**
+ *
+ * - frequency The filter frequency. Linked to input 1.
+ *
+ * @constructor
+ * @extends BiquadFilter
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} frequency The initial frequency.
+ */
+var BandPassFilter = function(audiolet, frequency) {
+ BiquadFilter.call(this, audiolet, frequency);
+};
+extend(BandPassFilter, BiquadFilter);
+
+/**
+ * Calculate the biquad filter coefficients using maths from
+ * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
+ *
+ * @param {Number} frequency The filter frequency.
+ */
+BandPassFilter.prototype.calculateCoefficients = function(frequency) {
+ var w0 = 2 * Math.PI * frequency / this.audiolet.device.sampleRate;
+ var cosw0 = Math.cos(w0);
+ var sinw0 = Math.sin(w0);
+ var alpha = sinw0 / (2 / Math.sqrt(2));
+
+ this.b0 = alpha;
+ this.b1 = 0;
+ this.b2 = -alpha;
+ this.a0 = 1 + alpha;
+ this.a1 = -2 * cosw0;
+ this.a2 = 1 - alpha;
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+BandPassFilter.prototype.toString = function() {
+ return 'Band Pass Filter';
+};
+
+/*!
+ * @depends BiquadFilter.js
+ */
+
+/**
+ * Band-reject filter
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Filter frequency
+ *
+ * **Outputs**
+ *
+ * - Filtered audio
+ *
+ * **Parameters**
+ *
+ * - frequency The filter frequency. Linked to input 1.
+ *
+ * @constructor
+ * @extends BiquadFilter
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} frequency The initial frequency.
+ */
+var BandRejectFilter = function(audiolet, frequency) {
+ BiquadFilter.call(this, audiolet, frequency);
+};
+extend(BandRejectFilter, BiquadFilter);
+
+/**
+ * Calculate the biquad filter coefficients using maths from
+ * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
+ *
+ * @param {Number} frequency The filter frequency.
+ */
+BandRejectFilter.prototype.calculateCoefficients = function(frequency) {
+ var w0 = 2 * Math.PI * frequency /
+ this.audiolet.device.sampleRate;
+ var cosw0 = Math.cos(w0);
+ var sinw0 = Math.sin(w0);
+ var alpha = sinw0 / (2 / Math.sqrt(2));
+
+ this.b0 = 1;
+ this.b1 = -2 * cosw0;
+ this.b2 = 1;
+ this.a0 = 1 + alpha;
+ this.a1 = -2 * cosw0;
+ this.a2 = 1 - alpha;
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+BandRejectFilter.prototype.toString = function() {
+ return 'Band Reject Filter';
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Reduce the bitrate of incoming audio
+ *
+ * **Inputs**
+ *
+ * - Audio 1
+ * - Number of bits
+ *
+ * **Outputs**
+ *
+ * - Bit Crushed Audio
+ *
+ * **Parameters**
+ *
+ * - bits The number of bit to reduce to. Linked to input 1.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} bits The initial number of bits.
+ */
+var BitCrusher = function(audiolet, bits) {
+ AudioletNode.call(this, audiolet, 2, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+ this.bits = new AudioletParameter(this, 1, bits);
+};
+extend(BitCrusher, AudioletNode);
+
+/**
+ * Process samples
+ */
+BitCrusher.prototype.generate = function() {
+ var input = this.inputs[0];
+
+ var maxValue = Math.pow(2, this.bits.getValue()) - 1;
+
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ this.outputs[0].samples[i] = Math.floor(input.samples[i] * maxValue) /
+ maxValue;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+BitCrusher.prototype.toString = function() {
+ return 'BitCrusher';
+};
+
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Play the contents of an audio buffer
+ *
+ * **Inputs**
+ *
+ * - Playback rate
+ * - Restart trigger
+ * - Start position
+ * - Loop on/off
+ *
+ * **Outputs**
+ *
+ * - Audio
+ *
+ * **Parameters**
+ *
+ * - playbackRate The rate that the buffer should play at. Value of 1 plays at
+ * the regular rate. Values > 1 are pitched up. Values < 1 are pitched down.
+ * Linked to input 0.
+ * - restartTrigger Changes of value from 0 -> 1 restart the playback from the
+ * start position. Linked to input 1.
+ * - startPosition The position at which playback should begin. Values between
+ * 0 (the beginning of the buffer) and 1 (the end of the buffer). Linked to
+ * input 2.
+ * - loop Whether the buffer should loop when it reaches the end. Linked to
+ * input 3
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {AudioletBuffer} buffer The buffer to play.
+ * @param {Number} [playbackRate=1] The initial playback rate.
+ * @param {Number} [startPosition=0] The initial start position.
+ * @param {Number} [loop=0] Initial value for whether to loop.
+ * @param {Function} [onComplete] Called when the buffer has finished playing.
+ */
+var BufferPlayer = function(audiolet, buffer, playbackRate, startPosition,
+ loop, onComplete) {
+ AudioletNode.call(this, audiolet, 3, 1);
+ this.buffer = buffer;
+ this.setNumberOfOutputChannels(0, this.buffer.numberOfChannels);
+ this.position = startPosition || 0;
+ this.playbackRate = new AudioletParameter(this, 0, playbackRate || 1);
+ this.restartTrigger = new AudioletParameter(this, 1, 0);
+ this.startPosition = new AudioletParameter(this, 2, startPosition || 0);
+ this.loop = new AudioletParameter(this, 3, loop || 0);
+ this.onComplete = onComplete;
+
+ this.restartTriggerOn = false;
+ this.playing = true;
+};
+extend(BufferPlayer, AudioletNode);
+
+/**
+ * Process samples
+ */
+BufferPlayer.prototype.generate = function() {
+ var output = this.outputs[0];
+
+ // Cache local variables
+ var numberOfChannels = output.samples.length;
+
+ if (this.buffer.length == 0 || !this.playing) {
+ // No buffer data, or not playing, so output zeros and return
+ for (var i=0; i<numberOfChannels; i++) {
+ output.samples[i] = 0;
+ }
+ return;
+ }
+
+ // Crap load of parameters
+ var playbackRate = this.playbackRate.getValue();
+ var restartTrigger = this.restartTrigger.getValue();
+ var startPosition = this.startPosition.getValue();
+ var loop = this.loop.getValue();
+
+ if (restartTrigger > 0 && !this.restartTriggerOn) {
+ // Trigger moved from <=0 to >0, so we restart playback from
+ // startPosition
+ this.position = startPosition;
+ this.restartTriggerOn = true;
+ this.playing = true;
+ }
+
+ if (restartTrigger <= 0 && this.restartTriggerOn) {
+ // Trigger moved back to <= 0
+ this.restartTriggerOn = false;
+ }
+
+ var numberOfChannels = this.buffer.channels.length;
+
+ for (var i = 0; i < numberOfChannels; i++) {
+ var inputChannel = this.buffer.getChannelData(i);
+ output.samples[i] = inputChannel[Math.floor(this.position)];
+ }
+
+ this.position += playbackRate;
+
+ if (this.position >= this.buffer.length) {
+ if (loop) {
+ // Back to the start
+ this.position %= this.buffer.length;
+ }
+ else {
+ // Finish playing until a new restart trigger
+ this.playing = false;
+ if (this.onComplete) {
+ this.onComplete();
+ }
+ }
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+BufferPlayer.prototype.toString = function() {
+ return ('Buffer player');
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Undamped comb filter
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Delay Time
+ * - Decay Time
+ *
+ * **Outputs**
+ *
+ * - Filtered audio
+ *
+ * **Parameters**
+ *
+ * - delayTime The delay time in seconds. Linked to input 1.
+ * - decayTime Time for the echoes to decay by 60dB. Linked to input 0.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} maximumDelayTime The largest allowable delay time.
+ * @param {Number} delayTime The initial delay time.
+ * @param {Number} decayTime The initial decay time.
+ */
+var CombFilter = function(audiolet, maximumDelayTime, delayTime, decayTime) {
+ AudioletNode.call(this, audiolet, 3, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+ this.maximumDelayTime = maximumDelayTime;
+ this.delayTime = new AudioletParameter(this, 1, delayTime || 1);
+ this.decayTime = new AudioletParameter(this, 2, decayTime);
+ this.buffers = [];
+ this.readWriteIndex = 0;
+};
+extend(CombFilter, AudioletNode);
+
+/**
+ * Process samples
+ */
+CombFilter.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var sampleRate = this.audiolet.device.sampleRate;
+
+ var delayTime = this.delayTime.getValue() * sampleRate;
+ var decayTime = this.decayTime.getValue() * sampleRate;
+ var feedback = Math.exp(-3 * delayTime / decayTime);
+
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ if (i >= this.buffers.length) {
+ // Create buffer for channel if it doesn't already exist
+ var bufferSize = this.maximumDelayTime * sampleRate;
+ this.buffers.push(new Float32Array(bufferSize));
+ }
+
+ var buffer = this.buffers[i];
+ var outputValue = buffer[this.readWriteIndex];
+ output.samples[i] = outputValue;
+ buffer[this.readWriteIndex] = input.samples[i] + feedback * outputValue;
+ }
+
+ this.readWriteIndex += 1;
+ if (this.readWriteIndex >= delayTime) {
+ this.readWriteIndex = 0;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+CombFilter.prototype.toString = function() {
+ return 'Comb Filter';
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Sine wave oscillator
+ *
+ * **Inputs**
+ *
+ * - Frequency
+ *
+ * **Outputs**
+ *
+ * - Sine wave
+ *
+ * **Parameters**
+ *
+ * - frequency The frequency of the oscillator. Linked to input 0.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [frequency=440] Initial frequency.
+ */
+var Sine = function(audiolet, frequency) {
+ AudioletNode.call(this, audiolet, 1, 1);
+ this.frequency = new AudioletParameter(this, 0, frequency || 440);
+ this.phase = 0;
+};
+extend(Sine, AudioletNode);
+
+/**
+ * Process samples
+ */
+Sine.prototype.generate = function() {
+ var output = this.outputs[0];
+
+ var frequency = this.frequency.getValue();
+ var sampleRate = this.audiolet.device.sampleRate;
+
+ output.samples[0] = Math.sin(this.phase);
+
+ this.phase += 2 * Math.PI * frequency / sampleRate;
+ if (this.phase > 2 * Math.PI) {
+ this.phase %= 2 * Math.PI;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Sine.prototype.toString = function() {
+ return 'Sine';
+};
+
+
+/*!
+ * @depends ../core/AudioletNode.js
+ * @depends Sine.js
+ */
+
+/**
+ * Equal-power cross-fade between two signals
+ *
+ * **Inputs**
+ *
+ * - Audio 1
+ * - Audio 2
+ * - Fade Position
+ *
+ * **Outputs**
+ *
+ * - Mixed audio
+ *
+ * **Parameters**
+ *
+ * - position The fade position. Values between 0 (Audio 1 only) and 1 (Audio
+ * 2 only). Linked to input 2.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [position=0.5] The initial fade position.
+ */
+var CrossFade = function(audiolet, position) {
+ AudioletNode.call(this, audiolet, 3, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+ this.position = new AudioletParameter(this, 2, position || 0.5);
+};
+extend(CrossFade, AudioletNode);
+
+/**
+ * Process samples
+ */
+CrossFade.prototype.generate = function() {
+ var inputA = this.inputs[0];
+ var inputB = this.inputs[1];
+ var output = this.outputs[0];
+
+ // Local processing variables
+ var position = this.position.getValue();
+
+ var scaledPosition = position * Math.PI / 2;
+ var gainA = Math.cos(scaledPosition);
+ var gainB = Math.sin(scaledPosition);
+
+ var numberOfChannels = output.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ var valueA = inputA.samples[i] || 0;
+ var valueB = inputB.samples[i] || 0;
+ output.samples[i] = valueA * gainA + valueB * gainB;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+CrossFade.prototype.toString = function() {
+ return 'Cross Fader';
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Damped comb filter
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Delay Time
+ * - Decay Time
+ * - Damping
+ *
+ * **Outputs**
+ *
+ * - Filtered audio
+ *
+ * **Parameters**
+ *
+ * - delayTime The delay time in seconds. Linked to input 1.
+ * - decayTime Time for the echoes to decay by 60dB. Linked to input 2.
+ * - damping The amount of high-frequency damping of echoes. Linked to input 3.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} maximumDelayTime The largest allowable delay time.
+ * @param {Number} delayTime The initial delay time.
+ * @param {Number} decayTime The initial decay time.
+ * @param {Number} damping The initial amount of damping.
+ */
+var DampedCombFilter = function(audiolet, maximumDelayTime, delayTime,
+ decayTime, damping) {
+ AudioletNode.call(this, audiolet, 4, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+ this.maximumDelayTime = maximumDelayTime;
+ this.delayTime = new AudioletParameter(this, 1, delayTime || 1);
+ this.decayTime = new AudioletParameter(this, 2, decayTime);
+ this.damping = new AudioletParameter(this, 3, damping);
+ var bufferSize = maximumDelayTime * this.audiolet.device.sampleRate;
+ this.buffers = [];
+ this.readWriteIndex = 0;
+ this.filterStores = [];
+};
+extend(DampedCombFilter, AudioletNode);
+
+/**
+ * Process samples
+ */
+DampedCombFilter.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var sampleRate = this.audiolet.device.sampleRate;
+
+ var delayTime = this.delayTime.getValue() * sampleRate;
+ var decayTime = this.decayTime.getValue() * sampleRate;
+ var damping = this.damping.getValue();
+ var feedback = Math.exp(-3 * delayTime / decayTime);
+
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ if (i >= this.buffers.length) {
+ var bufferSize = this.maximumDelayTime * sampleRate;
+ this.buffers.push(new Float32Array(bufferSize));
+ }
+
+ if (i >= this.filterStores.length) {
+ this.filterStores.push(0);
+ }
+
+ var buffer = this.buffers[i];
+ var filterStore = this.filterStores[i];
+
+ var outputValue = buffer[this.readWriteIndex];
+ filterStore = (outputValue * (1 - damping)) +
+ (filterStore * damping);
+ output.samples[i] = outputValue;
+ buffer[this.readWriteIndex] = input.samples[i] +
+ feedback * filterStore;
+
+ this.filterStores[i] = filterStore;
+ }
+
+ this.readWriteIndex += 1;
+ if (this.readWriteIndex >= delayTime) {
+ this.readWriteIndex = 0;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+DampedCombFilter.prototype.toString = function() {
+ return 'Damped Comb Filter';
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Filter for leaking DC offset. Maths is taken from
+ * https://ccrma.stanford.edu/~jos/filters/DC_Blocker.html
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Filter coefficient
+ *
+ * **Outputs**
+ *
+ * - Filtered audio
+ *
+ * **Parameters**
+ *
+ * - coefficient The filter coefficient. Linked to input 1.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [coefficient=0.995] The initial coefficient.
+ */
+var DCFilter = function(audiolet, coefficient) {
+ AudioletNode.call(this, audiolet, 2, 1);
+
+ // Same number of output channels as input channels
+ this.linkNumberOfOutputChannels(0, 0);
+
+ this.coefficient = new AudioletParameter(this, 1, coefficient || 0.995);
+
+ // Delayed values
+ this.xValues = [];
+ this.yValues = [];
+};
+extend(DCFilter, AudioletNode);
+
+/**
+ * Process samples
+ */
+DCFilter.prototype.generate = function() {
+ var coefficient = this.coefficient.getValue();
+ var input = this.inputs[0];
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ if (i >= this.xValues.length) {
+ this.xValues.push(0);
+ }
+ if (i >= this.yValues.length) {
+ this.yValues.push(0);
+ }
+
+ var x0 = input.samples[i];
+ var y0 = x0 - this.xValues[i] + coefficient * this.yValues[i];
+
+ this.outputs[0].samples[i] = y0;
+
+ this.xValues[i] = x0;
+ this.yValues[i] = y0;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+DCFilter.prototype.toString = function() {
+ return 'DC Filter';
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * A simple delay line.
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Delay Time
+ *
+ * **Outputs**
+ *
+ * - Delayed audio
+ *
+ * **Parameters**
+ *
+ * - delayTime The delay time in seconds. Linked to input 1.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} maximumDelayTime The largest allowable delay time.
+ * @param {Number} delayTime The initial delay time.
+ */
+var Delay = function(audiolet, maximumDelayTime, delayTime) {
+ AudioletNode.call(this, audiolet, 2, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+ this.maximumDelayTime = maximumDelayTime;
+ this.delayTime = new AudioletParameter(this, 1, delayTime || 1);
+ var bufferSize = maximumDelayTime * this.audiolet.device.sampleRate;
+ this.buffers = [];
+ this.readWriteIndex = 0;
+};
+extend(Delay, AudioletNode);
+
+/**
+ * Process samples
+ */
+Delay.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var sampleRate = this.audiolet.device.sampleRate;
+
+ var delayTime = this.delayTime.getValue() * sampleRate;
+
+ var numberOfChannels = input.samples.length;
+
+ for (var i = 0; i < numberOfChannels; i++) {
+ if (i >= this.buffers.length) {
+ var bufferSize = this.maximumDelayTime * sampleRate;
+ this.buffers.push(new Float32Array(bufferSize));
+ }
+
+ var buffer = this.buffers[i];
+ output.samples[i] = buffer[this.readWriteIndex];
+ buffer[this.readWriteIndex] = input.samples[i];
+ }
+
+ this.readWriteIndex += 1;
+ if (this.readWriteIndex >= delayTime) {
+ this.readWriteIndex = 0;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Delay.prototype.toString = function() {
+ return 'Delay';
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Detect discontinuities in the input stream. Looks for consecutive samples
+ * with a difference larger than a threshold value.
+ *
+ * **Inputs**
+ *
+ * - Audio
+ *
+ * **Outputs**
+ *
+ * - Audio
+ *
+ * @constructor
+ * @extends PassThroughNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [threshold=0.2] The threshold value.
+ * @param {Function} [callback] Function called if a discontinuity is detected.
+ */
+var DiscontinuityDetector = function(audiolet, threshold, callback) {
+ AudioletNode.call(this, audiolet, 1, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+
+ this.threshold = threshold || 0.2;
+ if (callback) {
+ this.callback = callback;
+ }
+ this.lastValues = [];
+
+};
+extend(DiscontinuityDetector, AudioletNode);
+
+/**
+ * Default callback. Logs the size and position of the discontinuity.
+ *
+ * @param {Number} size The size of the discontinuity.
+ * @param {Number} channel The index of the channel the samples were found in.
+ * @param {Number} index The sample index the discontinuity was found at.
+ */
+DiscontinuityDetector.prototype.callback = function(size, channel) {
+ console.error('Discontinuity of ' + size + ' detected on channel ' +
+ channel);
+};
+
+/**
+ * Process samples
+ */
+DiscontinuityDetector.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ if (i >= this.lastValues.length) {
+ this.lastValues.push(0);
+ }
+
+ var value = input.samples[i];
+ var diff = Math.abs(this.lastValues[i] - value);
+ if (diff > this.threshold) {
+ this.callback(diff, i);
+ }
+
+ this.lastValues[i] = value;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+DiscontinuityDetector.prototype.toString = function() {
+ return 'Discontinuity Detector';
+};
+
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Delay line with feedback
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Delay Time
+ * - Feedback
+ * - Mix
+ *
+ * **Outputs**
+ *
+ * - Delayed audio
+ *
+ * **Parameters**
+ *
+ * - delayTime The delay time in seconds. Linked to input 1.
+ * - feedback The amount of feedback. Linked to input 2.
+ * - mix The amount of delay to mix into the dry signal. Linked to input 3.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} maximumDelayTime The largest allowable delay time.
+ * @param {Number} delayTime The initial delay time.
+ * @param {Number} feedabck The initial feedback amount.
+ * @param {Number} mix The initial mix amount.
+ */
+var FeedbackDelay = function(audiolet, maximumDelayTime, delayTime, feedback,
+ mix) {
+ AudioletNode.call(this, audiolet, 4, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+ this.maximumDelayTime = maximumDelayTime;
+ this.delayTime = new AudioletParameter(this, 1, delayTime || 1);
+ this.feedback = new AudioletParameter(this, 2, feedback || 0.5);
+ this.mix = new AudioletParameter(this, 3, mix || 1);
+ var bufferSize = maximumDelayTime * this.audiolet.device.sampleRate;
+ this.buffers = [];
+ this.readWriteIndex = 0;
+};
+extend(FeedbackDelay, AudioletNode);
+
+/**
+ * Process samples
+ */
+FeedbackDelay.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var sampleRate = this.audiolet.output.device.sampleRate;
+
+ var delayTime = this.delayTime.getValue() * sampleRate;
+ var feedback = this.feedback.getValue();
+ var mix = this.mix.getValue();
+
+ var numberOfChannels = input.samples.length;
+ var numberOfBuffers = this.buffers.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ if (i >= numberOfBuffers) {
+ // Create buffer for channel if it doesn't already exist
+ var bufferSize = this.maximumDelayTime * sampleRate;
+ this.buffers.push(new Float32Array(bufferSize));
+ }
+
+ var buffer = this.buffers[i];
+
+ var inputSample = input.samples[i];
+ var bufferSample = buffer[this.readWriteIndex];
+
+ output.samples[i] = mix * bufferSample + (1 - mix) * inputSample;
+ buffer[this.readWriteIndex] = inputSample + feedback * bufferSample;
+ }
+
+ this.readWriteIndex += 1;
+ if (this.readWriteIndex >= delayTime) {
+ this.readWriteIndex = 0;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+FeedbackDelay.prototype.toString = function() {
+ return 'Feedback Delay';
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Fast Fourier Transform
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Delay Time
+ *
+ * **Outputs**
+ *
+ * - Fourier transformed audio
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} bufferSize The FFT buffer size.
+ */
+var FFT = function(audiolet, bufferSize) {
+ AudioletNode.call(this, audiolet, 2, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+ this.bufferSize = bufferSize;
+ this.readWriteIndex = 0;
+
+ this.buffer = new Float32Array(this.bufferSize);
+
+ this.realBuffer = new Float32Array(this.bufferSize);
+ this.imaginaryBuffer = new Float32Array(this.bufferSize);
+
+ this.reverseTable = new Uint32Array(this.bufferSize);
+ this.calculateReverseTable();
+};
+extend(FFT, AudioletNode);
+
+/**
+ * Process samples
+ */
+FFT.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ if (input.samples.length == 0) {
+ return;
+ }
+
+ this.buffer[this.readWriteIndex] = input.samples[0];
+ output.samples[0] = [this.realBuffer[this.readWriteIndex],
+ this.imaginaryBuffer[this.readWriteIndex]];
+
+ this.readWriteIndex += 1;
+ if (this.readWriteIndex >= this.bufferSize) {
+ this.transform();
+ this.readWriteIndex = 0;
+ }
+};
+
+/**
+ * Precalculate the reverse table.
+ * TODO: Split the function out so it can be reused in FFT and IFFT
+ */
+FFT.prototype.calculateReverseTable = function() {
+ var limit = 1;
+ var bit = this.bufferSize >> 1;
+
+ while (limit < this.bufferSize) {
+ for (var i = 0; i < limit; i++) {
+ this.reverseTable[i + limit] = this.reverseTable[i] + bit;
+ }
+
+ limit = limit << 1;
+ bit = bit >> 1;
+ }
+};
+
+
+/**
+ * Calculate the FFT for the saved buffer
+ */
+FFT.prototype.transform = function() {
+ for (var i = 0; i < this.bufferSize; i++) {
+ this.realBuffer[i] = this.buffer[this.reverseTable[i]];
+ this.imaginaryBuffer[i] = 0;
+ }
+
+ var halfSize = 1;
+
+ while (halfSize < this.bufferSize) {
+ var phaseShiftStepReal = Math.cos(-Math.PI / halfSize);
+ var phaseShiftStepImag = Math.sin(-Math.PI / halfSize);
+
+ var currentPhaseShiftReal = 1;
+ var currentPhaseShiftImag = 0;
+
+ for (var fftStep = 0; fftStep < halfSize; fftStep++) {
+ var i = fftStep;
+
+ while (i < this.bufferSize) {
+ var off = i + halfSize;
+ var tr = (currentPhaseShiftReal * this.realBuffer[off]) -
+ (currentPhaseShiftImag * this.imaginaryBuffer[off]);
+ var ti = (currentPhaseShiftReal * this.imaginaryBuffer[off]) +
+ (currentPhaseShiftImag * this.realBuffer[off]);
+
+ this.realBuffer[off] = this.realBuffer[i] - tr;
+ this.imaginaryBuffer[off] = this.imaginaryBuffer[i] - ti;
+ this.realBuffer[i] += tr;
+ this.imaginaryBuffer[i] += ti;
+
+ i += halfSize << 1;
+ }
+
+ var tmpReal = currentPhaseShiftReal;
+ currentPhaseShiftReal = (tmpReal * phaseShiftStepReal) -
+ (currentPhaseShiftImag *
+ phaseShiftStepImag);
+ currentPhaseShiftImag = (tmpReal * phaseShiftStepImag) +
+ (currentPhaseShiftImag *
+ phaseShiftStepReal);
+ }
+
+ halfSize = halfSize << 1;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+FFT.prototype.toString = function() {
+ return 'FFT';
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/*
+ * Multiply values
+ *
+ * **Inputs**
+ *
+ * - Audio 1
+ * - Audio 2
+ *
+ * **Outputs**
+ *
+ * - Multiplied audio
+ *
+ * **Parameters**
+ *
+ * - value The value to multiply by. Linked to input 1.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [value=1] The initial value to multiply by.
+ */
+var Multiply = function(audiolet, value) {
+ AudioletNode.call(this, audiolet, 2, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+ this.value = new AudioletParameter(this, 1, value || 1);
+};
+extend(Multiply, AudioletNode);
+
+/**
+ * Process samples
+ */
+Multiply.prototype.generate = function() {
+ var value = this.value.getValue();
+ var input = this.inputs[0];
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ this.outputs[0].samples[i] = input.samples[i] * value;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Multiply.prototype.toString = function() {
+ return 'Multiply';
+};
+
+
+/*!
+ * @depends ../operators/Multiply.js
+ */
+
+/**
+ * Simple gain control
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Gain
+ *
+ * **Outputs**
+ *
+ * - Audio
+ *
+ * **Parameters**
+ *
+ * - gain The amount of gain. Linked to input 1.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [gain=1] Initial gain.
+ */
+var Gain = function(audiolet, gain) {
+ // Same DSP as operators/Multiply.js, but different parameter name
+ Multiply.call(this, audiolet, gain);
+ this.gain = this.value;
+};
+extend(Gain, Multiply);
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Gain.prototype.toString = function() {
+ return ('Gain');
+};
+
+/*!
+ * @depends BiquadFilter.js
+ */
+
+/**
+ * High-pass filter
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Filter frequency
+ *
+ * **Outputs**
+ *
+ * - Filtered audio
+ *
+ * **Parameters**
+ *
+ * - frequency The filter frequency. Linked to input 1.
+ *
+ * @constructor
+ * @extends BiquadFilter
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} frequency The initial frequency.
+ */
+var HighPassFilter = function(audiolet, frequency) {
+ BiquadFilter.call(this, audiolet, frequency);
+};
+extend(HighPassFilter, BiquadFilter);
+
+/**
+ * Calculate the biquad filter coefficients using maths from
+ * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
+ *
+ * @param {Number} frequency The filter frequency.
+ */
+HighPassFilter.prototype.calculateCoefficients = function(frequency) {
+ var w0 = 2 * Math.PI * frequency /
+ this.audiolet.device.sampleRate;
+ var cosw0 = Math.cos(w0);
+ var sinw0 = Math.sin(w0);
+ var alpha = sinw0 / (2 / Math.sqrt(2));
+
+ this.b0 = (1 + cosw0) / 2;
+ this.b1 = - (1 + cosw0);
+ this.b2 = this.b0;
+ this.a0 = 1 + alpha;
+ this.a1 = -2 * cosw0;
+ this.a2 = 1 - alpha;
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+HighPassFilter.prototype.toString = function() {
+ return 'High Pass Filter';
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Inverse Fast Fourier Transform. Code liberally stolen with kind permission
+ * of Corben Brook from DSP.js (https://github.com/corbanbrook/dsp.js).
+ *
+ * **Inputs**
+ *
+ * - Fourier transformed audio
+ * - Delay Time
+ *
+ * **Outputs**
+ *
+ * - Audio
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} bufferSize The FFT buffer size.
+ */
+var IFFT = function(audiolet, bufferSize) {
+ AudioletNode.call(this, audiolet, 2, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+ this.bufferSize = bufferSize;
+ this.readWriteIndex = 0;
+
+ this.buffer = new Float32Array(this.bufferSize);
+
+ this.realBuffer = new Float32Array(this.bufferSize);
+ this.imaginaryBuffer = new Float32Array(this.bufferSize);
+
+ this.reverseTable = new Uint32Array(this.bufferSize);
+ this.calculateReverseTable();
+
+ this.reverseReal = new Float32Array(this.bufferSize);
+ this.reverseImaginary = new Float32Array(this.bufferSize);
+};
+extend(IFFT, AudioletNode);
+
+/**
+ * Process samples
+ */
+IFFT.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ if (!input.samples.length) {
+ return;
+ }
+
+ var values = input.samples[0];
+ this.realBuffer[this.readWriteIndex] = values[0];
+ this.imaginaryBuffer[this.readWriteIndex] = values[1];
+ output.samples[0] = this.buffer[this.readWriteIndex];
+
+ this.readWriteIndex += 1;
+ if (this.readWriteIndex >= this.bufferSize) {
+ this.transform();
+ this.readWriteIndex = 0;
+ }
+};
+
+/**
+ * Precalculate the reverse table.
+ * TODO: Split the function out so it can be reused in FFT and IFFT
+ */
+IFFT.prototype.calculateReverseTable = function() {
+ var limit = 1;
+ var bit = this.bufferSize >> 1;
+
+ while (limit < this.bufferSize) {
+ for (var i = 0; i < limit; i++) {
+ this.reverseTable[i + limit] = this.reverseTable[i] + bit;
+ }
+
+ limit = limit << 1;
+ bit = bit >> 1;
+ }
+};
+
+/**
+ * Calculate the inverse FFT for the saved real and imaginary buffers
+ */
+IFFT.prototype.transform = function() {
+ var halfSize = 1;
+
+ for (var i = 0; i < this.bufferSize; i++) {
+ this.imaginaryBuffer[i] *= -1;
+ }
+
+ for (var i = 0; i < this.bufferSize; i++) {
+ this.reverseReal[i] = this.realBuffer[this.reverseTable[i]];
+ this.reverseImaginary[i] = this.imaginaryBuffer[this.reverseTable[i]];
+ }
+
+ this.realBuffer.set(this.reverseReal);
+ this.imaginaryBuffer.set(this.reverseImaginary);
+
+
+ while (halfSize < this.bufferSize) {
+ var phaseShiftStepReal = Math.cos(-Math.PI / halfSize);
+ var phaseShiftStepImag = Math.sin(-Math.PI / halfSize);
+ var currentPhaseShiftReal = 1;
+ var currentPhaseShiftImag = 0;
+
+ for (var fftStep = 0; fftStep < halfSize; fftStep++) {
+ i = fftStep;
+
+ while (i < this.bufferSize) {
+ var off = i + halfSize;
+ var tr = (currentPhaseShiftReal * this.realBuffer[off]) -
+ (currentPhaseShiftImag * this.imaginaryBuffer[off]);
+ var ti = (currentPhaseShiftReal * this.imaginaryBuffer[off]) +
+ (currentPhaseShiftImag * this.realBuffer[off]);
+
+ this.realBuffer[off] = this.realBuffer[i] - tr;
+ this.imaginaryBuffer[off] = this.imaginaryBuffer[i] - ti;
+ this.realBuffer[i] += tr;
+ this.imaginaryBuffer[i] += ti;
+
+ i += halfSize << 1;
+ }
+
+ var tmpReal = currentPhaseShiftReal;
+ currentPhaseShiftReal = (tmpReal * phaseShiftStepReal) -
+ (currentPhaseShiftImag *
+ phaseShiftStepImag);
+ currentPhaseShiftImag = (tmpReal * phaseShiftStepImag) +
+ (currentPhaseShiftImag *
+ phaseShiftStepReal);
+ }
+
+ halfSize = halfSize << 1;
+ }
+
+ for (i = 0; i < this.bufferSize; i++) {
+ this.buffer[i] = this.realBuffer[i] / this.bufferSize;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+IFFT.prototype.toString = function() {
+ return 'IFFT';
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Exponential lag for smoothing signals.
+ *
+ * **Inputs**
+ *
+ * - Value
+ * - Lag time
+ *
+ * **Outputs**
+ *
+ * - Lagged value
+ *
+ * **Parameters**
+ *
+ * - value The value to lag. Linked to input 0.
+ * - lag The 60dB lag time. Linked to input 1.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [value=0] The initial value.
+ * @param {Number} [lagTime=1] The initial lag time.
+ */
+var Lag = function(audiolet, value, lagTime) {
+ AudioletNode.call(this, audiolet, 2, 1);
+ this.value = new AudioletParameter(this, 0, value || 0);
+ this.lag = new AudioletParameter(this, 1, lagTime || 1);
+ this.lastValue = 0;
+
+ this.log001 = Math.log(0.001);
+};
+extend(Lag, AudioletNode);
+
+/**
+ * Process samples
+ */
+Lag.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var sampleRate = this.audiolet.device.sampleRate;
+
+ var value = this.value.getValue();
+ var lag = this.lag.getValue();
+ var coefficient = Math.exp(this.log001 / (lag * sampleRate));
+
+ var outputValue = ((1 - coefficient) * value) +
+ (coefficient * this.lastValue);
+ output.samples[0] = outputValue;
+ this.lastValue = outputValue;
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Lag.prototype.toString = function() {
+ return 'Lag';
+};
+
+
+/*!
+ * @depends ../core/AudioletGroup.js
+ */
+
+/**
+ * A simple (and frankly shoddy) zero-lookahead limiter.
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Threshold
+ * - Attack
+ * - Release
+ *
+ * **Outputs**
+ *
+ * - Limited audio
+ *
+ * **Parameters**
+ *
+ * - threshold The limiter threshold. Linked to input 1.
+ * - attack The attack time in seconds. Linked to input 2.
+ * - release The release time in seconds. Linked to input 3.
+ *
+ * @constructor
+ * @extends AudioletGroup
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [threshold=0.95] The initial threshold.
+ * @param {Number} [attack=0.01] The initial attack time.
+ * @param {Number} [release=0.4] The initial release time.
+ */
+var Limiter = function(audiolet, threshold, attack, release) {
+ AudioletNode.call(this, audiolet, 4, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+
+ // Parameters
+ this.threshold = new AudioletParameter(this, 1, threshold || 0.95);
+ this.attack = new AudioletParameter(this, 2, attack || 0.01);
+ this.release = new AudioletParameter(this, 2, release || 0.4);
+
+ this.followers = [];
+};
+extend(Limiter, AudioletNode);
+
+/**
+ * Process samples
+ */
+Limiter.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var sampleRate = this.audiolet.device.sampleRate;
+
+ // Local processing variables
+ var attack = Math.pow(0.01, 1 / (this.attack.getValue() *
+ sampleRate));
+ var release = Math.pow(0.01, 1 / (this.release.getValue() *
+ sampleRate));
+
+ var threshold = this.threshold.getValue();
+
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ if (i >= this.followers.length) {
+ this.followers.push(0);
+ }
+
+ var follower = this.followers[i];
+
+ var value = input.samples[i];
+
+ // Calculate amplitude envelope
+ var absValue = Math.abs(value);
+ if (absValue > follower) {
+ follower = attack * (follower - absValue) + absValue;
+ }
+ else {
+ follower = release * (follower - absValue) + absValue;
+ }
+
+ var diff = follower - threshold;
+ if (diff > 0) {
+ output.samples[i] = value / (1 + diff);
+ }
+ else {
+ output.samples[i] = value;
+ }
+
+ this.followers[i] = follower;
+ }
+};
+
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Limiter.prototype.toString = function() {
+ return 'Limiter';
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Linear cross-fade between two signals
+ *
+ * **Inputs**
+ *
+ * - Audio 1
+ * - Audio 2
+ * - Fade Position
+ *
+ * **Outputs**
+ *
+ * - Mixed audio
+ *
+ * **Parameters**
+ *
+ * - position The fade position. Values between 0 (Audio 1 only) and 1 (Audio
+ * 2 only). Linked to input 2.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [position=0.5] The initial fade position.
+ */
+var LinearCrossFade = function(audiolet, position) {
+ AudioletNode.call(this, audiolet, 3, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+ this.position = new AudioletParameter(this, 2, position || 0.5);
+};
+extend(LinearCrossFade, AudioletNode);
+
+/**
+ * Process samples
+ */
+LinearCrossFade.prototype.generate = function() {
+ var inputA = this.inputs[0];
+ var inputB = this.inputs[1];
+ var output = this.outputs[0];
+
+ var position = this.position.getValue();
+
+ var gainA = 1 - position;
+ var gainB = position;
+
+ var numberOfChannels = output.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ var valueA = inputA.samples[i] || 0;
+ var valueB = inputB.samples[i] || 0;
+ output.samples[i] = valueA * gainA + valueB * gainB;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+LinearCrossFade.prototype.toString = function() {
+ return 'Linear Cross Fader';
+};
+
+/*!
+ * @depends BiquadFilter.js
+ */
+
+/**
+ * Low-pass filter
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Filter frequency
+ *
+ * **Outputs**
+ *
+ * - Filtered audio
+ *
+ * **Parameters**
+ *
+ * - frequency The filter frequency. Linked to input 1.
+ *
+ * @constructor
+ * @extends BiquadFilter
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} frequency The initial frequency.
+ */
+var LowPassFilter = function(audiolet, frequency) {
+ BiquadFilter.call(this, audiolet, frequency);
+};
+extend(LowPassFilter, BiquadFilter);
+
+/**
+ * Calculate the biquad filter coefficients using maths from
+ * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
+ *
+ * @param {Number} frequency The filter frequency.
+ */
+LowPassFilter.prototype.calculateCoefficients = function(frequency) {
+ var w0 = 2 * Math.PI * frequency /
+ this.audiolet.device.sampleRate;
+ var cosw0 = Math.cos(w0);
+ var sinw0 = Math.sin(w0);
+ var alpha = sinw0 / (2 / Math.sqrt(2));
+
+ this.b0 = (1 - cosw0) / 2;
+ this.b1 = 1 - cosw0;
+ this.b2 = this.b0;
+ this.a0 = 1 + alpha;
+ this.a1 = -2 * cosw0;
+ this.a2 = 1 - alpha;
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+LowPassFilter.prototype.toString = function() {
+ return 'Low Pass Filter';
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Position a single-channel input in stereo space
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Pan Position
+ *
+ * **Outputs**
+ *
+ * - Panned audio
+ *
+ * **Parameters**
+ *
+ * - pan The pan position. Values between 0 (hard-left) and 1 (hard-right).
+ * Linked to input 1.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [pan=0.5] The initial pan position.
+ */
+var Pan = function(audiolet, pan) {
+ AudioletNode.call(this, audiolet, 2, 1);
+ // Hardcode two output channels
+ this.setNumberOfOutputChannels(0, 2);
+ if (pan == null) {
+ var pan = 0.5;
+ }
+ this.pan = new AudioletParameter(this, 1, pan);
+};
+extend(Pan, AudioletNode);
+
+/**
+ * Process samples
+ */
+Pan.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var pan = this.pan.getValue();
+
+ var value = input.samples[0] || 0;
+ var scaledPan = pan * Math.PI / 2;
+ output.samples[0] = value * Math.cos(scaledPan);
+ output.samples[1] = value * Math.sin(scaledPan);
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Pan.prototype.toString = function() {
+ return 'Stereo Panner';
+};
+
+/*!
+ * @depends Envelope.js
+ */
+
+/**
+ * Simple attack-release envelope
+ *
+ * **Inputs**
+ *
+ * - Gate
+ *
+ * **Outputs**
+ *
+ * - Envelope
+ *
+ * **Parameters**
+ *
+ * - gate The gate controlling the envelope. Value changes from 0 -> 1
+ * trigger the envelope. Linked to input 0.
+ *
+ * @constructor
+ * @extends Envelope
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} gate The initial gate value.
+ * @param {Number} attack The attack time in seconds.
+ * @param {Number} release The release time in seconds.
+ * @param {Function} [onComplete] A function called after the release stage.
+ */
+var PercussiveEnvelope = function(audiolet, gate, attack, release,
+ onComplete) {
+ var levels = [0, 1, 0];
+ var times = [attack, release];
+ Envelope.call(this, audiolet, gate, levels, times, null, onComplete);
+
+ this.attack = this.times[0];
+ this.release = this.times[1];
+};
+extend(PercussiveEnvelope, Envelope);
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+PercussiveEnvelope.prototype.toString = function() {
+ return 'Percussive Envelope';
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Pulse wave oscillator.
+ *
+ * **Inputs**
+ *
+ * - Frequency
+ * - Pulse width
+ *
+ * **Outputs**
+ *
+ * - Waveform
+ *
+ * **Parameters**
+ *
+ * - frequency The oscillator frequency. Linked to input 0.
+ * - pulseWidth The pulse width. Linked to input 1.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [frequency=440] The initial frequency.
+ * @param {Number} [pulseWidth=0.5] The initial pulse width.
+ */
+var Pulse = function(audiolet, frequency, pulseWidth) {
+ AudioletNode.call(this, audiolet, 2, 1);
+ this.frequency = new AudioletParameter(this, 0, frequency || 440);
+ this.pulseWidth = new AudioletParameter(this, 1, pulseWidth || 0.5);
+ this.phase = 0;
+};
+extend(Pulse, AudioletNode);
+
+/**
+ * Process samples
+ */
+Pulse.prototype.generate = function() {
+ var pulseWidth = this.pulseWidth.getValue();
+ this.outputs[0].samples[0] = (this.phase < pulseWidth) ? 1 : -1;
+
+ var frequency = this.frequency.getValue();
+ var sampleRate = this.audiolet.device.sampleRate;
+ this.phase += frequency / sampleRate;
+ if (this.phase > 1) {
+ this.phase %= 1;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Pulse.prototype.toString = function() {
+ return 'Pulse';
+};
+
+
+/*!
+ * @depends ../core/AudioletNode.js
+ * @depends ../core/AudioletGroup.js
+ */
+
+/**
+ * Port of the Freeverb Schrodoer/Moorer reverb model. See
+ * https://ccrma.stanford.edu/~jos/pasp/Freeverb.html for a description of how
+ * each part works.
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Mix
+ * - Room Size
+ * - Damping
+ *
+ * **Outputs**
+ *
+ * - Reverberated Audio
+ *
+ * **Parameters**
+ *
+ * - mix The wet/dry mix. Values between 0 and 1. Linked to input 1.
+ * - roomSize The reverb's room size. Values between 0 and 1. Linked to input
+ * 2.
+ * - damping The amount of high-frequency damping. Values between 0 and 1.
+ * Linked to input 3.
+ *
+ * @constructor
+ * @extends AudioletGroup
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [mix=0.33] The initial wet/dry mix.
+ * @param {Number} [roomSize=0.5] The initial room size.
+ * @param {Number} [damping=0.5] The initial damping amount.
+ */
+var Reverb = function(audiolet, mix, roomSize, damping) {
+ AudioletNode.call(this, audiolet, 4, 1);
+
+ // Constants
+ this.initialMix = 0.33;
+ this.fixedGain = 0.015;
+ this.initialDamping = 0.5;
+ this.scaleDamping = 0.4;
+ this.initialRoomSize = 0.5;
+ this.scaleRoom = 0.28;
+ this.offsetRoom = 0.7;
+
+ // Parameters: for 44.1k or 48k
+ this.combTuning = [1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617];
+ this.allPassTuning = [556, 441, 341, 225];
+
+ // Controls
+ // Mix control
+ var mix = mix || this.initialMix;
+ this.mix = new AudioletParameter(this, 1, mix);
+
+ // Room size control
+ var roomSize = roomSize || this.initialRoomSize;
+ this.roomSize = new AudioletParameter(this, 2, roomSize);
+
+ // Damping control
+ var damping = damping || this.initialDamping;
+ this.damping = new AudioletParameter(this, 3, damping);
+
+ // Damped comb filters
+ this.combBuffers = [];
+ this.combIndices = [];
+ this.filterStores = [];
+
+ var numberOfCombs = this.combTuning.length;
+ for (var i = 0; i < numberOfCombs; i++) {
+ this.combBuffers.push(new Float32Array(this.combTuning[i]));
+ this.combIndices.push(0);
+ this.filterStores.push(0);
+ }
+
+ // All-pass filters
+ this.allPassBuffers = [];
+ this.allPassIndices = [];
+
+ var numberOfFilters = this.allPassTuning.length;
+ for (var i = 0; i < numberOfFilters; i++) {
+ this.allPassBuffers.push(new Float32Array(this.allPassTuning[i]));
+ this.allPassIndices.push(0);
+ }
+};
+extend(Reverb, AudioletNode);
+
+/**
+ * Process samples
+ */
+Reverb.prototype.generate = function() {
+ var mix = this.mix.getValue();
+ var roomSize = this.roomSize.getValue();
+ var damping = this.damping.getValue();
+
+ var numberOfCombs = this.combTuning.length;
+ var numberOfFilters = this.allPassTuning.length;
+
+ var value = this.inputs[0].samples[0] || 0;
+ var dryValue = value;
+
+ value *= this.fixedGain;
+ var gainedValue = value;
+
+ var damping = damping * this.scaleDamping;
+ var feedback = roomSize * this.scaleRoom + this.offsetRoom;
+
+ for (var i = 0; i < numberOfCombs; i++) {
+ var combIndex = this.combIndices[i];
+ var combBuffer = this.combBuffers[i];
+ var filterStore = this.filterStores[i];
+
+ var output = combBuffer[combIndex];
+ filterStore = (output * (1 - damping)) +
+ (filterStore * damping);
+ value += output;
+ combBuffer[combIndex] = gainedValue + feedback * filterStore;
+
+ combIndex += 1;
+ if (combIndex >= combBuffer.length) {
+ combIndex = 0;
+ }
+
+ this.combIndices[i] = combIndex;
+ this.filterStores[i] = filterStore;
+ }
+
+ for (var i = 0; i < numberOfFilters; i++) {
+ var allPassBuffer = this.allPassBuffers[i];
+ var allPassIndex = this.allPassIndices[i];
+
+ var input = value;
+ var bufferValue = allPassBuffer[allPassIndex];
+ value = -value + bufferValue;
+ allPassBuffer[allPassIndex] = input + (bufferValue * 0.5);
+
+ allPassIndex += 1;
+ if (allPassIndex >= allPassBuffer.length) {
+ allPassIndex = 0;
+ }
+
+ this.allPassIndices[i] = allPassIndex;
+ }
+
+ this.outputs[0].samples[0] = mix * value + (1 - mix) * dryValue;
+};
+
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Reverb.prototype.toString = function() {
+ return 'Reverb';
+};
+
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Saw wave oscillator using a lookup table
+ *
+ * **Inputs**
+ *
+ * - Frequency
+ *
+ * **Outputs**
+ *
+ * - Saw wave
+ *
+ * **Parameters**
+ *
+ * - frequency The frequency of the oscillator. Linked to input 0.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [frequency=440] Initial frequency.
+ */
+var Saw = function(audiolet, frequency) {
+ AudioletNode.call(this, audiolet, 1, 1);
+ this.frequency = new AudioletParameter(this, 0, frequency || 440);
+ this.phase = 0;
+};
+extend(Saw, AudioletNode);
+
+/**
+ * Process samples
+ */
+Saw.prototype.generate = function() {
+ var output = this.outputs[0];
+ var frequency = this.frequency.getValue();
+ var sampleRate = this.audiolet.device.sampleRate;
+
+ output.samples[0] = ((this.phase / 2 + 0.25) % 0.5 - 0.25) * 4;
+ this.phase += frequency / sampleRate;
+
+ if (this.phase > 1) {
+ this.phase %= 1;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Saw.prototype.toString = function() {
+ return 'Saw';
+};
+
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * A soft-clipper, which distorts at values over +-0.5.
+ *
+ * **Inputs**
+ *
+ * - Audio
+ *
+ * **Outputs**
+ *
+ * - Clipped audio
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ */
+
+var SoftClip = function(audiolet) {
+ AudioletNode.call(this, audiolet, 1, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+};
+extend(SoftClip, AudioletNode);
+
+/**
+ * Process samples
+ */
+SoftClip.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ var value = input.samples[i];
+ if (value > 0.5 || value < -0.5) {
+ output.samples[i] = (Math.abs(value) - 0.25) / value;
+ }
+ else {
+ output.samples[i] = value;
+ }
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+SoftClip.prototype.toString = function() {
+ return ('SoftClip');
+};
+
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Square wave oscillator
+ *
+ * **Inputs**
+ *
+ * - Frequency
+ *
+ * **Outputs**
+ *
+ * - Square wave
+ *
+ * **Parameters**
+ *
+ * - frequency The frequency of the oscillator. Linked to input 0.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [frequency=440] Initial frequency.
+ */
+var Square = function(audiolet, frequency) {
+ AudioletNode.call(this, audiolet, 1, 1);
+ this.frequency = new AudioletParameter(this, 0, frequency || 440);
+ this.phase = 0;
+};
+extend(Square, AudioletNode);
+
+/**
+ * Process samples
+ */
+Square.prototype.generate = function() {
+ var output = this.outputs[0];
+
+ var frequency = this.frequency.getValue();
+ var sampleRate = this.audiolet.device.sampleRate;
+
+ output.samples[0] = this.phase > 0.5 ? 1 : -1;
+
+ this.phase += frequency / sampleRate;
+ if (this.phase > 1) {
+ this.phase %= 1;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Square.prototype.toString = function() {
+ return 'Square';
+};
+
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Triangle wave oscillator using a lookup table
+ *
+ * **Inputs**
+ *
+ * - Frequency
+ *
+ * **Outputs**
+ *
+ * - Triangle wave
+ *
+ * **Parameters**
+ *
+ * - frequency The frequency of the oscillator. Linked to input 0.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [frequency=440] Initial frequency.
+ */
+var Triangle = function(audiolet, frequency) {
+ AudioletNode.call(this, audiolet, 1, 1);
+ this.frequency = new AudioletParameter(this, 0, frequency || 440);
+ this.phase = 0;
+};
+extend(Triangle, AudioletNode);
+
+/**
+ * Process samples
+ */
+Triangle.prototype.generate = function() {
+ var output = this.outputs[0];
+
+ var frequency = this.frequency.getValue();
+ var sampleRate = this.audiolet.device.sampleRate;
+
+ output.samples[0] = 1 - 4 * Math.abs((this.phase + 0.25) % 1 - 0.5);
+
+ this.phase += frequency / sampleRate;
+ if (this.phase > 1) {
+ this.phase %= 1;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Triangle.prototype.toString = function() {
+ return 'Triangle';
+};
+
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Simple trigger which allows you to set a single sample to be 1 and then
+ * resets itself.
+ *
+ * **Outputs**
+ *
+ * - Triggers
+ *
+ * **Parameters**
+ *
+ * - trigger Set to 1 to fire a trigger.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [trigger=0] The initial trigger state.
+ */
+var TriggerControl = function(audiolet, trigger) {
+ AudioletNode.call(this, audiolet, 0, 1);
+ this.trigger = new AudioletParameter(this, null, trigger || 0);
+};
+extend(TriggerControl, AudioletNode);
+
+/**
+ * Process samples
+ */
+TriggerControl.prototype.generate = function() {
+ if (this.trigger.getValue() > 0) {
+ this.outputs[0].samples[0] = 1;
+ this.trigger.setValue(0);
+ }
+ else {
+ this.outputs[0].samples[0] = 0;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+TriggerControl.prototype.toString = function() {
+ return 'Trigger Control';
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Upmix an input to a constant number of output channels
+ *
+ * **Inputs**
+ *
+ * - Audio
+ *
+ * **Outputs**
+ *
+ * - Upmixed audio
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} outputChannels The number of output channels.
+ */
+var UpMixer = function(audiolet, outputChannels) {
+ AudioletNode.call(this, audiolet, 1, 1);
+ this.outputs[0].numberOfChannels = outputChannels;
+};
+extend(UpMixer, AudioletNode);
+
+/**
+ * Process samples
+ */
+UpMixer.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var numberOfInputChannels = input.samples.length;
+ var numberOfOutputChannels = output.samples.length;
+
+ if (numberOfInputChannels == numberOfOutputChannels) {
+ output.samples = input.samples;
+ }
+ else {
+ for (var i = 0; i < numberOfOutputChannels; i++) {
+ output.samples[i] = input.samples[i % numberOfInputChannels];
+ }
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+UpMixer.prototype.toString = function() {
+ return 'UpMixer';
+};
+
+
+var WebKitBufferPlayer = function(audiolet, onComplete) {
+ AudioletNode.call(this, audiolet, 0, 1);
+ this.onComplete = onComplete;
+ this.isWebKit = this.audiolet.device.sink instanceof Sink.sinks.webkit;
+ this.ready = false;
+
+ // Until we are loaded, output no channels.
+ this.setNumberOfOutputChannels(0, 0);
+
+ if (!this.isWebKit) {
+ return;
+ }
+
+ this.context = this.audiolet.device.sink._context;
+ this.jsNode = null;
+ this.source = null;
+
+ this.ready = false;
+ this.loaded = false;
+
+ this.buffers = [];
+ this.readPosition = 0;
+
+ this.endTime = null;
+};
+extend(WebKitBufferPlayer, AudioletNode);
+
+WebKitBufferPlayer.prototype.load = function(url, onLoad, onError) {
+ if (!this.isWebKit) {
+ return;
+ }
+
+ this.stop();
+
+ // Request the new file
+ this.xhr = new XMLHttpRequest();
+ this.xhr.open("GET", url, true);
+ this.xhr.responseType = "arraybuffer";
+ this.xhr.onload = this.onLoad.bind(this, onLoad, onError);
+ this.xhr.onerror = onError;
+ this.xhr.send();
+};
+
+WebKitBufferPlayer.prototype.stop = function() {
+ this.ready = false;
+ this.loaded = false;
+
+ this.buffers = [];
+ this.readPosition = 0;
+ this.endTime = null;
+
+ this.setNumberOfOutputChannels(0);
+
+ this.disconnectWebKitNodes();
+};
+
+WebKitBufferPlayer.prototype.disconnectWebKitNodes = function() {
+ if (this.source && this.jsNode) {
+ this.source.disconnect(this.jsNode);
+ this.jsNode.disconnect(this.context.destination);
+ this.source = null;
+ this.jsNode = null;
+ }
+};
+
+WebKitBufferPlayer.prototype.onLoad = function(onLoad, onError) {
+ // Load the buffer into memory for decoding
+// this.fileBuffer = this.context.createBuffer(this.xhr.response, false);
+ this.context.decodeAudioData(this.xhr.response, function(buffer) {
+ this.onDecode(buffer);
+ onLoad();
+ }.bind(this), onError);
+};
+
+WebKitBufferPlayer.prototype.onDecode = function(buffer) {
+ this.fileBuffer = buffer;
+
+ // Create the WebKit buffer source for playback
+ this.source = this.context.createBufferSource();
+ this.source.buffer = this.fileBuffer;
+
+ // Make sure we are outputting the right number of channels on Audiolet's
+ // side
+ var numberOfChannels = this.fileBuffer.numberOfChannels;
+ this.setNumberOfOutputChannels(0, numberOfChannels);
+
+ // Create the JavaScript node for reading the data into Audiolet
+ this.jsNode = this.context.createJavaScriptNode(4096, numberOfChannels, 0);
+ this.jsNode.onaudioprocess = this.onData.bind(this);
+
+ // Connect it all up
+ this.source.connect(this.jsNode);
+ this.jsNode.connect(this.context.destination);
+ this.source.noteOn(0);
+ this.endTime = this.context.currentTime + this.fileBuffer.duration;
+
+ this.loaded = true;
+};
+
+WebKitBufferPlayer.prototype.onData = function(event) {
+ if (this.loaded) {
+ this.ready = true;
+ }
+
+ var numberOfChannels = event.inputBuffer.numberOfChannels;
+
+ for (var i=0; i<numberOfChannels; i++) {
+ this.buffers[i] = event.inputBuffer.getChannelData(i);
+ this.readPosition = 0;
+ }
+};
+
+WebKitBufferPlayer.prototype.generate = function() {
+ if (!this.ready) {
+ return;
+ }
+
+ var output = this.outputs[0];
+
+ var numberOfChannels = output.samples.length;
+ for (var i=0; i<numberOfChannels; i++) {
+ output.samples[i] = this.buffers[i][this.readPosition];
+ }
+ this.readPosition += 1;
+
+ if (this.context.currentTime > this.endTime) {
+ this.stop();
+ this.onComplete();
+ }
+};
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * A white noise source
+ *
+ * **Outputs**
+ *
+ * - White noise
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ */
+var WhiteNoise = function(audiolet) {
+ AudioletNode.call(this, audiolet, 0, 1);
+};
+extend(WhiteNoise, AudioletNode);
+
+/**
+ * Process samples
+ */
+WhiteNoise.prototype.generate = function() {
+ this.outputs[0].samples[0] = Math.random() * 2 - 1;
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+WhiteNoise.prototype.toString = function() {
+ return 'White Noise';
+};
+
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Add values
+ *
+ * **Inputs**
+ *
+ * - Audio 1
+ * - Audio 2
+ *
+ * **Outputs**
+ *
+ * - Summed audio
+ *
+ * **Parameters**
+ *
+ * - value The value to add. Linked to input 1.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [value=0] The initial value to add.
+ */
+var Add = function(audiolet, value) {
+ AudioletNode.call(this, audiolet, 2, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+ this.value = new AudioletParameter(this, 1, value || 0);
+};
+extend(Add, AudioletNode);
+
+/**
+ * Process samples
+ */
+Add.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var value = this.value.getValue();
+
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ output.samples[i] = input.samples[i] + value;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Add.prototype.toString = function() {
+ return 'Add';
+};
+
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Divide values
+ *
+ * **Inputs**
+ *
+ * - Audio 1
+ * - Audio 2
+ *
+ * **Outputs**
+ *
+ * - Divided audio
+ *
+ * **Parameters**
+ *
+ * - value The value to divide by. Linked to input 1.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [value=1] The initial value to divide by.
+ */
+var Divide = function(audiolet, value) {
+ AudioletNode.call(this, audiolet, 2, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+ this.value = new AudioletParameter(this, 1, value || 1);
+};
+extend(Divide, AudioletNode);
+
+/**
+ * Process samples
+ */
+Divide.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var value = this.value.getValue();
+
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ output.samples[i] = input.samples[i] / value;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Divide.prototype.toString = function() {
+ return 'Divide';
+};
+
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Modulo values
+ *
+ * **Inputs**
+ *
+ * - Audio 1
+ * - Audio 2
+ *
+ * **Outputs**
+ *
+ * - Moduloed audio
+ *
+ * **Parameters**
+ *
+ * - value The value to modulo by. Linked to input 1.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [value=1] The initial value to modulo by.
+ */
+var Modulo = function(audiolet, value) {
+ AudioletNode.call(this, audiolet, 2, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+ this.value = new AudioletParameter(this, 1, value || 1);
+};
+extend(Modulo, AudioletNode);
+
+/**
+ * Process samples
+ */
+Modulo.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var value = this.value.getValue();
+
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ output.samples[i] = input.samples[i] % value;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Modulo.prototype.toString = function() {
+ return 'Modulo';
+};
+
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/*
+ * Multiply and add values
+ *
+ * **Inputs**
+ *
+ * - Audio
+ * - Multiply audio
+ * - Add audio
+ *
+ * **Outputs**
+ *
+ * - MulAdded audio
+ *
+ * **Parameters**
+ *
+ * - mul The value to multiply by. Linked to input 1.
+ * - add The value to add. Linked to input 2.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [mul=1] The initial value to multiply by.
+ * @param {Number} [add=0] The initial value to add.
+ */
+var MulAdd = function(audiolet, mul, add) {
+ AudioletNode.call(this, audiolet, 3, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+ this.mul = new AudioletParameter(this, 1, mul || 1);
+ this.add = new AudioletParameter(this, 2, add || 0);
+};
+extend(MulAdd, AudioletNode);
+
+/**
+ * Process samples
+ */
+MulAdd.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var mul = this.mul.getValue();
+ var add = this.add.getValue();
+
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ output.samples[i] = input.samples[i] * mul + add;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+MulAdd.prototype.toString = function() {
+ return 'Multiplier/Adder';
+};
+
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Reciprocal (1/x) of values
+ *
+ * **Inputs**
+ *
+ * - Audio
+ *
+ * **Outputs**
+ *
+ * - Reciprocal audio
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ */
+var Reciprocal = function(audiolet) {
+ AudioletNode.call(this, audiolet, 1, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+};
+extend(Reciprocal, AudioletNode);
+
+/**
+ * Process samples
+ */
+Reciprocal.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ output.samples[i] = 1 / input.samples[i];
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Reciprocal.prototype.toString = function() {
+ return 'Reciprocal';
+};
+
+
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Subtract values
+ *
+ * **Inputs**
+ *
+ * - Audio 1
+ * - Audio 2
+ *
+ * **Outputs**
+ *
+ * - Subtracted audio
+ *
+ * **Parameters**
+ *
+ * - value The value to subtract. Linked to input 1.
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ * @param {Number} [value=0] The initial value to subtract.
+ */
+var Subtract = function(audiolet, value) {
+ AudioletNode.call(this, audiolet, 2, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+ this.value = new AudioletParameter(this, 1, value || 0);
+};
+extend(Subtract, AudioletNode);
+
+/**
+ * Process samples
+ */
+Subtract.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var value = this.value.getValue();
+
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ output.samples[i] = input.samples[i] - value;
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Subtract.prototype.toString = function() {
+ return 'Subtract';
+};
+
+
+/**
+ * @depends ../core/AudioletNode.js
+ */
+
+/**
+ * Hyperbolic tangent of values. Works nicely as a distortion function.
+ *
+ * **Inputs**
+ *
+ * - Audio
+ *
+ * **Outputs**
+ *
+ * - Tanh audio
+ *
+ * @constructor
+ * @extends AudioletNode
+ * @param {Audiolet} audiolet The audiolet object.
+ */
+
+var Tanh = function(audiolet) {
+ AudioletNode.call(this, audiolet, 1, 1);
+ this.linkNumberOfOutputChannels(0, 0);
+};
+extend(Tanh, AudioletNode);
+
+/**
+ * Process samples
+ */
+Tanh.prototype.generate = function() {
+ var input = this.inputs[0];
+ var output = this.outputs[0];
+
+ var numberOfChannels = input.samples.length;
+ for (var i = 0; i < numberOfChannels; i++) {
+ var value = input.samples[i];
+ output.samples[i] = (Math.exp(value) - Math.exp(-value)) /
+ (Math.exp(value) + Math.exp(-value));
+ }
+};
+
+/**
+ * toString
+ *
+ * @return {String} String representation.
+ */
+Tanh.prototype.toString = function() {
+ return ('Tanh');
+};
+
+
+/**
+ * A generic pattern. Patterns are simple classes which return the next value
+ * in a sequence when the next function is called. Patterns can be embedded
+ * inside other patterns to produce complex sequences of values. When a
+ * pattern is finished its next function returns null.
+ *
+ * @constructor
+ */
+var Pattern = function() {
+};
+
+/**
+ * Default next function.
+ *
+ * @return {null} Null.
+ */
+Pattern.prototype.next = function() {
+ return null;
+};
+
+/**
+ * Return the current value of an item contained in a pattern.
+ *
+ * @param {Pattern|Object} The item.
+ * @return {Object} The value of the item.
+ */
+Pattern.prototype.valueOf = function(item) {
+ if (item instanceof Pattern) {
+ return (item.next());
+ }
+ else {
+ return (item);
+ }
+};
+
+/**
+ * Default reset function.
+ */
+Pattern.prototype.reset = function() {
+};
+
+
+/*!
+ * @depends Pattern.js
+ */
+
+/**
+ * Arithmetic sequence. Adds a value to a running total on each next call.
+ *
+ * @constructor
+ * @extends Pattern
+ * @param {Number} start Starting value.
+ * @param {Pattern|Number} step Value to add.
+ * @param {Number} repeats Number of values to generate.
+ */
+var PArithmetic = function(start, step, repeats) {
+ Pattern.call(this);
+ this.start = start;
+ this.value = start;
+ this.step = step;
+ this.repeats = repeats;
+ this.position = 0;
+};
+extend(PArithmetic, Pattern);
+
+/**
+ * Generate the next value in the pattern.
+ *
+ * @return {Number} The next value.
+ */
+PArithmetic.prototype.next = function() {
+ var returnValue;
+ if (this.position == 0) {
+ returnValue = this.value;
+ this.position += 1;
+ }
+ else if (this.position < this.repeats) {
+ var step = this.valueOf(this.step);
+ if (step != null) {
+ this.value += step;
+ returnValue = this.value;
+ this.position += 1;
+ }
+ else {
+ returnValue = null;
+ }
+ }
+ else {
+ returnValue = null;
+ }
+ return (returnValue);
+};
+
+/**
+ * Reset the pattern
+ */
+PArithmetic.prototype.reset = function() {
+ this.value = this.start;
+ this.position = 0;
+ if (this.step instanceof Pattern) {
+ this.step.reset();
+ }
+};
+
+/**
+ * Supercollider alias
+ */
+var Pseries = PArithmetic;
+
+
+/*!
+ * @depends Pattern.js
+ */
+
+/**
+ * Choose a random value from an array.
+ *
+ * @constructor
+ * @extends Pattern
+ * @param {Object[]} list Array of items to choose from.
+ * @param {Number} [repeats=1] Number of values to generate.
+ */
+var PChoose = function(list, repeats) {
+ Pattern.call(this);
+ this.list = list;
+ this.repeats = repeats || 1;
+ this.position = 0;
+};
+extend(PChoose, Pattern);
+
+/**
+ * Generate the next value in the pattern.
+ *
+ * @return {Number} The next value.
+ */
+PChoose.prototype.next = function() {
+ var returnValue;
+ if (this.position < this.repeats) {
+ var index = Math.floor(Math.random() * this.list.length);
+ var item = this.list[index];
+ var value = this.valueOf(item);
+ if (value != null) {
+ if (!(item instanceof Pattern)) {
+ this.position += 1;
+ }
+ returnValue = value;
+ }
+ else {
+ if (item instanceof Pattern) {
+ item.reset();
+ }
+ this.position += 1;
+ returnValue = this.next();
+ }
+ }
+ else {
+ returnValue = null;
+ }
+ return (returnValue);
+};
+
+/**
+ * Reset the pattern
+ */
+PChoose.prototype.reset = function() {
+ this.position = 0;
+ for (var i = 0; i < this.list.length; i++) {
+ var item = this.list[i];
+ if (item instanceof Pattern) {
+ item.reset();
+ }
+ }
+};
+
+/**
+ * Supercollider alias
+ */
+var Prand = PChoose;
+
+
+/*!
+ * @depends Pattern.js
+ */
+
+
+/**
+ * Geometric sequence. Multiplies a running total by a value on each next
+ * call.
+ *
+ * @constructor
+ * @extends Pattern
+ * @param {Number} start Starting value.
+ * @param {Pattern|Number} step Value to multiply by.
+ * @param {Number} repeats Number of values to generate.
+ */
+var PGeometric = function(start, step, repeats) {
+ Pattern.call(this);
+ this.start = start;
+ this.value = start;
+ this.step = step;
+ this.repeats = repeats;
+ this.position = 0;
+};
+extend(PGeometric, Pattern);
+
+/**
+ * Generate the next value in the pattern.
+ *
+ * @return {Number} The next value.
+ */
+PGeometric.prototype.next = function() {
+ var returnValue;
+ if (this.position == 0) {
+ returnValue = this.value;
+ this.position += 1;
+ }
+ else if (this.position < this.repeats) {
+ var step = this.valueOf(this.step);
+ if (step != null) {
+ this.value *= step;
+ returnValue = this.value;
+ this.position += 1;
+ }
+ else {
+ returnValue = null;
+ }
+ }
+ else {
+ returnValue = null;
+ }
+ return (returnValue);
+};
+
+/**
+ * Reset the pattern
+ */
+PGeometric.prototype.reset = function() {
+ this.value = this.start;
+ this.position = 0;
+ if (this.step instanceof Pattern) {
+ this.step.reset();
+ }
+};
+
+/**
+ * Supercollider alias
+ */
+var Pgeom = PGeometric;
+
+
+/*!
+ * @depends Pattern.js
+ */
+
+/**
+ * Proxy pattern. Holds a pattern which can safely be replaced by a different
+ * pattern while it is running.
+ *
+ *
+ * @constructor
+ * @extends Pattern
+ * @param {Pattern} pattern The initial pattern.
+ */
+var PProxy = function(pattern) {
+ Pattern.call(this);
+ if (pattern) {
+ this.pattern = pattern;
+ }
+};
+extend(PProxy, Pattern);
+
+/**
+ * Generate the next value in the pattern.
+ *
+ * @return {Number} The next value.
+ */
+PProxy.prototype.next = function() {
+ var returnValue;
+ if (this.pattern) {
+ var returnValue = this.pattern.next();
+ }
+ else {
+ returnValue = null;
+ }
+ return returnValue;
+};
+
+/**
+ * Alias
+ */
+var Pp = PProxy;
+
+
+/*!
+ * @depends Pattern.js
+ */
+
+/**
+ * Sequence of random numbers.
+ *
+ * @constructor
+ * @extends Pattern
+ * @param {Number|Pattern} low Lowest possible value.
+ * @param {Number|Pattern} high Highest possible value.
+ * @param {Number} repeats Number of values to generate.
+ */
+var PRandom = function(low, high, repeats) {
+ Pattern.call(this);
+ this.low = low;
+ this.high = high;
+ this.repeats = repeats;
+ this.position = 0;
+};
+extend(PRandom, Pattern);
+
+/**
+ * Generate the next value in the pattern.
+ *
+ * @return {Number} The next value.
+ */
+PRandom.prototype.next = function() {
+ var returnValue;
+ if (this.position < this.repeats) {
+ var low = this.valueOf(this.low);
+ var high = this.valueOf(this.high);
+ if (low != null && high != null) {
+ returnValue = low + Math.random() * (high - low);
+ this.position += 1;
+ }
+ else {
+ returnValue = null;
+ }
+ }
+ else {
+ returnValue = null;
+ }
+ return (returnValue);
+};
+
+/**
+ * Reset the pattern
+ */
+PRandom.prototype.reset = function() {
+ this.position = 0;
+};
+
+/**
+ * Supercollider alias
+ */
+var Pwhite = PRandom;
+
+
+/*!
+ * @depends Pattern.js
+ */
+
+/**
+ * Iterate through a list of values.
+ *
+ * @constructor
+ * @extends Pattern
+ * @param {Object[]} list Array of values.
+ * @param {Number} [repeats=1] Number of times to loop through the array.
+ * @param {Number} [offset=0] Index to start from.
+ */
+var PSequence = function(list, repeats, offset) {
+ Pattern.call(this);
+ this.list = list;
+ this.repeats = repeats || 1;
+ this.position = 0;
+ this.offset = offset || 0;
+};
+extend(PSequence, Pattern);
+
+/**
+ * Generate the next value in the pattern.
+ *
+ * @return {Number} The next value.
+ */
+PSequence.prototype.next = function() {
+ var returnValue;
+ if (this.position < this.repeats * this.list.length) {
+ var index = (this.position + this.offset) % this.list.length;
+ var item = this.list[index];
+ var value = this.valueOf(item);
+ if (value != null) {
+ if (!(item instanceof Pattern)) {
+ this.position += 1;
+ }
+ returnValue = value;
+ }
+ else {
+ if (item instanceof Pattern) {
+ item.reset();
+ }
+ this.position += 1;
+ returnValue = this.next();
+ }
+ }
+ else {
+ returnValue = null;
+ }
+ return (returnValue);
+};
+
+/**
+ * Reset the pattern
+ */
+PSequence.prototype.reset = function() {
+ this.position = 0;
+ for (var i = 0; i < this.list.length; i++) {
+ var item = this.list[i];
+ if (item instanceof Pattern) {
+ item.reset();
+ }
+ }
+};
+
+/**
+ * Supercollider alias
+ */
+var Pseq = PSequence;
+
+
+/*!
+ * @depends Pattern.js
+ */
+
+/**
+ * Iterate through a list of values.
+ *
+ * @constructor
+ * @extends Pattern
+ * @param {Object[]} list Array of values.
+ * @param {Number} [repeats=1] Number of values to generate.
+ * @param {Number} [offset=0] Index to start from.
+ */
+var PSeries = function(list, repeats, offset) {
+ Pattern.call(this);
+ this.list = list;
+ this.repeats = repeats || 1;
+ this.position = 0;
+ this.offset = offset || 0;
+};
+extend(PSeries, Pattern);
+
+/**
+ * Generate the next value in the pattern.
+ *
+ * @return {Number} The next value.
+ */
+PSeries.prototype.next = function() {
+ var returnValue;
+ if (this.position < this.repeats) {
+ var index = (this.position + this.offset) % this.list.length;
+ var item = this.list[index];
+ var value = this.valueOf(item);
+ if (value != null) {
+ if (!(item instanceof Pattern)) {
+ this.position += 1;
+ }
+ returnValue = value;
+ }
+ else {
+ if (item instanceof Pattern) {
+ item.reset();
+ }
+ this.position += 1;
+ returnValue = this.next();
+ }
+ }
+ else {
+ returnValue = null;
+ }
+ return (returnValue);
+};
+
+/**
+ * Reset the pattern
+ */
+PSeries.prototype.reset = function() {
+ this.position = 0;
+ for (var i = 0; i < this.list.length; i++) {
+ var item = this.list[i];
+ if (item instanceof Pattern) {
+ item.reset();
+ }
+ }
+};
+
+/**
+ * Supercollider alias
+ */
+var Pser = PSeries;
+
+
+/*!
+ * @depends Pattern.js
+ */
+
+/**
+ * Reorder an array, then iterate through it's values.
+ *
+ * @constructor
+ * @extends Pattern
+ * @param {Object[]} list Array of values.
+ * @param {Number} repeats Number of times to loop through the array.
+ */
+var PShuffle = function(list, repeats) {
+ Pattern.call(this);
+ this.list = [];
+ // Shuffle values into new list
+ while (list.length) {
+ var index = Math.floor(Math.random() * list.length);
+ var value = list.splice(index, 1);
+ this.list.push(value);
+ }
+ this.repeats = repeats;
+ this.position = 0;
+};
+extend(PShuffle, Pattern);
+
+/**
+ * Generate the next value in the pattern.
+ *
+ * @return {Number} The next value.
+ */
+PShuffle.prototype.next = function() {
+ var returnValue;
+ if (this.position < this.repeats * this.list.length) {
+ var index = (this.position + this.offset) % this.list.length;
+ var item = this.list[index];
+ var value = this.valueOf(item);
+ if (value != null) {
+ if (!(item instanceof Pattern)) {
+ this.position += 1;
+ }
+ returnValue = value;
+ }
+ else {
+ if (item instanceof Pattern) {
+ item.reset();
+ }
+ this.position += 1;
+ returnValue = this.next();
+ }
+ }
+ else {
+ returnValue = null;
+ }
+ return (returnValue);
+};
+
+/**
+ * Supercollider alias
+ */
+var Pshuffle = PShuffle;
+
+
+/**
+ * Representation of a generic musical scale. Can be subclassed to produce
+ * specific scales.
+ *
+ * @constructor
+ * @param {Number[]} degrees Array of integer degrees.
+ * @param {Tuning} [tuning] The scale's tuning. Defaults to 12-tone ET.
+ */
+var Scale = function(degrees, tuning) {
+ this.degrees = degrees;
+ this.tuning = tuning || new EqualTemperamentTuning(12);
+};
+
+/**
+ * Get the frequency of a note in the scale.
+ *
+ * @constructor
+ * @param {Number} degree The note's degree.
+ * @param {Number} rootFrequency The root frequency of the scale.
+ * @param {Number} octave The octave of the note.
+ * @return {Number} The frequency of the note in hz.
+ */
+Scale.prototype.getFrequency = function(degree, rootFrequency, octave) {
+ var frequency = rootFrequency;
+ octave += Math.floor(degree / this.degrees.length);
+ degree %= this.degrees.length;
+ frequency *= Math.pow(this.tuning.octaveRatio, octave);
+ frequency *= this.tuning.ratios[this.degrees[degree]];
+ return frequency;
+};
+
+/*!
+ * @depends Scale.js
+ */
+
+/**
+ * Major scale.
+ *
+ * @constructor
+ * @extends Scale
+ */
+var MajorScale = function() {
+ Scale.call(this, [0, 2, 4, 5, 7, 9, 11]);
+};
+extend(MajorScale, Scale);
+
+/*!
+ * @depends Scale.js
+ */
+
+/**
+ * Minor scale.
+ *
+ * @constructor
+ * @extends Scale
+ */
+
+var MinorScale = function() {
+ Scale.call(this, [0, 2, 3, 5, 7, 8, 10]);
+};
+extend(MinorScale, Scale);
+
+/**
+ * Representation of a generic musical tuning. Can be subclassed to produce
+ * specific tunings.
+ *
+ * @constructor
+ * @param {Number[]} semitones Array of semitone values for the tuning.
+ * @param {Number} [octaveRatio=2] Frequency ratio for notes an octave apart.
+ */
+
+var Tuning = function(semitones, octaveRatio) {
+ this.semitones = semitones;
+ this.octaveRatio = octaveRatio || 2;
+ this.ratios = [];
+ var tuningLength = this.semitones.length;
+ for (var i = 0; i < tuningLength; i++) {
+ this.ratios.push(Math.pow(2, this.semitones[i] / tuningLength));
+ }
+};
+
+/*!
+ * @depends Tuning.js
+ */
+
+/**
+ * Equal temperament tuning.
+ *
+ * @constructor
+ * @extends Tuning
+ * @param {Number} pitchesPerOctave The number of notes in each octave.
+ */
+var EqualTemperamentTuning = function(pitchesPerOctave) {
+ var semitones = [];
+ for (var i = 0; i < pitchesPerOctave; i++) {
+ semitones.push(i);
+ }
+ Tuning.call(this, semitones, 2);
+};
+extend(EqualTemperamentTuning, Tuning);
+
+var Sink = this.Sink = function (global) {
+
+/**
+ * Creates a Sink according to specified parameters, if possible.
+ *
+ * @class
+ *
+ * @arg =!readFn
+ * @arg =!channelCount
+ * @arg =!bufferSize
+ * @arg =!sampleRate
+ *
+ * @param {Function} readFn A callback to handle the buffer fills.
+ * @param {Number} channelCount Channel count.
+ * @param {Number} bufferSize (Optional) Specifies a pre-buffer size to control the amount of latency.
+ * @param {Number} sampleRate Sample rate (ms).
+ * @param {Number} default=0 writePosition Write position of the sink, as in how many samples have been written per channel.
+ * @param {String} default=async writeMode The default mode of writing to the sink.
+ * @param {String} default=interleaved channelMode The mode in which the sink asks the sample buffers to be channeled in.
+ * @param {Number} default=0 previousHit The previous time of a callback.
+ * @param {Buffer} default=null ringBuffer The ring buffer array of the sink. If null, ring buffering will not be applied.
+ * @param {Number} default=0 ringOffset The current position of the ring buffer.
+*/
+function Sink (readFn, channelCount, bufferSize, sampleRate) {
+ var sinks = Sink.sinks.list,
+ i;
+ for (i=0; i<sinks.length; i++) {
+ if (sinks[i].enabled) {
+ try {
+ return new sinks[i](readFn, channelCount, bufferSize, sampleRate);
+ } catch(e1){}
+ }
+ }
+
+ throw Sink.Error(0x02);
+}
+
+function SinkClass () {
+}
+
+Sink.SinkClass = SinkClass;
+
+SinkClass.prototype = Sink.prototype = {
+ sampleRate: 44100,
+ channelCount: 2,
+ bufferSize: 4096,
+
+ writePosition: 0,
+ previousHit: 0,
+ ringOffset: 0,
+
+ channelMode: 'interleaved',
+ isReady: false,
+
+/**
+ * Does the initialization of the sink.
+ * @method Sink
+*/
+ start: function (readFn, channelCount, bufferSize, sampleRate) {
+ this.channelCount = isNaN(channelCount) || channelCount === null ? this.channelCount: channelCount;
+ this.bufferSize = isNaN(bufferSize) || bufferSize === null ? this.bufferSize : bufferSize;
+ this.sampleRate = isNaN(sampleRate) || sampleRate === null ? this.sampleRate : sampleRate;
+ this.readFn = readFn;
+ this.activeRecordings = [];
+ this.previousHit = +new Date();
+ Sink.EventEmitter.call(this);
+ Sink.emit('init', [this].concat([].slice.call(arguments)));
+ },
+/**
+ * The method which will handle all the different types of processing applied on a callback.
+ * @method Sink
+*/
+ process: function (soundData, channelCount) {
+ this.emit('preprocess', arguments);
+
+ if (this.ringBuffer) {
+ (this.channelMode === 'interleaved' ? this.ringSpin : this.ringSpinInterleaved).apply(this, arguments);
+ }
+
+ if (this.channelMode === 'interleaved') {
+ this.emit('audioprocess', arguments);
+
+ if (this.readFn) {
+ this.readFn.apply(this, arguments);
+ }
+ } else {
+ var soundDataSplit = Sink.deinterleave(soundData, this.channelCount),
+ args = [soundDataSplit].concat([].slice.call(arguments, 1));
+ this.emit('audioprocess', args);
+
+ if (this.readFn) {
+ this.readFn.apply(this, args);
+ }
+
+ Sink.interleave(soundDataSplit, this.channelCount, soundData);
+ }
+ this.emit('postprocess', arguments);
+ this.previousHit = +new Date();
+ this.writePosition += soundData.length / channelCount;
+ },
+/**
+ * Get the current output position, defaults to writePosition - bufferSize.
+ *
+ * @method Sink
+ *
+ * @return {Number} The position of the write head, in samples, per channel.
+*/
+ getPlaybackTime: function () {
+ return this.writePosition - this.bufferSize;
+ },
+/**
+ * Internal method to send the ready signal if not ready yet.
+ * @method Sink
+*/
+ ready: function () {
+ if (this.isReady) return;
+
+ this.isReady = true;
+ this.emit('ready', []);
+ }
+};
+
+/**
+ * The container for all the available sinks. Also a decorator function for creating a new Sink class and binding it.
+ *
+ * @method Sink
+ * @static
+ *
+ * @arg {String} type The name / type of the Sink.
+ * @arg {Function} constructor The constructor function for the Sink.
+ * @arg {Object} prototype The prototype of the Sink. (optional)
+ * @arg {Boolean} disabled Whether the Sink should be disabled at first.
+*/
+
+function sinks (type, constructor, prototype, disabled, priority) {
+ prototype = prototype || constructor.prototype;
+ constructor.prototype = new Sink.SinkClass();
+ constructor.prototype.type = type;
+ constructor.enabled = !disabled;
+
+ var k;
+ for (k in prototype) {
+ if (prototype.hasOwnProperty(k)) {
+ constructor.prototype[k] = prototype[k];
+ }
+ }
+
+ sinks[type] = constructor;
+ sinks.list[priority ? 'unshift' : 'push'](constructor);
+}
+
+Sink.sinks = Sink.devices = sinks;
+Sink.sinks.list = [];
+
+Sink.singleton = function () {
+ var sink = Sink.apply(null, arguments);
+
+ Sink.singleton = function () {
+ return sink;
+ };
+
+ return sink;
+};
+
+global.Sink = Sink;
+
+return Sink;
+
+}(function (){ return this; }());
+void function (Sink) {
+
+/**
+ * A light event emitter.
+ *
+ * @class
+ * @static Sink
+*/
+function EventEmitter () {
+ var k;
+ for (k in EventEmitter.prototype) {
+ if (EventEmitter.prototype.hasOwnProperty(k)) {
+ this[k] = EventEmitter.prototype[k];
+ }
+ }
+ this._listeners = {};
+}
+
+EventEmitter.prototype = {
+ _listeners: null,
+/**
+ * Emits an event.
+ *
+ * @method EventEmitter
+ *
+ * @arg {String} name The name of the event to emit.
+ * @arg {Array} args The arguments to pass to the event handlers.
+*/
+ emit: function (name, args) {
+ if (this._listeners[name]) {
+ for (var i=0; i<this._listeners[name].length; i++) {
+ this._listeners[name][i].apply(this, args);
+ }
+ }
+ return this;
+ },
+/**
+ * Adds an event listener to an event.
+ *
+ * @method EventEmitter
+ *
+ * @arg {String} name The name of the event.
+ * @arg {Function} listener The event listener to attach to the event.
+*/
+ on: function (name, listener) {
+ this._listeners[name] = this._listeners[name] || [];
+ this._listeners[name].push(listener);
+ return this;
+ },
+/**
+ * Adds an event listener to an event.
+ *
+ * @method EventEmitter
+ *
+ * @arg {String} name The name of the event.
+ * @arg {Function} !listener The event listener to remove from the event. If not specified, will delete all.
+*/
+ off: function (name, listener) {
+ if (this._listeners[name]) {
+ if (!listener) {
+ delete this._listeners[name];
+ return this;
+ }
+
+ for (var i=0; i<this._listeners[name].length; i++) {
+ if (this._listeners[name][i] === listener) {
+ this._listeners[name].splice(i--, 1);
+ }
+ }
+
+ if (!this._listeners[name].length) {
+ delete this._listeners[name];
+ }
+ }
+ return this;
+ }
+};
+
+Sink.EventEmitter = EventEmitter;
+
+EventEmitter.call(Sink);
+
+}(this.Sink);
+void function (Sink) {
+
+/**
+ * Creates a timer with consistent (ie. not clamped) intervals even in background tabs.
+ * Uses inline workers to achieve this. If not available, will revert to regular timers.
+ *
+ * @static Sink
+ * @name doInterval
+ *
+ * @arg {Function} callback The callback to trigger on timer hit.
+ * @arg {Number} timeout The interval between timer hits.
+ *
+ * @return {Function} A function to cancel the timer.
+*/
+
+Sink.doInterval = function (callback, timeout) {
+ var timer, kill;
+
+ function create (noWorker) {
+ if (Sink.inlineWorker.working && !noWorker) {
+ timer = Sink.inlineWorker('setInterval(function (){ postMessage("tic"); }, ' + timeout + ');');
+ timer.onmessage = function (){
+ callback();
+ };
+ kill = function () {
+ timer.terminate();
+ };
+ } else {
+ timer = setInterval(callback, timeout);
+ kill = function (){
+ clearInterval(timer);
+ };
+ }
+ }
+
+ if (Sink.inlineWorker.ready) {
+ create();
+ } else {
+ Sink.inlineWorker.on('ready', function () {
+ create();
+ });
+ }
+
+ return function () {
+ if (!kill) {
+ if (!Sink.inlineWorker.ready) {
+ Sink.inlineWorker.on('ready', function () {
+ if (kill) kill();
+ });
+ }
+ } else {
+ kill();
+ }
+ };
+};
+
+}(this.Sink);
+void function (Sink) {
+
+var _Blob, _BlobBuilder, _URL, _btoa;
+
+void function (prefixes, urlPrefixes) {
+ function find (name, prefixes) {
+ var b, a = prefixes.slice();
+
+ for (b=a.shift(); typeof b !== 'undefined'; b=a.shift()) {
+ b = Function('return typeof ' + b + name +
+ '=== "undefined" ? undefined : ' +
+ b + name)();
+
+ if (b) return b;
+ }
+ }
+
+ _Blob = find('Blob', prefixes);
+ _BlobBuilder = find('BlobBuilder', prefixes);
+ _URL = find('URL', urlPrefixes);
+ _btoa = find('btoa', ['']);
+}([
+ '',
+ 'Moz',
+ 'WebKit',
+ 'MS'
+], [
+ '',
+ 'webkit'
+]);
+
+var createBlob = _Blob && _URL && function (content, type) {
+ return _URL.createObjectURL(new _Blob([content], { type: type }));
+};
+
+var createBlobBuilder = _BlobBuilder && _URL && function (content, type) {
+ var bb = new _BlobBuilder();
+ bb.append(content);
+
+ return _URL.createObjectURL(bb.getBlob(type));
+};
+
+var createData = _btoa && function (content, type) {
+ return 'data:' + type + ';base64,' + _btoa(content);
+};
+
+var createDynURL =
+ createBlob ||
+ createBlobBuilder ||
+ createData;
+
+if (!createDynURL) return;
+
+if (createBlob) createDynURL.createBlob = createBlob;
+if (createBlobBuilder) createDynURL.createBlobBuilder = createBlobBuilder;
+if (createData) createDynURL.createData = createData;
+
+if (_Blob) createDynURL.Blob = _Blob;
+if (_BlobBuilder) createDynURL.BlobBuilder = _BlobBuilder;
+if (_URL) createDynURL.URL = _URL;
+
+Sink.createDynURL = createDynURL;
+
+Sink.revokeDynURL = function (url) {
+ if (typeof url === 'string' && url.indexOf('data:') === 0) {
+ return false;
+ } else {
+ return _URL.revokeObjectURL(url);
+ }
+};
+
+}(this.Sink);
+void function (Sink) {
+
+/*
+ * A Sink-specific error class.
+ *
+ * @class
+ * @static Sink
+ * @name Error
+ *
+ * @arg =code
+ *
+ * @param {Number} code The error code.
+ * @param {String} message A brief description of the error.
+ * @param {String} explanation A more verbose explanation of why the error occured and how to fix.
+*/
+
+function SinkError(code) {
+ if (!SinkError.hasOwnProperty(code)) throw SinkError(1);
+ if (!(this instanceof SinkError)) return new SinkError(code);
+
+ var k;
+ for (k in SinkError[code]) {
+ if (SinkError[code].hasOwnProperty(k)) {
+ this[k] = SinkError[code][k];
+ }
+ }
+
+ this.code = code;
+}
+
+SinkError.prototype = new Error();
+
+SinkError.prototype.toString = function () {
+ return 'SinkError 0x' + this.code.toString(16) + ': ' + this.message;
+};
+
+SinkError[0x01] = {
+ message: 'No such error code.',
+ explanation: 'The error code does not exist.'
+};
+SinkError[0x02] = {
+ message: 'No audio sink available.',
+ explanation: 'The audio device may be busy, or no supported output API is available for this browser.'
+};
+
+SinkError[0x10] = {
+ message: 'Buffer underflow.',
+ explanation: 'Trying to recover...'
+};
+SinkError[0x11] = {
+ message: 'Critical recovery fail.',
+ explanation: 'The buffer underflow has reached a critical point, trying to recover, but will probably fail anyway.'
+};
+SinkError[0x12] = {
+ message: 'Buffer size too large.',
+ explanation: 'Unable to allocate the buffer due to excessive length, please try a smaller buffer. Buffer size should probably be smaller than the sample rate.'
+};
+
+Sink.Error = SinkError;
+
+}(this.Sink);
+void function (Sink) {
+
+/**
+ * Creates an inline worker using a data/blob URL, if possible.
+ *
+ * @static Sink
+ *
+ * @arg {String} script
+ *
+ * @return {Worker} A web worker, or null if impossible to create.
+*/
+
+var define = Object.defineProperty ? function (obj, name, value) {
+ Object.defineProperty(obj, name, {
+ value: value,
+ configurable: true,
+ writable: true
+ });
+} : function (obj, name, value) {
+ obj[name] = value;
+};
+
+function terminate () {
+ define(this, 'terminate', this._terminate);
+
+ Sink.revokeDynURL(this._url);
+
+ delete this._url;
+ delete this._terminate;
+ return this.terminate();
+}
+
+function inlineWorker (script) {
+ function wrap (type, content, typeName) {
+ try {
+ var url = type(content, 'text/javascript');
+ var worker = new Worker(url);
+
+ define(worker, '_url', url);
+ define(worker, '_terminate', worker.terminate);
+ define(worker, 'terminate', terminate);
+
+ if (inlineWorker.type) return worker;
+
+ inlineWorker.type = typeName;
+ inlineWorker.createURL = type;
+
+ return worker;
+ } catch (e) {
+ return null;
+ }
+ }
+
+ var createDynURL = Sink.createDynURL;
+ var worker;
+
+ if (inlineWorker.createURL) {
+ return wrap(inlineWorker.createURL, script, inlineWorker.type);
+ }
+
+ worker = wrap(createDynURL.createBlob, script, 'blob');
+ if (worker) return worker;
+
+ worker = wrap(createDynURL.createBlobBuilder, script, 'blobbuilder');
+ if (worker) return worker;
+
+ worker = wrap(createDynURL.createData, script, 'data');
+
+ return worker;
+}
+
+Sink.EventEmitter.call(inlineWorker);
+
+inlineWorker.test = function () {
+ inlineWorker.ready = inlineWorker.working = false;
+ inlineWorker.type = '';
+ inlineWorker.createURL = null;
+
+ var worker = inlineWorker('this.onmessage=function(e){postMessage(e.data)}');
+ var data = 'inlineWorker';
+
+ function ready (success) {
+ if (inlineWorker.ready) return;
+
+ inlineWorker.ready = true;
+ inlineWorker.working = success;
+ inlineWorker.emit('ready', [success]);
+ inlineWorker.off('ready');
+
+ if (success && worker) {
+ worker.terminate();
+ }
+
+ worker = null;
+ }
+
+ if (!worker) {
+ setTimeout(function () {
+ ready(false);
+ }, 0);
+ } else {
+ worker.onmessage = function (e) {
+ ready(e.data === data);
+ };
+
+ worker.postMessage(data);
+
+ setTimeout(function () {
+ ready(false);
+ }, 1000);
+ }
+};
+
+Sink.inlineWorker = inlineWorker;
+
+inlineWorker.test();
+
+}(this.Sink);
+void function (Sink) {
+
+/**
+ * A Sink class for the Mozilla Audio Data API.
+*/
+
+Sink.sinks('audiodata', function () {
+ var self = this,
+ currentWritePosition = 0,
+ tail = null,
+ audioDevice = new Audio(),
+ written, currentPosition, available, soundData, prevPos,
+ timer; // Fix for https://bugzilla.mozilla.org/show_bug.cgi?id=630117
+ self.start.apply(self, arguments);
+ self.preBufferSize = isNaN(arguments[4]) || arguments[4] === null ? this.preBufferSize : arguments[4];
+
+ function bufferFill() {
+ if (tail) {
+ written = audioDevice.mozWriteAudio(tail);
+ currentWritePosition += written;
+ if (written < tail.length){
+ tail = tail.subarray(written);
+ return tail;
+ }
+ tail = null;
+ }
+
+ currentPosition = audioDevice.mozCurrentSampleOffset();
+ available = Number(currentPosition + (prevPos !== currentPosition ? self.bufferSize : self.preBufferSize) * self.channelCount - currentWritePosition);
+
+ if (currentPosition === prevPos) {
+ self.emit('error', [Sink.Error(0x10)]);
+ }
+
+ if (available > 0 || prevPos === currentPosition){
+ self.ready();
+
+ try {
+ soundData = new Float32Array(prevPos === currentPosition ? self.preBufferSize * self.channelCount :
+ self.forceBufferSize ? available < self.bufferSize * 2 ? self.bufferSize * 2 : available : available);
+ } catch(e) {
+ self.emit('error', [Sink.Error(0x12)]);
+ self.kill();
+ return;
+ }
+ self.process(soundData, self.channelCount);
+ written = self._audio.mozWriteAudio(soundData);
+ if (written < soundData.length){
+ tail = soundData.subarray(written);
+ }
+ currentWritePosition += written;
+ }
+ prevPos = currentPosition;
+ }
+
+ audioDevice.mozSetup(self.channelCount, self.sampleRate);
+
+ this._timers = [];
+
+ this._timers.push(Sink.doInterval(function () {
+ // Check for complete death of the output
+ if (+new Date() - self.previousHit > 2000) {
+ self._audio = audioDevice = new Audio();
+ audioDevice.mozSetup(self.channelCount, self.sampleRate);
+ currentWritePosition = 0;
+ self.emit('error', [Sink.Error(0x11)]);
+ }
+ }, 1000));
+
+ this._timers.push(Sink.doInterval(bufferFill, self.interval));
+
+ self._bufferFill = bufferFill;
+ self._audio = audioDevice;
+}, {
+ // These are somewhat safe values...
+ bufferSize: 24576,
+ preBufferSize: 24576,
+ forceBufferSize: false,
+ interval: 100,
+
+ kill: function () {
+ while (this._timers.length) {
+ this._timers.shift()();
+ }
+
+ this.emit('kill');
+ },
+
+ getPlaybackTime: function () {
+ return this._audio.mozCurrentSampleOffset() / this.channelCount;
+ }
+}, false, true);
+
+Sink.sinks.moz = Sink.sinks.audiodata;
+
+}(this.Sink);
+void function (Sink) {
+
+/**
+ * A dummy Sink. (No output)
+*/
+
+Sink.sinks('dummy', function () {
+ var self = this;
+ self.start.apply(self, arguments);
+
+ function bufferFill () {
+ var soundData = new Float32Array(self.bufferSize * self.channelCount);
+ self.process(soundData, self.channelCount);
+ }
+
+ self._kill = Sink.doInterval(bufferFill, self.bufferSize / self.sampleRate * 1000);
+
+ self._callback = bufferFill;
+}, {
+ kill: function () {
+ this._kill();
+ this.emit('kill');
+ }
+}, true);
+
+}(this.Sink);
+(function (Sink, sinks) {
+
+sinks = Sink.sinks;
+
+function newAudio (src) {
+ var audio = document.createElement('audio');
+ if (src) {
+ audio.src = src;
+ }
+ return audio;
+}
+
+/* TODO: Implement a <BGSOUND> hack for IE8. */
+
+/**
+ * A sink class for WAV data URLs
+ * Relies on pcmdata.js and utils to be present.
+ * Thanks to grantgalitz and others for the idea.
+*/
+sinks('wav', function () {
+ var self = this,
+ audio = new sinks.wav.wavAudio(),
+ PCMData = typeof PCMData === 'undefined' ? audioLib.PCMData : PCMData;
+ self.start.apply(self, arguments);
+ var soundData = new Float32Array(self.bufferSize * self.channelCount),
+ zeroData = new Float32Array(self.bufferSize * self.channelCount);
+
+ if (!newAudio().canPlayType('audio/wav; codecs=1') || !btoa) throw 0;
+
+ function bufferFill () {
+ if (self._audio.hasNextFrame) return;
+
+ self.ready();
+
+ Sink.memcpy(zeroData, 0, soundData, 0);
+ self.process(soundData, self.channelCount);
+
+ self._audio.setSource('data:audio/wav;base64,' + btoa(
+ audioLib.PCMData.encode({
+ data: soundData,
+ sampleRate: self.sampleRate,
+ channelCount: self.channelCount,
+ bytesPerSample: self.quality
+ })
+ ));
+
+ if (!self._audio.currentFrame.src) self._audio.nextClip();
+ }
+
+ self.kill = Sink.doInterval(bufferFill, 40);
+ self._bufferFill = bufferFill;
+ self._audio = audio;
+}, {
+ quality: 1,
+ bufferSize: 22050,
+
+ getPlaybackTime: function () {
+ var audio = this._audio;
+ return (audio.currentFrame ? audio.currentFrame.currentTime * this.sampleRate : 0) + audio.samples;
+ }
+});
+
+function wavAudio () {
+ var self = this;
+
+ self.currentFrame = newAudio();
+ self.nextFrame = newAudio();
+
+ self._onended = function () {
+ self.samples += self.bufferSize;
+ self.nextClip();
+ };
+}
+
+wavAudio.prototype = {
+ samples: 0,
+ nextFrame: null,
+ currentFrame: null,
+ _onended: null,
+ hasNextFrame: false,
+
+ nextClip: function () {
+ var curFrame = this.currentFrame;
+ this.currentFrame = this.nextFrame;
+ this.nextFrame = curFrame;
+ this.hasNextFrame = false;
+ this.currentFrame.play();
+ },
+
+ setSource: function (src) {
+ this.nextFrame.src = src;
+ this.nextFrame.addEventListener('ended', this._onended, true);
+
+ this.hasNextFrame = true;
+ }
+};
+
+sinks.wav.wavAudio = wavAudio;
+
+}(this.Sink));
+ (function (sinks, fixChrome82795) {
+
+var AudioContext = typeof window === 'undefined' ? null : window.webkitAudioContext || window.AudioContext;
+
+/**
+ * A sink class for the Web Audio API
+*/
+
+sinks('webaudio', function (readFn, channelCount, bufferSize, sampleRate) {
+ var self = this,
+ context = sinks.webaudio.getContext(),
+ node = null,
+ soundData = null,
+ zeroBuffer = null;
+ self.start.apply(self, arguments);
+ node = context.createJavaScriptNode(self.bufferSize, 0, self.channelCount);
+
+ function bufferFill(e) {
+ var outputBuffer = e.outputBuffer,
+ channelCount = outputBuffer.numberOfChannels,
+ i, n, l = outputBuffer.length,
+ size = outputBuffer.size,
+ channels = new Array(channelCount),
+ tail;
+
+ self.ready();
+
+ soundData = soundData && soundData.length === l * channelCount ? soundData : new Float32Array(l * channelCount);
+ zeroBuffer = zeroBuffer && zeroBuffer.length === soundData.length ? zeroBuffer : new Float32Array(l * channelCount);
+ soundData.set(zeroBuffer);
+
+ for (i=0; i<channelCount; i++) {
+ channels[i] = outputBuffer.getChannelData(i);
+ }
+
+ self.process(soundData, self.channelCount);
+
+ for (i=0; i<l; i++) {
+ for (n=0; n < channelCount; n++) {
+ channels[n][i] = soundData[i * self.channelCount + n];
+ }
+ }
+ }
+
+ self.sampleRate = context.sampleRate;
+
+ node.onaudioprocess = bufferFill;
+ node.connect(context.destination);
+
+ self._context = context;
+ self._node = node;
+ self._callback = bufferFill;
+ /* Keep references in order to avoid garbage collection removing the listeners, working around http://code.google.com/p/chromium/issues/detail?id=82795 */
+ // Thanks to @baffo32
+ fixChrome82795.push(node);
+}, {
+ kill: function () {
+ this._node.disconnect(0);
+
+ for (var i=0; i<fixChrome82795.length; i++) {
+ if (fixChrome82795[i] === this._node) {
+ fixChrome82795.splice(i--, 1);
+ }
+ }
+
+ this._node = this._context = null;
+ this.emit('kill');
+ },
+
+ getPlaybackTime: function () {
+ return this._context.currentTime * this.sampleRate;
+ }
+}, false, true);
+
+sinks.webkit = sinks.webaudio;
+
+sinks.webaudio.fix82795 = fixChrome82795;
+
+sinks.webaudio.getContext = function () {
+ // For now, we have to accept that the AudioContext is at 48000Hz, or whatever it decides.
+ var context = new AudioContext(/*sampleRate*/);
+
+ sinks.webaudio.getContext = function () {
+ return context;
+ };
+
+ return context;
+};
+
+}(this.Sink.sinks, []));
+(function (Sink) {
+
+/**
+ * A Sink class for the Media Streams Processing API and/or Web Audio API in a Web Worker.
+*/
+
+Sink.sinks('worker', function () {
+ var self = this,
+ global = (function(){ return this; }()),
+ soundData = null,
+ outBuffer = null,
+ zeroBuffer = null;
+ self.start.apply(self, arguments);
+
+ // Let's see if we're in a worker.
+
+ importScripts();
+
+ function mspBufferFill (e) {
+ if (!self.isReady) {
+ self.initMSP(e);
+ }
+
+ self.ready();
+
+ var channelCount = self.channelCount,
+ l = e.audioLength,
+ n, i;
+
+ soundData = soundData && soundData.length === l * channelCount ? soundData : new Float32Array(l * channelCount);
+ outBuffer = outBuffer && outBuffer.length === soundData.length ? outBuffer : new Float32Array(l * channelCount);
+ zeroBuffer = zeroBuffer && zeroBuffer.length === soundData.length ? zeroBuffer : new Float32Array(l * channelCount);
+
+ soundData.set(zeroBuffer);
+ outBuffer.set(zeroBuffer);
+
+ self.process(soundData, self.channelCount);
+
+ for (n=0; n<channelCount; n++) {
+ for (i=0; i<l; i++) {
+ outBuffer[n * e.audioLength + i] = soundData[n + i * channelCount];
+ }
+ }
+
+ e.writeAudio(outBuffer);
+ }
+
+ function waBufferFill(e) {
+ if (!self.isReady) {
+ self.initWA(e);
+ }
+
+ self.ready();
+
+ var outputBuffer = e.outputBuffer,
+ channelCount = outputBuffer.numberOfChannels,
+ i, n, l = outputBuffer.length,
+ size = outputBuffer.size,
+ channels = new Array(channelCount),
+ tail;
+
+ soundData = soundData && soundData.length === l * channelCount ? soundData : new Float32Array(l * channelCount);
+ zeroBuffer = zeroBuffer && zeroBuffer.length === soundData.length ? zeroBuffer : new Float32Array(l * channelCount);
+ soundData.set(zeroBuffer);
+
+ for (i=0; i<channelCount; i++) {
+ channels[i] = outputBuffer.getChannelData(i);
+ }
+
+ self.process(soundData, self.channelCount);
+
+ for (i=0; i<l; i++) {
+ for (n=0; n < channelCount; n++) {
+ channels[n][i] = soundData[i * self.channelCount + n];
+ }
+ }
+ }
+
+ global.onprocessmedia = mspBufferFill;
+ global.onaudioprocess = waBufferFill;
+
+ self._mspBufferFill = mspBufferFill;
+ self._waBufferFill = waBufferFill;
+
+}, {
+ ready: false,
+
+ initMSP: function (e) {
+ this.channelCount = e.audioChannels;
+ this.sampleRate = e.audioSampleRate;
+ this.bufferSize = e.audioLength * this.channelCount;
+ this.ready = true;
+ this.emit('ready', []);
+ },
+
+ initWA: function (e) {
+ var b = e.outputBuffer;
+ this.channelCount = b.numberOfChannels;
+ this.sampleRate = b.sampleRate;
+ this.bufferSize = b.length * this.channelCount;
+ this.ready = true;
+ this.emit('ready', []);
+ }
+});
+
+}(this.Sink));
+(function (Sink) {
+
+/**
+ * Splits a sample buffer into those of different channels.
+ *
+ * @static Sink
+ * @name deinterleave
+ *
+ * @arg {Buffer} buffer The sample buffer to split.
+ * @arg {Number} channelCount The number of channels to split to.
+ *
+ * @return {Array} An array containing the resulting sample buffers.
+*/
+
+Sink.deinterleave = function (buffer, channelCount) {
+ var l = buffer.length,
+ size = l / channelCount,
+ ret = [],
+ i, n;
+ for (i=0; i<channelCount; i++){
+ ret[i] = new Float32Array(size);
+ for (n=0; n<size; n++){
+ ret[i][n] = buffer[n * channelCount + i];
+ }
+ }
+ return ret;
+};
+
+/**
+ * Joins an array of sample buffers into a single buffer.
+ *
+ * @static Sink
+ * @name resample
+ *
+ * @arg {Array} buffers The buffers to join.
+ * @arg {Number} !channelCount The number of channels. Defaults to buffers.length
+ * @arg {Buffer} !buffer The output buffer.
+ *
+ * @return {Buffer} The interleaved buffer created.
+*/
+
+Sink.interleave = function (buffers, channelCount, buffer) {
+ channelCount = channelCount || buffers.length;
+ var l = buffers[0].length,
+ bufferCount = buffers.length,
+ i, n;
+ buffer = buffer || new Float32Array(l * channelCount);
+ for (i=0; i<bufferCount; i++) {
+ for (n=0; n<l; n++) {
+ buffer[i + n * channelCount] = buffers[i][n];
+ }
+ }
+ return buffer;
+};
+
+/**
+ * Mixes two or more buffers down to one.
+ *
+ * @static Sink
+ * @name mix
+ *
+ * @arg {Buffer} buffer The buffer to append the others to.
+ * @arg {Buffer} bufferX The buffers to append from.
+ *
+ * @return {Buffer} The mixed buffer.
+*/
+
+Sink.mix = function (buffer) {
+ var buffers = [].slice.call(arguments, 1),
+ l, i, c;
+ for (c=0; c<buffers.length; c++){
+ l = Math.max(buffer.length, buffers[c].length);
+ for (i=0; i<l; i++){
+ buffer[i] += buffers[c][i];
+ }
+ }
+ return buffer;
+};
+
+/**
+ * Resets a buffer to all zeroes.
+ *
+ * @static Sink
+ * @name resetBuffer
+ *
+ * @arg {Buffer} buffer The buffer to reset.
+ *
+ * @return {Buffer} The 0-reset buffer.
+*/
+
+Sink.resetBuffer = function (buffer) {
+ var l = buffer.length,
+ i;
+ for (i=0; i<l; i++){
+ buffer[i] = 0;
+ }
+ return buffer;
+};
+
+/**
+ * Copies the content of a buffer to another buffer.
+ *
+ * @static Sink
+ * @name clone
+ *
+ * @arg {Buffer} buffer The buffer to copy from.
+ * @arg {Buffer} !result The buffer to copy to.
+ *
+ * @return {Buffer} A clone of the buffer.
+*/
+
+Sink.clone = function (buffer, result) {
+ var l = buffer.length,
+ i;
+ result = result || new Float32Array(l);
+ for (i=0; i<l; i++){
+ result[i] = buffer[i];
+ }
+ return result;
+};
+
+/**
+ * Creates an array of buffers of the specified length and the specified count.
+ *
+ * @static Sink
+ * @name createDeinterleaved
+ *
+ * @arg {Number} length The length of a single channel.
+ * @arg {Number} channelCount The number of channels.
+ * @return {Array} The array of buffers.
+*/
+
+Sink.createDeinterleaved = function (length, channelCount) {
+ var result = new Array(channelCount),
+ i;
+ for (i=0; i<channelCount; i++){
+ result[i] = new Float32Array(length);
+ }
+ return result;
+};
+
+Sink.memcpy = function (src, srcOffset, dst, dstOffset, length) {
+ src = src.subarray || src.slice ? src : src.buffer;
+ dst = dst.subarray || dst.slice ? dst : dst.buffer;
+
+ src = srcOffset ? src.subarray ?
+ src.subarray(srcOffset, length && srcOffset + length) :
+ src.slice(srcOffset, length && srcOffset + length) : src;
+
+ if (dst.set) {
+ dst.set(src, dstOffset);
+ } else {
+ for (var i=0; i<src.length; i++) {
+ dst[i + dstOffset] = src[i];
+ }
+ }
+
+ return dst;
+};
+
+Sink.memslice = function (buffer, offset, length) {
+ return buffer.subarray ? buffer.subarray(offset, length) : buffer.slice(offset, length);
+};
+
+Sink.mempad = function (buffer, out, offset) {
+ out = out.length ? out : new (buffer.constructor)(out);
+ Sink.memcpy(buffer, 0, out, offset);
+ return out;
+};
+
+Sink.linspace = function (start, end, out) {
+ var l, i, n, step;
+ out = out.length ? (l=out.length) && out : Array(l=out);
+ step = (end - start) / --l;
+ for (n=start+step, i=1; i<l; i++, n+=step) {
+ out[i] = n;
+ }
+ out[0] = start;
+ out[l] = end;
+ return out;
+};
+
+Sink.ftoi = function (input, bitCount, output) {
+ var i, mask = Math.pow(2, bitCount - 1);
+
+ output = output || new (input.constructor)(input.length);
+
+ for (i=0; i<input.length; i++) {
+ output[i] = ~~(mask * input[i]);
+ }
+
+ return output;
+};
+
+}(this.Sink));
+(function (Sink) {
+
+function Proxy (bufferSize, channelCount) {
+ Sink.EventEmitter.call(this);
+
+ this.bufferSize = isNaN(bufferSize) || bufferSize === null ? this.bufferSize : bufferSize;
+ this.channelCount = isNaN(channelCount) || channelCount === null ? this.channelCount : channelCount;
+
+ var self = this;
+ this.callback = function () {
+ return self.process.apply(self, arguments);
+ };
+
+ this.resetBuffer();
+}
+
+Proxy.prototype = {
+ buffer: null,
+ zeroBuffer: null,
+ parentSink: null,
+ bufferSize: 4096,
+ channelCount: 2,
+ offset: null,
+
+ resetBuffer: function () {
+ this.buffer = new Float32Array(this.bufferSize);
+ this.zeroBuffer = new Float32Array(this.bufferSize);
+ },
+
+ process: function (buffer, channelCount) {
+ if (this.offset === null) {
+ this.loadBuffer();
+ }
+
+ for (var i=0; i<buffer.length; i++) {
+ if (this.offset >= this.buffer.length) {
+ this.loadBuffer();
+ }
+
+ buffer[i] = this.buffer[this.offset++];
+ }
+ },
+
+ loadBuffer: function () {
+ this.offset = 0;
+ Sink.memcpy(this.zeroBuffer, 0, this.buffer, 0);
+ this.emit('audioprocess', [this.buffer, this.channelCount]);
+ }
+};
+
+Sink.Proxy = Proxy;
+
+/**
+ * Creates a proxy callback system for the sink instance.
+ * Requires Sink utils.
+ *
+ * @method Sink
+ * @method createProxy
+ *
+ * @arg {Number} !bufferSize The buffer size for the proxy.
+*/
+Sink.prototype.createProxy = function (bufferSize) {
+ var proxy = new Sink.Proxy(bufferSize, this.channelCount);
+ proxy.parentSink = this;
+
+ this.on('audioprocess', proxy.callback);
+
+ return proxy;
+};
+
+}(this.Sink));
+(function (Sink) {
+
+(function(){
+
+/**
+ * If method is supplied, adds a new interpolation method to Sink.interpolation, otherwise sets the default interpolation method (Sink.interpolate) to the specified property of Sink.interpolate.
+ *
+ * @arg {String} name The name of the interpolation method to get / set.
+ * @arg {Function} !method The interpolation method.
+*/
+
+function interpolation(name, method) {
+ if (name && method) {
+ interpolation[name] = method;
+ } else if (name && interpolation[name] instanceof Function) {
+ Sink.interpolate = interpolation[name];
+ }
+ return interpolation[name];
+}
+
+Sink.interpolation = interpolation;
+
+
+/**
+ * Interpolates a fractal part position in an array to a sample. (Linear interpolation)
+ *
+ * @param {Array} arr The sample buffer.
+ * @param {number} pos The position to interpolate from.
+ * @return {Float32} The interpolated sample.
+*/
+interpolation('linear', function (arr, pos) {
+ var first = Math.floor(pos),
+ second = first + 1,
+ frac = pos - first;
+ second = second < arr.length ? second : 0;
+ return arr[first] * (1 - frac) + arr[second] * frac;
+});
+
+/**
+ * Interpolates a fractal part position in an array to a sample. (Nearest neighbour interpolation)
+ *
+ * @param {Array} arr The sample buffer.
+ * @param {number} pos The position to interpolate from.
+ * @return {Float32} The interpolated sample.
+*/
+interpolation('nearest', function (arr, pos) {
+ return pos >= arr.length - 0.5 ? arr[0] : arr[Math.round(pos)];
+});
+
+interpolation('linear');
+
+}());
+
+
+/**
+ * Resamples a sample buffer from a frequency to a frequency and / or from a sample rate to a sample rate.
+ *
+ * @static Sink
+ * @name resample
+ *
+ * @arg {Buffer} buffer The sample buffer to resample.
+ * @arg {Number} fromRate The original sample rate of the buffer, or if the last argument, the speed ratio to convert with.
+ * @arg {Number} fromFrequency The original frequency of the buffer, or if the last argument, used as toRate and the secondary comparison will not be made.
+ * @arg {Number} toRate The sample rate of the created buffer.
+ * @arg {Number} toFrequency The frequency of the created buffer.
+ *
+ * @return The new resampled buffer.
+*/
+Sink.resample = function (buffer, fromRate /* or speed */, fromFrequency /* or toRate */, toRate, toFrequency) {
+ var
+ argc = arguments.length,
+ speed = argc === 2 ? fromRate : argc === 3 ? fromRate / fromFrequency : toRate / fromRate * toFrequency / fromFrequency,
+ l = buffer.length,
+ length = Math.ceil(l / speed),
+ newBuffer = new Float32Array(length),
+ i, n;
+ for (i=0, n=0; i<l; i += speed) {
+ newBuffer[n++] = Sink.interpolate(buffer, i);
+ }
+ return newBuffer;
+};
+
+}(this.Sink));
+void function (Sink) {
+
+Sink.on('init', function (sink) {
+ sink.activeRecordings = [];
+ sink.on('postprocess', sink.recordData);
+});
+
+Sink.prototype.activeRecordings = null;
+
+/**
+ * Starts recording the sink output.
+ *
+ * @method Sink
+ * @name record
+ *
+ * @return {Recording} The recording object for the recording started.
+*/
+Sink.prototype.record = function () {
+ var recording = new Sink.Recording(this);
+ this.emit('record', [recording]);
+ return recording;
+};
+/**
+ * Private method that handles the adding the buffers to all the current recordings.
+ *
+ * @method Sink
+ * @method recordData
+ *
+ * @arg {Array} buffer The buffer to record.
+*/
+Sink.prototype.recordData = function (buffer) {
+ var activeRecs = this.activeRecordings,
+ i, l = activeRecs.length;
+ for (i=0; i<l; i++) {
+ activeRecs[i].add(buffer);
+ }
+};
+
+/**
+ * A Recording class for recording sink output.
+ *
+ * @class
+ * @static Sink
+ * @arg {Object} bindTo The sink to bind the recording to.
+*/
+
+function Recording (bindTo) {
+ this.boundTo = bindTo;
+ this.buffers = [];
+ bindTo.activeRecordings.push(this);
+}
+
+Recording.prototype = {
+/**
+ * Adds a new buffer to the recording.
+ *
+ * @arg {Array} buffer The buffer to add.
+ *
+ * @method Recording
+*/
+ add: function (buffer) {
+ this.buffers.push(buffer);
+ },
+/**
+ * Empties the recording.
+ *
+ * @method Recording
+*/
+ clear: function () {
+ this.buffers = [];
+ },
+/**
+ * Stops the recording and unbinds it from it's host sink.
+ *
+ * @method Recording
+*/
+ stop: function () {
+ var recordings = this.boundTo.activeRecordings,
+ i;
+ for (i=0; i<recordings.length; i++) {
+ if (recordings[i] === this) {
+ recordings.splice(i--, 1);
+ }
+ }
+ },
+/**
+ * Joins the recorded buffers into a single buffer.
+ *
+ * @method Recording
+*/
+ join: function () {
+ var bufferLength = 0,
+ bufPos = 0,
+ buffers = this.buffers,
+ newArray,
+ n, i, l = buffers.length;
+
+ for (i=0; i<l; i++) {
+ bufferLength += buffers[i].length;
+ }
+ newArray = new Float32Array(bufferLength);
+ for (i=0; i<l; i++) {
+ for (n=0; n<buffers[i].length; n++) {
+ newArray[bufPos + n] = buffers[i][n];
+ }
+ bufPos += buffers[i].length;
+ }
+ return newArray;
+ }
+};
+
+Sink.Recording = Recording;
+
+}(this.Sink);
+void function (Sink) {
+
+function processRingBuffer () {
+ if (this.ringBuffer) {
+ (this.channelMode === 'interleaved' ? this.ringSpin : this.ringSpinInterleaved).apply(this, arguments);
+ }
+}
+
+Sink.on('init', function (sink) {
+ sink.on('preprocess', processRingBuffer);
+});
+
+Sink.prototype.ringBuffer = null;
+
+/**
+ * A private method that applies the ring buffer contents to the specified buffer, while in interleaved mode.
+ *
+ * @method Sink
+ * @name ringSpin
+ *
+ * @arg {Array} buffer The buffer to write to.
+*/
+Sink.prototype.ringSpin = function (buffer) {
+ var ring = this.ringBuffer,
+ l = buffer.length,
+ m = ring.length,
+ off = this.ringOffset,
+ i;
+ for (i=0; i<l; i++){
+ buffer[i] += ring[off];
+ off = (off + 1) % m;
+ }
+ this.ringOffset = off;
+};
+
+/**
+ * A private method that applies the ring buffer contents to the specified buffer, while in deinterleaved mode.
+ *
+ * @method Sink
+ * @name ringSpinDeinterleaved
+ *
+ * @param {Array} buffer The buffers to write to.
+*/
+Sink.prototype.ringSpinDeinterleaved = function (buffer) {
+ var ring = this.ringBuffer,
+ l = buffer.length,
+ ch = ring.length,
+ m = ring[0].length,
+ len = ch * m,
+ off = this.ringOffset,
+ i, n;
+ for (i=0; i<l; i+=ch){
+ for (n=0; n<ch; n++){
+ buffer[i + n] += ring[n][off];
+ }
+ off = (off + 1) % m;
+ }
+ this.ringOffset = n;
+};
+
+}(this.Sink);
+void function (Sink, proto) {
+
+proto = Sink.prototype;
+
+Sink.on('init', function (sink) {
+ sink.asyncBuffers = [];
+ sink.syncBuffers = [];
+ sink.on('preprocess', sink.writeBuffersSync);
+ sink.on('postprocess', sink.writeBuffersAsync);
+});
+
+proto.writeMode = 'async';
+proto.asyncBuffers = proto.syncBuffers = null;
+
+/**
+ * Private method that handles the mixing of asynchronously written buffers.
+ *
+ * @method Sink
+ * @name writeBuffersAsync
+ *
+ * @arg {Array} buffer The buffer to write to.
+*/
+proto.writeBuffersAsync = function (buffer) {
+ var buffers = this.asyncBuffers,
+ l = buffer.length,
+ buf,
+ bufLength,
+ i, n, offset;
+ if (buffers) {
+ for (i=0; i<buffers.length; i++) {
+ buf = buffers[i];
+ bufLength = buf.b.length;
+ offset = buf.d;
+ buf.d -= Math.min(offset, l);
+
+ for (n=0; n + offset < l && n < bufLength; n++) {
+ buffer[n + offset] += buf.b[n];
+ }
+ buf.b = buf.b.subarray(n + offset);
+ if (i >= bufLength) {
+ buffers.splice(i--, 1);
+ }
+ }
+ }
+};
+
+/**
+ * A private method that handles mixing synchronously written buffers.
+ *
+ * @method Sink
+ * @name writeBuffersSync
+ *
+ * @arg {Array} buffer The buffer to write to.
+*/
+proto.writeBuffersSync = function (buffer) {
+ var buffers = this.syncBuffers,
+ l = buffer.length,
+ i = 0,
+ soff = 0;
+ for (;i<l && buffers.length; i++) {
+ buffer[i] += buffers[0][soff];
+ if (buffers[0].length <= soff){
+ buffers.splice(0, 1);
+ soff = 0;
+ continue;
+ }
+ soff++;
+ }
+ if (buffers.length) {
+ buffers[0] = buffers[0].subarray(soff);
+ }
+};
+
+/**
+ * Writes a buffer asynchronously on top of the existing signal, after a specified delay.
+ *
+ * @method Sink
+ * @name writeBufferAsync
+ *
+ * @arg {Array} buffer The buffer to write.
+ * @arg {Number} delay The delay to write after. If not specified, the Sink will calculate a delay to compensate the latency.
+ * @return {Number} The number of currently stored asynchronous buffers.
+*/
+proto.writeBufferAsync = function (buffer, delay) {
+ buffer = this.mode === 'deinterleaved' ? Sink.interleave(buffer, this.channelCount) : buffer;
+ var buffers = this.asyncBuffers;
+ buffers.push({
+ b: buffer,
+ d: isNaN(delay) ? ~~((+new Date() - this.previousHit) / 1000 * this.sampleRate) : delay
+ });
+ return buffers.length;
+};
+
+/**
+ * Writes a buffer synchronously to the output.
+ *
+ * @method Sink
+ * @name writeBufferSync
+ *
+ * @param {Array} buffer The buffer to write.
+ * @return {Number} The number of currently stored synchronous buffers.
+*/
+proto.writeBufferSync = function (buffer) {
+ buffer = this.mode === 'deinterleaved' ? Sink.interleave(buffer, this.channelCount) : buffer;
+ var buffers = this.syncBuffers;
+ buffers.push(buffer);
+ return buffers.length;
+};
+
+/**
+ * Writes a buffer, according to the write mode specified.
+ *
+ * @method Sink
+ * @name writeBuffer
+ *
+ * @arg {Array} buffer The buffer to write.
+ * @arg {Number} delay The delay to write after. If not specified, the Sink will calculate a delay to compensate the latency. (only applicable in asynchronous write mode)
+ * @return {Number} The number of currently stored (a)synchronous buffers.
+*/
+proto.writeBuffer = function () {
+ return this[this.writeMode === 'async' ? 'writeBufferAsync' : 'writeBufferSync'].apply(this, arguments);
+};
+
+/**
+ * Gets the total amount of yet unwritten samples in the synchronous buffers.
+ *
+ * @method Sink
+ * @name getSyncWriteOffset
+ *
+ * @return {Number} The total amount of yet unwritten samples in the synchronous buffers.
+*/
+proto.getSyncWriteOffset = function () {
+ var buffers = this.syncBuffers,
+ offset = 0,
+ i;
+ for (i=0; i<buffers.length; i++) {
+ offset += buffers[i].length;
+ }
+ return offset;
+};
+
+} (this.Sink);
+
diff --git a/public/js/vendor/Audiolet.min.js b/public/js/vendor/Audiolet.min.js
new file mode 100644
index 0000000..388c5c5
--- /dev/null
+++ b/public/js/vendor/Audiolet.min.js
@@ -0,0 +1,252 @@
+var AudioletBuffer=function(a,c){this.numberOfChannels=a;this.length=c;this.channels=[];for(var b=0;b<this.numberOfChannels;b++){this.channels.push(new Float32Array(c))}this.unslicedChannels=[];for(var b=0;b<this.numberOfChannels;b++){this.unslicedChannels.push(this.channels[b])}this.isEmpty=false;this.channelOffset=0};AudioletBuffer.prototype.getChannelData=function(a){return(this.channels[a])};AudioletBuffer.prototype.set=function(a){var b=a.numberOfChannels;for(var c=0;c<b;c++){this.channels[c].set(a.getChannelData(c))}};AudioletBuffer.prototype.setSection=function(c,g,f,b){f=f||0;b=b||0;var d=c.numberOfChannels;for(var e=0;e<d;e++){f+=c.channelOffset;b+=this.channelOffset;var a=this.unslicedChannels[e].subarray(b,b+g);var h=c.unslicedChannels[e].subarray(f,f+g);
+/*!
+ var channel1 = this.getChannelData(i).subarray(outputOffset,
+ outputOffset +
+ length);
+ var channel2 = buffer.getChannelData(i).subarray(inputOffset,
+ inputOffset +
+ length);
+ */
+a.set(h)}};AudioletBuffer.prototype.add=function(b){var f=this.length;var d=b.numberOfChannels;for(var e=0;e<d;e++){var a=this.getChannelData(e);var g=b.getChannelData(e);for(var c=0;c<f;c++){a[c]+=g[c]}}};AudioletBuffer.prototype.addSection=function(f,b,h,a){h=h||0;a=a||0;var k=f.numberOfChannels;for(var g=0;g<k;g++){var d=this.getChannelData(g);var c=f.getChannelData(g);for(var e=0;e<b;e++){d[e+a]+=c[e+h]}}};AudioletBuffer.prototype.resize=function(n,c,e,g){g=g||0;var j=this.channels;var b=this.unslicedChannels;var d=this.length;var f=this.channelOffset+g;for(var h=0;h<n;h++){var k=j[h];var m=b[h];if(c>d){var l=k;if(!e||!m||m.length<c){m=new Float32Array(c)}k=m.subarray(0,c);if(!e&&l){k.set(l,g)}f=0}else{if(!m){var a=b[0].length;m=new Float32Array(a)}g=f;k=m.subarray(g,g+c)}j[h]=k;b[h]=m}this.channels=j.slice(0,n);this.unslicedChannels=b.slice(0,n);this.length=c;this.numberOfChannels=n;this.channelOffset=f};AudioletBuffer.prototype.push=function(a){var b=a.length;this.resize(this.numberOfChannels,this.length+b);this.setSection(a,b,0,this.length-b)};AudioletBuffer.prototype.pop=function(a){var b=a.length;var c=this.length-b;a.setSection(this,b,c,0);this.resize(this.numberOfChannels,c)};AudioletBuffer.prototype.unshift=function(a){var b=a.length;this.resize(this.numberOfChannels,this.length+b,false,b);this.setSection(a,b,0,0)};AudioletBuffer.prototype.shift=function(a){var b=a.length;a.setSection(this,b,0,0);this.resize(this.numberOfChannels,this.length-b,false,b)};AudioletBuffer.prototype.zero=function(){var b=this.numberOfChannels;for(var c=0;c<b;c++){var e=this.getChannelData(c);var d=this.length;for(var a=0;a<d;a++){e[a]=0}}};AudioletBuffer.prototype.combined=function(){var a=this.channels;var b=this.numberOfChannels;var e=this.length;var d=new Float32Array(b*e);for(var c=0;c<b;c++){d.set(a[c],c*e)}return d};AudioletBuffer.prototype.interleaved=function(){var a=this.channels;var c=this.numberOfChannels;var e=this.length;var f=new Float32Array(c*e);for(var d=0;d<e;d++){for(var b=0;b<c;b++){f[c*d+b]=a[b][d]}}return f};AudioletBuffer.prototype.copy=function(){var a=new AudioletBuffer(this.numberOfChannels,this.length);a.set(this);return a};AudioletBuffer.prototype.load=function(c,a,d){var b=new AudioFileRequest(c,a);b.onSuccess=function(e){this.length=e.length;this.numberOfChannels=e.channels.length;this.unslicedChannels=e.channels;this.channels=e.channels;this.channelOffset=0;if(d){d()}}.bind(this);b.onFailure=function(){console.error("Could not load",c)}.bind(this);b.send()};var AudioletGroup=function(b,a,d){this.audiolet=b;this.inputs=[];for(var c=0;c<a;c++){this.inputs.push(new PassThroughNode(this.audiolet,1,1))}this.outputs=[];for(var c=0;c<d;c++){this.outputs.push(new PassThroughNode(this.audiolet,1,1))}};AudioletGroup.prototype.connect=function(c,b,a){this.outputs[b||0].connect(c,0,a)};AudioletGroup.prototype.disconnect=function(c,b,a){this.outputs[b||0].disconnect(c,0,a)};AudioletGroup.prototype.remove=function(){var a=this.inputs.length;for(var b=0;b<a;b++){this.inputs[b].remove()}var c=this.outputs.length;for(var b=0;b<c;b++){this.outputs[b].remove()}};
+/*!
+ * @depends AudioletGroup.js
+ */
+var AudioletDestination=function(c,b,a,d){AudioletGroup.call(this,c,1,0);this.device=new AudioletDevice(c,b,a,d);c.device=this.device;this.scheduler=new Scheduler(c);c.scheduler=this.scheduler;this.upMixer=new UpMixer(c,this.device.numberOfChannels);this.inputs[0].connect(this.scheduler);this.scheduler.connect(this.upMixer);this.upMixer.connect(this.device)};extend(AudioletDestination,AudioletGroup);AudioletDestination.prototype.toString=function(){return"Destination"};var AudioletNode=function(c,b,e,a){this.audiolet=c;this.inputs=[];for(var d=0;d<b;d++){this.inputs.push(new AudioletInput(this,d))}this.outputs=[];for(var d=0;d<e;d++){this.outputs.push(new AudioletOutput(this,d))}if(a){this.generate=a}};AudioletNode.prototype.connect=function(d,c,b){if(d instanceof AudioletGroup){d=d.inputs[b||0];b=0}var a=this.outputs[c||0];var e=d.inputs[b||0];a.connect(e);e.connect(a);this.audiolet.device.needTraverse=true};AudioletNode.prototype.disconnect=function(d,c,b){if(d instanceof AudioletGroup){d=d.inputs[b||0];b=0}var a=this.outputs[c||0];var e=d.inputs[b||0];e.disconnect(a);a.disconnect(e);this.audiolet.device.needTraverse=true};AudioletNode.prototype.setNumberOfOutputChannels=function(a,b){this.outputs[a].numberOfChannels=b};AudioletNode.prototype.linkNumberOfOutputChannels=function(b,a){this.outputs[b].linkNumberOfChannels(this.inputs[a])};AudioletNode.prototype.tick=function(){this.createInputSamples();this.createOutputSamples();this.generate()};AudioletNode.prototype.traverse=function(a){if(a.indexOf(this)==-1){a.push(this);a=this.traverseParents(a)}return a};AudioletNode.prototype.traverseParents=function(c){var a=this.inputs.length;for(var e=0;e<a;e++){var b=this.inputs[e];var f=b.connectedFrom.length;for(var d=0;d<f;d++){c=b.connectedFrom[d].node.traverse(c)}}return c};AudioletNode.prototype.generate=function(){};AudioletNode.prototype.createInputSamples=function(){var a=this.inputs.length;for(var f=0;f<a;f++){var d=this.inputs[f];var h=0;for(var e=0;e<d.connectedFrom.length;e++){var c=d.connectedFrom[e];for(var b=0;b<c.samples.length;b++){var g=c.samples[b];if(b<h){d.samples[b]+=g}else{d.samples[b]=g;h+=1}}}if(d.samples.length>h){d.samples=d.samples.slice(0,h)}}};AudioletNode.prototype.createOutputSamples=function(){var e=this.outputs.length;for(var d=0;d<e;d++){var a=this.outputs[d];var c=a.getNumberOfChannels();if(a.samples.length==c){continue}else{if(a.samples.length>c){a.samples=a.samples.slice(0,c);continue}}for(var b=a.samples.length;b<c;b++){a.samples[b]=0}}};AudioletNode.prototype.remove=function(){var g=this.inputs.length;for(var e=0;e<g;e++){var h=this.inputs[e];var c=h.connectedFrom.length;for(var d=0;d<c;d++){var a=h.connectedFrom[d];var b=a.node;b.disconnect(this,a.index,e)}}var f=this.outputs.length;for(var e=0;e<f;e++){var b=this.outputs[e];var c=b.connectedTo.length;for(var d=0;d<c;d++){var k=b.connectedTo[d];var h=k.node;this.disconnect(h,e,k.index)}}};
+/*!
+ * @depends AudioletNode.js
+ */
+function AudioletDevice(c,b,a,d){AudioletNode.call(this,c,1,0);this.sink=Sink(this.tick.bind(this),a,d,b);this.sampleRate=this.sink.sampleRate;this.numberOfChannels=this.sink.channelCount;this.bufferSize=this.sink.preBufferSize;this.writePosition=0;this.buffer=null;this.paused=false;this.needTraverse=true;this.nodes=[]}extend(AudioletDevice,AudioletNode);AudioletDevice.prototype.tick=function(a,d){if(!this.paused){var b=this.inputs[0];var f=a.length/d;for(var e=0;e<f;e++){if(this.needTraverse){this.nodes=this.traverse([]);this.needTraverse=false}for(var c=this.nodes.length-1;c>0;c--){this.nodes[c].tick()}this.createInputSamples();for(var c=0;c<d;c++){a[e*d+c]=b.samples[c]}this.writePosition+=1}}};AudioletDevice.prototype.getPlaybackTime=function(){return this.sink.getPlaybackTime()};AudioletDevice.prototype.getWriteTime=function(){return this.writePosition};AudioletDevice.prototype.pause=function(){this.paused=true};AudioletDevice.prototype.play=function(){this.paused=false};AudioletDevice.prototype.toString=function(){return"Audio Output Device"};var AudioletInput=function(b,a){this.node=b;this.index=a;this.connectedFrom=[];this.samples=[]};AudioletInput.prototype.connect=function(a){this.connectedFrom.push(a)};AudioletInput.prototype.disconnect=function(a){var c=this.connectedFrom.length;for(var b=0;b<c;b++){if(a==this.connectedFrom[b]){this.connectedFrom.splice(b,1);break}}if(this.connectedFrom.length==0){this.samples=[]}};AudioletInput.prototype.toString=function(){return this.node.toString()+"Input #"+this.index};var Audiolet=function(b,a,c){this.output=new AudioletDestination(this,b,a,c)};var AudioletOutput=function(b,a){this.node=b;this.index=a;this.connectedTo=[];this.samples=[];this.linkedInput=null;this.numberOfChannels=1};AudioletOutput.prototype.connect=function(a){this.connectedTo.push(a)};AudioletOutput.prototype.disconnect=function(a){var c=this.connectedTo.length;for(var b=0;b<c;b++){if(a==this.connectedTo[b]){this.connectedTo.splice(b,1);break}}};AudioletOutput.prototype.linkNumberOfChannels=function(a){this.linkedInput=a};AudioletOutput.prototype.unlinkNumberOfChannels=function(){this.linkedInput=null};AudioletOutput.prototype.getNumberOfChannels=function(){if(this.linkedInput&&this.linkedInput.connectedFrom.length){return(this.linkedInput.samples.length)}return(this.numberOfChannels)};AudioletOutput.prototype.toString=function(){return this.node.toString()+"Output #"+this.index+" - "};var AudioletParameter=function(a,c,b){this.node=a;if(typeof c!="undefined"&&c!=null){this.input=a.inputs[c]}else{this.input=null}this.value=b||0};AudioletParameter.prototype.isStatic=function(){return(this.input.samples.length==0)};AudioletParameter.prototype.isDynamic=function(){return(this.input.samples.length>0)};AudioletParameter.prototype.setValue=function(a){this.value=a};AudioletParameter.prototype.getValue=function(){if(this.input!=null&&this.input.samples.length>0){return this.input.samples[0]}else{return this.value}};function extend(a,c){function b(){}b.prototype=c.prototype;a.prototype=new b();a.prototype.constructor=a}
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var ParameterNode=function(a,b){AudioletNode.call(this,a,1,1);this.parameter=new AudioletParameter(this,0,b)};extend(ParameterNode,AudioletNode);ParameterNode.prototype.generate=function(){this.outputs[0].samples[0]=this.parameter.getValue()};ParameterNode.prototype.toString=function(){return"Parameter Node"};
+/*!
+ * @depends AudioletNode.js
+ */
+var PassThroughNode=function(b,a,c){AudioletNode.call(this,b,a,c)};extend(PassThroughNode,AudioletNode);PassThroughNode.prototype.createOutputSamples=function(){var f=this.outputs.length;for(var e=0;e<f;e++){var b=this.inputs[e];var a=this.outputs[e];if(b&&b.samples.length!=0){a.samples=b.samples}else{var d=a.getNumberOfChannels();if(a.samples.length==d){continue}else{if(a.samples.length>d){a.samples=a.samples.slice(0,d);continue}}for(var c=a.samples.length;c<d;c++){a.samples[c]=0}}}};PassThroughNode.prototype.toString=function(){return"Pass Through Node"};var PriorityQueue=function(c,b){if(b){this.compare=b}if(c){this.heap=c;for(var a=0;a<Math.floor(this.heap.length/2);a++){this.siftUp(a)}}else{this.heap=[]}};PriorityQueue.prototype.push=function(a){this.heap.push(a);this.siftDown(0,this.heap.length-1)};PriorityQueue.prototype.pop=function(){var b,a;b=this.heap.pop();if(this.heap.length){var a=this.heap[0];this.heap[0]=b;this.siftUp(0)}else{a=b}return(a)};PriorityQueue.prototype.peek=function(){return(this.heap[0])};PriorityQueue.prototype.isEmpty=function(){return(this.heap.length==0)};PriorityQueue.prototype.siftDown=function(d,a){var b=this.heap[a];while(a>d){var e=(a-1)>>1;var c=this.heap[e];if(this.compare(b,c)){this.heap[a]=c;a=e;continue}break}this.heap[a]=b};PriorityQueue.prototype.siftUp=function(a){var e=this.heap.length;var d=a;var c=this.heap[a];var b=2*a+1;while(b<e){var f=b+1;if(f<e&&!this.compare(this.heap[b],this.heap[f])){b=f}this.heap[a]=this.heap[b];a=b;b=2*a+1}this.heap[a]=c;this.siftDown(d,a)};PriorityQueue.prototype.compare=function(d,c){return(d<c)};
+/*!
+ * @depends PassThroughNode.js
+ */
+var Scheduler=function(a,b){PassThroughNode.call(this,a,1,1);this.linkNumberOfOutputChannels(0,0);this.bpm=b||120;this.queue=new PriorityQueue(null,function(d,c){return(d.time<c.time)});this.time=0;this.beat=0;this.beatInBar=0;this.bar=0;this.seconds=0;this.beatsPerBar=0;this.lastBeatTime=0;this.beatLength=60/this.bpm*this.audiolet.device.sampleRate};extend(Scheduler,PassThroughNode);Scheduler.prototype.setTempo=function(a){this.bpm=a;this.beatLength=60/this.bpm*this.audiolet.device.sampleRate};Scheduler.prototype.addRelative=function(a,c){var b={};b.callback=c;b.time=this.time+a*this.beatLength;this.queue.push(b);return b};Scheduler.prototype.addAbsolute=function(b,c){if(b<this.beat||b==this.beat&&this.time>this.lastBeatTime){return null}var a={};a.callback=c;a.time=this.lastBeatTime+(b-this.beat)*this.beatLength;this.queue.push(a);return a};Scheduler.prototype.play=function(b,a,d){var c={};c.patterns=b;c.durationPattern=a;c.callback=d;c.time=this.audiolet.device.getWriteTime();this.queue.push(c);return c};Scheduler.prototype.playAbsolute=function(d,b,a,e){if(d<this.beat||d==this.beat&&this.time>this.lastBeatTime){return null}var c={};c.patterns=b;c.durationPattern=a;c.callback=e;c.time=this.lastBeatTime+(d-this.beat)*this.beatLength;this.queue.push(c);return c};Scheduler.prototype.remove=function(b){var a=this.queue.heap.indexOf(b);if(a!=-1){this.queue.heap.splice(a,1);this.queue=new PriorityQueue(this.queue.heap,function(d,c){return(d.time<c.time)})}};Scheduler.prototype.stop=function(a){this.remove(a)};Scheduler.prototype.tick=function(){PassThroughNode.prototype.tick.call(this);this.tickClock();while(!this.queue.isEmpty()&&this.queue.peek().time<=this.time){var a=this.queue.pop();this.processEvent(a)}};Scheduler.prototype.tickClock=function(){this.time+=1;this.seconds=this.time/this.audiolet.device.sampleRate;if(this.time>=this.lastBeatTime+this.beatLength){this.beat+=1;this.beatInBar+=1;if(this.beatInBar==this.beatsPerBar){this.bar+=1;this.beatInBar=0}this.lastBeatTime+=this.beatLength}};Scheduler.prototype.processEvent=function(a){var h=a.durationPattern;if(h){var g=[];var b=a.patterns;var e=b.length;for(var d=0;d<e;d++){var f=b[d];var j=f.next();if(j!=null){g.push(j)}else{return}}a.callback.apply(null,g);var c;if(h instanceof Pattern){c=h.next()}else{c=h}if(c){a.time+=c*this.beatLength;this.queue.push(a)}}else{a.callback()}};Scheduler.prototype.toString=function(){return"Scheduler"};var Int8Array,Uint8Array,Int16Array,Uint16Array;var Int32Array,Uint32Array,Float32Array,Float64Array;var types=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var original,shim;for(var i=0;i<types.length;++i){if(types[i]){if(types[i].prototype.slice===undefined){original="subarray";shim="slice"}else{if(types[i].prototype.subarray===undefined){original="slice";shim="subarray"}}Object.defineProperty(types[i].prototype,shim,{value:types[i].prototype[original],enumerable:false})}}
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var Add=function(a,b){AudioletNode.call(this,a,2,1);this.linkNumberOfOutputChannels(0,0);this.value=new AudioletParameter(this,1,b||0)};extend(Add,AudioletNode);Add.prototype.generate=function(){var b=this.inputs[0];var a=this.outputs[0];var e=this.value.getValue();var c=b.samples.length;for(var d=0;d<c;d++){a.samples[d]=b.samples[d]+e}};Add.prototype.toString=function(){return"Add"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var Divide=function(a,b){AudioletNode.call(this,a,2,1);this.linkNumberOfOutputChannels(0,0);this.value=new AudioletParameter(this,1,b||1)};extend(Divide,AudioletNode);Divide.prototype.generate=function(){var b=this.inputs[0];var a=this.outputs[0];var e=this.value.getValue();var c=b.samples.length;for(var d=0;d<c;d++){a.samples[d]=b.samples[d]/e}};Divide.prototype.toString=function(){return"Divide"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var Modulo=function(a,b){AudioletNode.call(this,a,2,1);this.linkNumberOfOutputChannels(0,0);this.value=new AudioletParameter(this,1,b||1)};extend(Modulo,AudioletNode);Modulo.prototype.generate=function(){var b=this.inputs[0];var a=this.outputs[0];var e=this.value.getValue();var c=b.samples.length;for(var d=0;d<c;d++){a.samples[d]=b.samples[d]%e}};Modulo.prototype.toString=function(){return"Modulo"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var MulAdd=function(a,c,b){AudioletNode.call(this,a,3,1);this.linkNumberOfOutputChannels(0,0);this.mul=new AudioletParameter(this,1,c||1);this.add=new AudioletParameter(this,2,b||0)};extend(MulAdd,AudioletNode);MulAdd.prototype.generate=function(){var b=this.inputs[0];var a=this.outputs[0];var f=this.mul.getValue();var e=this.add.getValue();var c=b.samples.length;for(var d=0;d<c;d++){a.samples[d]=b.samples[d]*f+e}};MulAdd.prototype.toString=function(){return"Multiplier/Adder"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var Multiply=function(a,b){AudioletNode.call(this,a,2,1);this.linkNumberOfOutputChannels(0,0);this.value=new AudioletParameter(this,1,b||1)};extend(Multiply,AudioletNode);Multiply.prototype.generate=function(){var d=this.value.getValue();var a=this.inputs[0];var b=a.samples.length;for(var c=0;c<b;c++){this.outputs[0].samples[c]=a.samples[c]*d}};Multiply.prototype.toString=function(){return"Multiply"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var Reciprocal=function(a){AudioletNode.call(this,a,1,1);this.linkNumberOfOutputChannels(0,0)};extend(Reciprocal,AudioletNode);Reciprocal.prototype.generate=function(){var b=this.inputs[0];var a=this.outputs[0];var c=b.samples.length;for(var d=0;d<c;d++){a.samples[d]=1/b.samples[d]}};Reciprocal.prototype.toString=function(){return"Reciprocal"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var Subtract=function(a,b){AudioletNode.call(this,a,2,1);this.linkNumberOfOutputChannels(0,0);this.value=new AudioletParameter(this,1,b||0)};extend(Subtract,AudioletNode);Subtract.prototype.generate=function(){var b=this.inputs[0];var a=this.outputs[0];var e=this.value.getValue();var c=b.samples.length;for(var d=0;d<c;d++){a.samples[d]=b.samples[d]-e}};Subtract.prototype.toString=function(){return"Subtract"};var Tanh=function(a){AudioletNode.call(this,a,1,1);this.linkNumberOfOutputChannels(0,0)};extend(Tanh,AudioletNode);Tanh.prototype.generate=function(){var b=this.inputs[0];var a=this.outputs[0];var c=b.samples.length;for(var d=0;d<c;d++){var e=b.samples[d];a.samples[d]=(Math.exp(e)-Math.exp(-e))/(Math.exp(e)+Math.exp(-e))}};Tanh.prototype.toString=function(){return("Tanh")};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var Envelope=function(b,d,e,g,a,f){AudioletNode.call(this,b,1,1);this.gate=new AudioletParameter(this,0,d||1);this.levels=[];for(var c=0;c<e.length;c++){this.levels.push(new AudioletParameter(this,null,e[c]))}this.times=[];for(var c=0;c<g.length;c++){this.times.push(new AudioletParameter(this,null,g[c]))}this.releaseStage=a;this.onComplete=f;this.stage=null;this.time=null;this.changeTime=null;this.level=this.levels[0].getValue();this.delta=0;this.gateOn=false};extend(Envelope,AudioletNode);Envelope.prototype.generate=function(){var b=this.gate.getValue();var a=false;if(b&&!this.gateOn){this.gateOn=true;this.stage=0;this.time=0;this.delta=0;this.level=this.levels[0].getValue();if(this.stage!=this.releaseStage){a=true}}if(this.gateOn&&!b){this.gateOn=false;if(this.releaseStage!=null){this.stage=this.releaseStage;a=true}}if(this.changeTime){this.time+=1;if(this.time>=this.changeTime){this.stage+=1;if(this.stage!=this.releaseStage){a=true}else{this.changeTime=null;this.delta=0}}}if(a){if(this.stage!=this.times.length){this.delta=this.calculateDelta(this.stage,this.level);this.changeTime=this.calculateChangeTime(this.stage,this.time)}else{if(this.onComplete){this.onComplete()}this.stage=null;this.time=null;this.changeTime=null;this.delta=0}}this.level+=this.delta;this.outputs[0].samples[0]=this.level};Envelope.prototype.calculateDelta=function(b,d){var c=this.levels[b+1].getValue()-d;var a=this.times[b].getValue()*this.audiolet.device.sampleRate;return(c/a)};Envelope.prototype.calculateChangeTime=function(b,c){var a=this.times[b].getValue()*this.audiolet.device.sampleRate;return(c+a)};Envelope.prototype.toString=function(){return"Envelope"};
+/*!
+ * @depends Envelope.js
+ */
+var ADSREnvelope=function(j,e,c,d,g,f,b){var h=[0,1,g,0];var a=[c,d,f];Envelope.call(this,j,e,h,a,2,b);this.attack=this.times[0];this.decay=this.times[1];this.sustain=this.levels[2];this.release=this.levels[2]};extend(ADSREnvelope,Envelope);ADSREnvelope.prototype.toString=function(){return"ADSR Envelope"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var BiquadFilter=function(a,b){AudioletNode.call(this,a,2,1);this.linkNumberOfOutputChannels(0,0);this.frequency=new AudioletParameter(this,1,b||22100);this.lastFrequency=null;this.xValues=[];this.yValues=[];this.b0=0;this.b1=0;this.b2=0;this.a0=0;this.a1=0;this.a2=0};extend(BiquadFilter,AudioletNode);BiquadFilter.prototype.calculateCoefficients=function(a){};BiquadFilter.prototype.generate=function(){var k=this.inputs[0];var j=this.outputs[0];var m=this.xValues;var p=this.yValues;var l=this.frequency.getValue();if(l!=this.lastFrequency){this.calculateCoefficients(l);this.lastFrequency=l}var v=this.a0;var u=this.a1;var t=this.a2;var h=this.b0;var g=this.b1;var f=this.b2;var d=k.samples.length;for(var n=0;n<d;n++){if(n>=m.length){m.push([0,0]);p.push([0,0])}var r=m[n];var q=r[0];var o=r[1];var e=p[n];var b=e[0];var a=e[1];var s=k.samples[n];var c=(h/v)*s+(g/v)*q+(f/v)*o-(u/v)*b-(t/v)*a;j.samples[n]=c;r[0]=s;r[1]=q;e[0]=c;e[1]=b}};BiquadFilter.prototype.toString=function(){return"Biquad Filter"};
+/*!
+ * @depends BiquadFilter.js
+ */
+var AllPassFilter=function(a,b){BiquadFilter.call(this,a,b)};extend(AllPassFilter,BiquadFilter);AllPassFilter.prototype.calculateCoefficients=function(e){var b=2*Math.PI*e/this.audiolet.device.sampleRate;var a=Math.cos(b);var c=Math.sin(b);var d=c/(2/Math.sqrt(2));this.b0=1-d;this.b1=-2*a;this.b2=1+d;this.a0=1+d;this.a1=-2*a;this.a2=1-d};AllPassFilter.prototype.toString=function(){return"All Pass Filter"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var Amplitude=function(b,c,a){AudioletNode.call(this,b,3,1);this.linkNumberOfOutputChannels(0,0);this.followers=[];this.attack=new AudioletParameter(this,1,c||0.01);this.release=new AudioletParameter(this,2,a||0.01)};extend(Amplitude,AudioletNode);Amplitude.prototype.generate=function(){var h=this.inputs[0];var a=this.outputs[0];var f=this.followers;var b=f.length;var l=this.audiolet.device.sampleRate;var d=this.attack.getValue();d=Math.pow(0.01,1/(d*l));var j=this.release.getValue();j=Math.pow(0.01,1/(j*l));var g=h.samples.length;for(var c=0;c<g;c++){if(c>=b){f.push(0)}var e=f[c];var k=Math.abs(h.samples[c]);if(k>e){e=d*(e-k)+k}else{e=j*(e-k)+k}a.samples[c]=e;f[c]=e}};Amplitude.prototype.toString=function(){return("Amplitude")};
+/*!
+ * @depends ../core/PassThroughNode.js
+ */
+var BadValueDetector=function(a,b){PassThroughNode.call(this,a,1,1);this.linkNumberOfOutputChannels(0,0);if(b){this.callback=b}};extend(BadValueDetector,PassThroughNode);BadValueDetector.prototype.callback=function(b,a){console.error(b+" detected at channel "+a)};BadValueDetector.prototype.generate=function(){var a=this.inputs[0];var b=a.samples.length;for(var c=0;c<b;c++){var d=a.samples[c];if(typeof d=="undefined"||d==null||isNaN(d)||d==Infinity||d==-Infinity){this.callback(d,c)}}};BadValueDetector.prototype.toString=function(){return"Bad Value Detector"};
+/*!
+ * @depends BiquadFilter.js
+ */
+var BandPassFilter=function(a,b){BiquadFilter.call(this,a,b)};extend(BandPassFilter,BiquadFilter);BandPassFilter.prototype.calculateCoefficients=function(e){var b=2*Math.PI*e/this.audiolet.device.sampleRate;var a=Math.cos(b);var c=Math.sin(b);var d=c/(2/Math.sqrt(2));this.b0=d;this.b1=0;this.b2=-d;this.a0=1+d;this.a1=-2*a;this.a2=1-d};BandPassFilter.prototype.toString=function(){return"Band Pass Filter"};
+/*!
+ * @depends BiquadFilter.js
+ */
+var BandRejectFilter=function(a,b){BiquadFilter.call(this,a,b)};extend(BandRejectFilter,BiquadFilter);BandRejectFilter.prototype.calculateCoefficients=function(e){var b=2*Math.PI*e/this.audiolet.device.sampleRate;var a=Math.cos(b);var c=Math.sin(b);var d=c/(2/Math.sqrt(2));this.b0=1;this.b1=-2*a;this.b2=1;this.a0=1+d;this.a1=-2*a;this.a2=1-d};BandRejectFilter.prototype.toString=function(){return"Band Reject Filter"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var BitCrusher=function(a,b){AudioletNode.call(this,a,2,1);this.linkNumberOfOutputChannels(0,0);this.bits=new AudioletParameter(this,1,b)};extend(BitCrusher,AudioletNode);BitCrusher.prototype.generate=function(){var a=this.inputs[0];var d=Math.pow(2,this.bits.getValue())-1;var b=a.samples.length;for(var c=0;c<b;c++){this.outputs[0].samples[c]=Math.floor(a.samples[c]*d)/d}};BitCrusher.prototype.toString=function(){return"BitCrusher"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var BufferPlayer=function(c,b,e,d,a,f){AudioletNode.call(this,c,3,1);this.buffer=b;this.setNumberOfOutputChannels(0,this.buffer.numberOfChannels);this.position=d||0;this.playbackRate=new AudioletParameter(this,0,e||1);this.restartTrigger=new AudioletParameter(this,1,0);this.startPosition=new AudioletParameter(this,2,d||0);this.loop=new AudioletParameter(this,3,a||0);this.onComplete=f;this.restartTriggerOn=false;this.playing=true};extend(BufferPlayer,AudioletNode);BufferPlayer.prototype.generate=function(){var c=this.outputs[0];var d=c.samples.length;if(this.buffer.length==0||!this.playing){for(var e=0;e<d;e++){c.samples[e]=0}return}var g=this.playbackRate.getValue();var b=this.restartTrigger.getValue();var f=this.startPosition.getValue();var a=this.loop.getValue();if(b>0&&!this.restartTriggerOn){this.position=f;this.restartTriggerOn=true;this.playing=true}if(b<=0&&this.restartTriggerOn){this.restartTriggerOn=false}var d=this.buffer.channels.length;for(var e=0;e<d;e++){var h=this.buffer.getChannelData(e);c.samples[e]=h[Math.floor(this.position)]}this.position+=g;if(this.position>=this.buffer.length){if(a){this.position%=this.buffer.length}else{this.playing=false;if(this.onComplete){this.onComplete()}}}};BufferPlayer.prototype.toString=function(){return("Buffer player")};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var CombFilter=function(a,d,c,b){AudioletNode.call(this,a,3,1);this.linkNumberOfOutputChannels(0,0);this.maximumDelayTime=d;this.delayTime=new AudioletParameter(this,1,c||1);this.decayTime=new AudioletParameter(this,2,b);this.buffers=[];this.readWriteIndex=0};extend(CombFilter,AudioletNode);CombFilter.prototype.generate=function(){var k=this.inputs[0];var c=this.outputs[0];var l=this.audiolet.device.sampleRate;var f=this.delayTime.getValue()*l;var h=this.decayTime.getValue()*l;var a=Math.exp(-3*f/h);var j=k.samples.length;for(var e=0;e<j;e++){if(e>=this.buffers.length){var b=this.maximumDelayTime*l;this.buffers.push(new Float32Array(b))}var d=this.buffers[e];var g=d[this.readWriteIndex];c.samples[e]=g;d[this.readWriteIndex]=k.samples[e]+a*g}this.readWriteIndex+=1;if(this.readWriteIndex>=f){this.readWriteIndex=0}};CombFilter.prototype.toString=function(){return"Comb Filter"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var Sine=function(a,b){AudioletNode.call(this,a,1,1);this.frequency=new AudioletParameter(this,0,b||440);this.phase=0};extend(Sine,AudioletNode);Sine.prototype.generate=function(){var a=this.outputs[0];var c=this.frequency.getValue();var b=this.audiolet.device.sampleRate;a.samples[0]=Math.sin(this.phase);this.phase+=2*Math.PI*c/b;if(this.phase>2*Math.PI){this.phase%=2*Math.PI}};Sine.prototype.toString=function(){return"Sine"};
+/*!
+ * @depends ../core/AudioletNode.js
+ * @depends Sine.js
+ */
+var CrossFade=function(b,a){AudioletNode.call(this,b,3,1);this.linkNumberOfOutputChannels(0,0);this.position=new AudioletParameter(this,2,a||0.5)};extend(CrossFade,AudioletNode);CrossFade.prototype.generate=function(){var j=this.inputs[0];var h=this.inputs[1];var c=this.outputs[0];var g=this.position.getValue();var b=g*Math.PI/2;var f=Math.cos(b);var e=Math.sin(b);var k=c.samples.length;for(var d=0;d<k;d++){var a=j.samples[d]||0;var l=h.samples[d]||0;c.samples[d]=a*f+l*e}};CrossFade.prototype.toString=function(){return"Cross Fader"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var DampedCombFilter=function(b,e,d,c,a){AudioletNode.call(this,b,4,1);this.linkNumberOfOutputChannels(0,0);this.maximumDelayTime=e;this.delayTime=new AudioletParameter(this,1,d||1);this.decayTime=new AudioletParameter(this,2,c);this.damping=new AudioletParameter(this,3,a);var f=e*this.audiolet.device.sampleRate;this.buffers=[];this.readWriteIndex=0;this.filterStores=[]};extend(DampedCombFilter,AudioletNode);DampedCombFilter.prototype.generate=function(){var m=this.inputs[0];var c=this.outputs[0];var n=this.audiolet.device.sampleRate;var g=this.delayTime.getValue()*n;var k=this.decayTime.getValue()*n;var d=this.damping.getValue();var a=Math.exp(-3*g/k);var l=m.samples.length;for(var f=0;f<l;f++){if(f>=this.buffers.length){var b=this.maximumDelayTime*n;this.buffers.push(new Float32Array(b))}if(f>=this.filterStores.length){this.filterStores.push(0)}var e=this.buffers[f];var h=this.filterStores[f];var j=e[this.readWriteIndex];h=(j*(1-d))+(h*d);c.samples[f]=j;e[this.readWriteIndex]=m.samples[f]+a*h;this.filterStores[f]=h}this.readWriteIndex+=1;if(this.readWriteIndex>=g){this.readWriteIndex=0}};DampedCombFilter.prototype.toString=function(){return"Damped Comb Filter"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var DCFilter=function(b,a){AudioletNode.call(this,b,2,1);this.linkNumberOfOutputChannels(0,0);this.coefficient=new AudioletParameter(this,1,a||0.995);this.xValues=[];this.yValues=[]};extend(DCFilter,AudioletNode);DCFilter.prototype.generate=function(){var c=this.coefficient.getValue();var a=this.inputs[0];var b=a.samples.length;for(var e=0;e<b;e++){if(e>=this.xValues.length){this.xValues.push(0)}if(e>=this.yValues.length){this.yValues.push(0)}var d=a.samples[e];var f=d-this.xValues[e]+c*this.yValues[e];this.outputs[0].samples[e]=f;this.xValues[e]=d;this.yValues[e]=f}};DCFilter.prototype.toString=function(){return"DC Filter"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var Delay=function(a,c,b){AudioletNode.call(this,a,2,1);this.linkNumberOfOutputChannels(0,0);this.maximumDelayTime=c;this.delayTime=new AudioletParameter(this,1,b||1);var d=c*this.audiolet.device.sampleRate;this.buffers=[];this.readWriteIndex=0};extend(Delay,AudioletNode);Delay.prototype.generate=function(){var c=this.inputs[0];var b=this.outputs[0];var e=this.audiolet.device.sampleRate;var g=this.delayTime.getValue()*e;var d=c.samples.length;for(var f=0;f<d;f++){if(f>=this.buffers.length){var h=this.maximumDelayTime*e;this.buffers.push(new Float32Array(h))}var a=this.buffers[f];b.samples[f]=a[this.readWriteIndex];a[this.readWriteIndex]=c.samples[f]}this.readWriteIndex+=1;if(this.readWriteIndex>=g){this.readWriteIndex=0}};Delay.prototype.toString=function(){return"Delay"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var DiscontinuityDetector=function(b,a,c){AudioletNode.call(this,b,1,1);this.linkNumberOfOutputChannels(0,0);this.threshold=a||0.2;if(c){this.callback=c}this.lastValues=[]};extend(DiscontinuityDetector,AudioletNode);DiscontinuityDetector.prototype.callback=function(a,b){console.error("Discontinuity of "+a+" detected on channel "+b)};DiscontinuityDetector.prototype.generate=function(){var b=this.inputs[0];var a=this.outputs[0];var c=b.samples.length;for(var d=0;d<c;d++){if(d>=this.lastValues.length){this.lastValues.push(0)}var e=b.samples[d];var f=Math.abs(this.lastValues[d]-e);if(f>this.threshold){this.callback(f,d)}this.lastValues[d]=e}};DiscontinuityDetector.prototype.toString=function(){return"Discontinuity Detector"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var FeedbackDelay=function(b,e,d,a,c){AudioletNode.call(this,b,4,1);this.linkNumberOfOutputChannels(0,0);this.maximumDelayTime=e;this.delayTime=new AudioletParameter(this,1,d||1);this.feedback=new AudioletParameter(this,2,a||0.5);this.mix=new AudioletParameter(this,3,c||1);var f=e*this.audiolet.device.sampleRate;this.buffers=[];this.readWriteIndex=0};extend(FeedbackDelay,AudioletNode);FeedbackDelay.prototype.generate=function(){var j=this.inputs[0];var c=this.outputs[0];var k=this.audiolet.output.device.sampleRate;var g=this.delayTime.getValue()*k;var a=this.feedback.getValue();var n=this.mix.getValue();var h=j.samples.length;var d=this.buffers.length;for(var f=0;f<h;f++){if(f>=d){var b=this.maximumDelayTime*k;this.buffers.push(new Float32Array(b))}var e=this.buffers[f];var m=j.samples[f];var l=e[this.readWriteIndex];c.samples[f]=n*l+(1-n)*m;e[this.readWriteIndex]=m+a*l}this.readWriteIndex+=1;if(this.readWriteIndex>=g){this.readWriteIndex=0}};FeedbackDelay.prototype.toString=function(){return"Feedback Delay"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var FFT=function(a,b){AudioletNode.call(this,a,2,1);this.linkNumberOfOutputChannels(0,0);this.bufferSize=b;this.readWriteIndex=0;this.buffer=new Float32Array(this.bufferSize);this.realBuffer=new Float32Array(this.bufferSize);this.imaginaryBuffer=new Float32Array(this.bufferSize);this.reverseTable=new Uint32Array(this.bufferSize);this.calculateReverseTable()};extend(FFT,AudioletNode);FFT.prototype.generate=function(){var b=this.inputs[0];var a=this.outputs[0];if(b.samples.length==0){return}this.buffer[this.readWriteIndex]=b.samples[0];a.samples[0]=[this.realBuffer[this.readWriteIndex],this.imaginaryBuffer[this.readWriteIndex]];this.readWriteIndex+=1;if(this.readWriteIndex>=this.bufferSize){this.transform();this.readWriteIndex=0}};FFT.prototype.calculateReverseTable=function(){var a=1;var c=this.bufferSize>>1;while(a<this.bufferSize){for(var b=0;b<a;b++){this.reverseTable[b+a]=this.reverseTable[b]+c}a=a<<1;c=c>>1}};FFT.prototype.transform=function(){for(var e=0;e<this.bufferSize;e++){this.realBuffer[e]=this.buffer[this.reverseTable[e]];this.imaginaryBuffer[e]=0}var f=1;while(f<this.bufferSize){var l=Math.cos(-Math.PI/f);var c=Math.sin(-Math.PI/f);var g=1;var k=0;for(var b=0;b<f;b++){var e=b;while(e<this.bufferSize){var d=e+f;var h=(g*this.realBuffer[d])-(k*this.imaginaryBuffer[d]);var a=(g*this.imaginaryBuffer[d])+(k*this.realBuffer[d]);this.realBuffer[d]=this.realBuffer[e]-h;this.imaginaryBuffer[d]=this.imaginaryBuffer[e]-a;this.realBuffer[e]+=h;this.imaginaryBuffer[e]+=a;e+=f<<1}var j=g;g=(j*l)-(k*c);k=(j*c)+(k*l)}f=f<<1}};FFT.prototype.toString=function(){return"FFT"};
+/*!
+ * @depends ../operators/Multiply.js
+ */
+var Gain=function(a,b){Multiply.call(this,a,b);this.gain=this.value};extend(Gain,Multiply);Gain.prototype.toString=function(){return("Gain")};
+/*!
+ * @depends BiquadFilter.js
+ */
+var HighPassFilter=function(a,b){BiquadFilter.call(this,a,b)};extend(HighPassFilter,BiquadFilter);HighPassFilter.prototype.calculateCoefficients=function(e){var b=2*Math.PI*e/this.audiolet.device.sampleRate;var a=Math.cos(b);var c=Math.sin(b);var d=c/(2/Math.sqrt(2));this.b0=(1+a)/2;this.b1=-(1+a);this.b2=this.b0;this.a0=1+d;this.a1=-2*a;this.a2=1-d};HighPassFilter.prototype.toString=function(){return"High Pass Filter"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var IFFT=function(a,b){AudioletNode.call(this,a,2,1);this.linkNumberOfOutputChannels(0,0);this.bufferSize=b;this.readWriteIndex=0;this.buffer=new Float32Array(this.bufferSize);this.realBuffer=new Float32Array(this.bufferSize);this.imaginaryBuffer=new Float32Array(this.bufferSize);this.reverseTable=new Uint32Array(this.bufferSize);this.calculateReverseTable();this.reverseReal=new Float32Array(this.bufferSize);this.reverseImaginary=new Float32Array(this.bufferSize)};extend(IFFT,AudioletNode);IFFT.prototype.generate=function(){var c=this.inputs[0];var b=this.outputs[0];if(!c.samples.length){return}var a=c.samples[0];this.realBuffer[this.readWriteIndex]=a[0];this.imaginaryBuffer[this.readWriteIndex]=a[1];b.samples[0]=this.buffer[this.readWriteIndex];this.readWriteIndex+=1;if(this.readWriteIndex>=this.bufferSize){this.transform();this.readWriteIndex=0}};IFFT.prototype.calculateReverseTable=function(){var a=1;var c=this.bufferSize>>1;while(a<this.bufferSize){for(var b=0;b<a;b++){this.reverseTable[b+a]=this.reverseTable[b]+c}a=a<<1;c=c>>1}};IFFT.prototype.transform=function(){var e=1;for(var f=0;f<this.bufferSize;f++){this.imaginaryBuffer[f]*=-1}for(var f=0;f<this.bufferSize;f++){this.reverseReal[f]=this.realBuffer[this.reverseTable[f]];this.reverseImaginary[f]=this.imaginaryBuffer[this.reverseTable[f]]}this.realBuffer.set(this.reverseReal);this.imaginaryBuffer.set(this.reverseImaginary);while(e<this.bufferSize){var l=Math.cos(-Math.PI/e);var c=Math.sin(-Math.PI/e);var g=1;var k=0;for(var b=0;b<e;b++){f=b;while(f<this.bufferSize){var d=f+e;var h=(g*this.realBuffer[d])-(k*this.imaginaryBuffer[d]);var a=(g*this.imaginaryBuffer[d])+(k*this.realBuffer[d]);this.realBuffer[d]=this.realBuffer[f]-h;this.imaginaryBuffer[d]=this.imaginaryBuffer[f]-a;this.realBuffer[f]+=h;this.imaginaryBuffer[f]+=a;f+=e<<1}var j=g;g=(j*l)-(k*c);k=(j*c)+(k*l)}e=e<<1}for(f=0;f<this.bufferSize;f++){this.buffer[f]=this.realBuffer[f]/this.bufferSize}};IFFT.prototype.toString=function(){return"IFFT"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var Lag=function(a,c,b){AudioletNode.call(this,a,2,1);this.value=new AudioletParameter(this,0,c||0);this.lag=new AudioletParameter(this,1,b||1);this.lastValue=0;this.log001=Math.log(0.001)};extend(Lag,AudioletNode);Lag.prototype.generate=function(){var c=this.inputs[0];var b=this.outputs[0];var e=this.audiolet.device.sampleRate;var f=this.value.getValue();var g=this.lag.getValue();var d=Math.exp(this.log001/(g*e));var a=((1-d)*f)+(d*this.lastValue);b.samples[0]=a;this.lastValue=a};Lag.prototype.toString=function(){return"Lag"};
+/*!
+ * @depends ../core/AudioletGroup.js
+ */
+var Limiter=function(c,b,d,a){AudioletNode.call(this,c,4,1);this.linkNumberOfOutputChannels(0,0);this.threshold=new AudioletParameter(this,1,b||0.95);this.attack=new AudioletParameter(this,2,d||0.01);this.release=new AudioletParameter(this,2,a||0.4);this.followers=[]};extend(Limiter,AudioletNode);Limiter.prototype.generate=function(){var h=this.inputs[0];var a=this.outputs[0];var m=this.audiolet.device.sampleRate;var b=Math.pow(0.01,1/(this.attack.getValue()*m));var j=Math.pow(0.01,1/(this.release.getValue()*m));var d=this.threshold.getValue();var g=h.samples.length;for(var c=0;c<g;c++){if(c>=this.followers.length){this.followers.push(0)}var f=this.followers[c];var l=h.samples[c];var e=Math.abs(l);if(e>f){f=b*(f-e)+e}else{f=j*(f-e)+e}var k=f-d;if(k>0){a.samples[c]=l/(1+k)}else{a.samples[c]=l}this.followers[c]=f}};Limiter.prototype.toString=function(){return"Limiter"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var LinearCrossFade=function(b,a){AudioletNode.call(this,b,3,1);this.linkNumberOfOutputChannels(0,0);this.position=new AudioletParameter(this,2,a||0.5)};extend(LinearCrossFade,AudioletNode);LinearCrossFade.prototype.generate=function(){var h=this.inputs[0];var g=this.inputs[1];var b=this.outputs[0];var f=this.position.getValue();var e=1-f;var c=f;var j=b.samples.length;for(var d=0;d<j;d++){var a=h.samples[d]||0;var k=g.samples[d]||0;b.samples[d]=a*e+k*c}};LinearCrossFade.prototype.toString=function(){return"Linear Cross Fader"};
+/*!
+ * @depends BiquadFilter.js
+ */
+var LowPassFilter=function(a,b){BiquadFilter.call(this,a,b)};extend(LowPassFilter,BiquadFilter);LowPassFilter.prototype.calculateCoefficients=function(e){var b=2*Math.PI*e/this.audiolet.device.sampleRate;var a=Math.cos(b);var c=Math.sin(b);var d=c/(2/Math.sqrt(2));this.b0=(1-a)/2;this.b1=1-a;this.b2=this.b0;this.a0=1+d;this.a1=-2*a;this.a2=1-d};LowPassFilter.prototype.toString=function(){return"Low Pass Filter"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var Pan=function(a,b){AudioletNode.call(this,a,2,1);this.setNumberOfOutputChannels(0,2);if(b==null){var b=0.5}this.pan=new AudioletParameter(this,1,b)};extend(Pan,AudioletNode);Pan.prototype.generate=function(){var c=this.inputs[0];var b=this.outputs[0];var e=this.pan.getValue();var d=c.samples[0]||0;var a=e*Math.PI/2;b.samples[0]=d*Math.cos(a);b.samples[1]=d*Math.sin(a)};Pan.prototype.toString=function(){return"Stereo Panner"};
+/*!
+ * @depends Envelope.js
+ */
+var PercussiveEnvelope=function(b,c,g,a,e){var d=[0,1,0];var f=[g,a];Envelope.call(this,b,c,d,f,null,e);this.attack=this.times[0];this.release=this.times[1]};extend(PercussiveEnvelope,Envelope);PercussiveEnvelope.prototype.toString=function(){return"Percussive Envelope"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var Pulse=function(a,b,c){AudioletNode.call(this,a,2,1);this.frequency=new AudioletParameter(this,0,b||440);this.pulseWidth=new AudioletParameter(this,1,c||0.5);this.phase=0};extend(Pulse,AudioletNode);Pulse.prototype.generate=function(){var c=this.pulseWidth.getValue();this.outputs[0].samples[0]=(this.phase<c)?1:-1;var b=this.frequency.getValue();var a=this.audiolet.device.sampleRate;this.phase+=b/a;if(this.phase>1){this.phase%=1}};Pulse.prototype.toString=function(){return"Pulse"};
+/*!
+ * @depends ../core/AudioletNode.js
+ * @depends ../core/AudioletGroup.js
+ */
+var Reverb=function(d,g,a,c){AudioletNode.call(this,d,4,1);this.initialMix=0.33;this.fixedGain=0.015;this.initialDamping=0.5;this.scaleDamping=0.4;this.initialRoomSize=0.5;this.scaleRoom=0.28;this.offsetRoom=0.7;this.combTuning=[1116,1188,1277,1356,1422,1491,1557,1617];this.allPassTuning=[556,441,341,225];var g=g||this.initialMix;this.mix=new AudioletParameter(this,1,g);var a=a||this.initialRoomSize;this.roomSize=new AudioletParameter(this,2,a);var c=c||this.initialDamping;this.damping=new AudioletParameter(this,3,c);this.combBuffers=[];this.combIndices=[];this.filterStores=[];var b=this.combTuning.length;for(var f=0;f<b;f++){this.combBuffers.push(new Float32Array(this.combTuning[f]));this.combIndices.push(0);this.filterStores.push(0)}this.allPassBuffers=[];this.allPassIndices=[];var e=this.allPassTuning.length;for(var f=0;f<e;f++){this.allPassBuffers.push(new Float32Array(this.allPassTuning[f]));this.allPassIndices.push(0)}};extend(Reverb,AudioletNode);Reverb.prototype.generate=function(){var k=this.mix.getValue();var m=this.roomSize.getValue();var d=this.damping.getValue();var a=this.combTuning.length;var s=this.allPassTuning.length;var q=this.inputs[0].samples[0]||0;var j=q;q*=this.fixedGain;var c=q;var d=d*this.scaleDamping;var e=m*this.scaleRoom+this.offsetRoom;for(var r=0;r<a;r++){var o=this.combIndices[r];var p=this.combBuffers[r];var n=this.filterStores[r];var h=p[o];n=(h*(1-d))+(n*d);q+=h;p[o]=c+e*n;o+=1;if(o>=p.length){o=0}this.combIndices[r]=o;this.filterStores[r]=n}for(var r=0;r<s;r++){var b=this.allPassBuffers[r];var g=this.allPassIndices[r];var l=q;var f=b[g];q=-q+f;b[g]=l+(f*0.5);g+=1;if(g>=b.length){g=0}this.allPassIndices[r]=g}this.outputs[0].samples[0]=k*q+(1-k)*j};Reverb.prototype.toString=function(){return"Reverb"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var Saw=function(a,b){AudioletNode.call(this,a,1,1);this.frequency=new AudioletParameter(this,0,b||440);this.phase=0};extend(Saw,AudioletNode);Saw.prototype.generate=function(){var a=this.outputs[0];var c=this.frequency.getValue();var b=this.audiolet.device.sampleRate;a.samples[0]=((this.phase/2+0.25)%0.5-0.25)*4;this.phase+=c/b;if(this.phase>1){this.phase%=1}};Saw.prototype.toString=function(){return"Saw"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var SoftClip=function(a){AudioletNode.call(this,a,1,1);this.linkNumberOfOutputChannels(0,0)};extend(SoftClip,AudioletNode);SoftClip.prototype.generate=function(){var b=this.inputs[0];var a=this.outputs[0];var c=b.samples.length;for(var d=0;d<c;d++){var e=b.samples[d];if(e>0.5||e<-0.5){a.samples[d]=(Math.abs(e)-0.25)/e}else{a.samples[d]=e}}};SoftClip.prototype.toString=function(){return("SoftClip")};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var Square=function(a,b){AudioletNode.call(this,a,1,1);this.frequency=new AudioletParameter(this,0,b||440);this.phase=0};extend(Square,AudioletNode);Square.prototype.generate=function(){var a=this.outputs[0];var c=this.frequency.getValue();var b=this.audiolet.device.sampleRate;a.samples[0]=this.phase>0.5?1:-1;this.phase+=c/b;if(this.phase>1){this.phase%=1}};Square.prototype.toString=function(){return"Square"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var Triangle=function(a,b){AudioletNode.call(this,a,1,1);this.frequency=new AudioletParameter(this,0,b||440);this.phase=0};extend(Triangle,AudioletNode);Triangle.prototype.generate=function(){var a=this.outputs[0];var c=this.frequency.getValue();var b=this.audiolet.device.sampleRate;a.samples[0]=1-4*Math.abs((this.phase+0.25)%1-0.5);this.phase+=c/b;if(this.phase>1){this.phase%=1}};Triangle.prototype.toString=function(){return"Triangle"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var TriggerControl=function(b,a){AudioletNode.call(this,b,0,1);this.trigger=new AudioletParameter(this,null,a||0)};extend(TriggerControl,AudioletNode);TriggerControl.prototype.generate=function(){if(this.trigger.getValue()>0){this.outputs[0].samples[0]=1;this.trigger.setValue(0)}else{this.outputs[0].samples[0]=0}};TriggerControl.prototype.toString=function(){return"Trigger Control"};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var UpMixer=function(a,b){AudioletNode.call(this,a,1,1);this.outputs[0].numberOfChannels=b};extend(UpMixer,AudioletNode);UpMixer.prototype.generate=function(){var c=this.inputs[0];var b=this.outputs[0];var e=c.samples.length;var a=b.samples.length;if(e==a){b.samples=c.samples}else{for(var d=0;d<a;d++){b.samples[d]=c.samples[d%e]}}};UpMixer.prototype.toString=function(){return"UpMixer"};var WebKitBufferPlayer=function(a,b){AudioletNode.call(this,a,0,1);this.onComplete=b;this.isWebKit=this.audiolet.device.sink instanceof Sink.sinks.webkit;this.ready=false;this.setNumberOfOutputChannels(0,0);if(!this.isWebKit){return}this.context=this.audiolet.device.sink._context;this.jsNode=null;this.source=null;this.ready=false;this.loaded=false;this.buffers=[];this.readPosition=0;this.endTime=null};extend(WebKitBufferPlayer,AudioletNode);WebKitBufferPlayer.prototype.load=function(a,b,c){if(!this.isWebKit){return}this.stop();this.xhr=new XMLHttpRequest();this.xhr.open("GET",a,true);this.xhr.responseType="arraybuffer";this.xhr.onload=this.onLoad.bind(this,b,c);this.xhr.onerror=c;this.xhr.send()};WebKitBufferPlayer.prototype.stop=function(){this.ready=false;this.loaded=false;this.buffers=[];this.readPosition=0;this.endTime=null;this.setNumberOfOutputChannels(0);this.disconnectWebKitNodes()};WebKitBufferPlayer.prototype.disconnectWebKitNodes=function(){if(this.source&&this.jsNode){this.source.disconnect(this.jsNode);this.jsNode.disconnect(this.context.destination);this.source=null;this.jsNode=null}};WebKitBufferPlayer.prototype.onLoad=function(a,b){this.context.decodeAudioData(this.xhr.response,function(c){this.onDecode(c);a()}.bind(this),b)};WebKitBufferPlayer.prototype.onDecode=function(a){this.fileBuffer=a;this.source=this.context.createBufferSource();this.source.buffer=this.fileBuffer;var b=this.fileBuffer.numberOfChannels;this.setNumberOfOutputChannels(0,b);this.jsNode=this.context.createJavaScriptNode(4096,b,0);this.jsNode.onaudioprocess=this.onData.bind(this);this.source.connect(this.jsNode);this.jsNode.connect(this.context.destination);this.source.noteOn(0);this.endTime=this.context.currentTime+this.fileBuffer.duration;this.loaded=true};WebKitBufferPlayer.prototype.onData=function(c){if(this.loaded){this.ready=true}var a=c.inputBuffer.numberOfChannels;for(var b=0;b<a;b++){this.buffers[b]=c.inputBuffer.getChannelData(b);this.readPosition=0}};WebKitBufferPlayer.prototype.generate=function(){if(!this.ready){return}var a=this.outputs[0];var b=a.samples.length;for(var c=0;c<b;c++){a.samples[c]=this.buffers[c][this.readPosition]}this.readPosition+=1;if(this.context.currentTime>this.endTime){this.stop();this.onComplete()}};
+/*!
+ * @depends ../core/AudioletNode.js
+ */
+var WhiteNoise=function(a){AudioletNode.call(this,a,0,1)};extend(WhiteNoise,AudioletNode);WhiteNoise.prototype.generate=function(){this.outputs[0].samples[0]=Math.random()*2-1};WhiteNoise.prototype.toString=function(){return"White Noise"};var Pattern=function(){};Pattern.prototype.next=function(){return null};Pattern.prototype.valueOf=function(a){if(a instanceof Pattern){return(a.next())}else{return(a)}};Pattern.prototype.reset=function(){};
+/*!
+ * @depends Pattern.js
+ */
+var PArithmetic=function(c,b,a){Pattern.call(this);this.start=c;this.value=c;this.step=b;this.repeats=a;this.position=0};extend(PArithmetic,Pattern);PArithmetic.prototype.next=function(){var a;if(this.position==0){a=this.value;this.position+=1}else{if(this.position<this.repeats){var b=this.valueOf(this.step);if(b!=null){this.value+=b;a=this.value;this.position+=1}else{a=null}}else{a=null}}return(a)};PArithmetic.prototype.reset=function(){this.value=this.start;this.position=0;if(this.step instanceof Pattern){this.step.reset()}};var Pseries=PArithmetic;
+/*!
+ * @depends Pattern.js
+ */
+var PChoose=function(b,a){Pattern.call(this);this.list=b;this.repeats=a||1;this.position=0};extend(PChoose,Pattern);PChoose.prototype.next=function(){var b;if(this.position<this.repeats){var a=Math.floor(Math.random()*this.list.length);var c=this.list[a];var d=this.valueOf(c);if(d!=null){if(!(c instanceof Pattern)){this.position+=1}b=d}else{if(c instanceof Pattern){c.reset()}this.position+=1;b=this.next()}}else{b=null}return(b)};PChoose.prototype.reset=function(){this.position=0;for(var a=0;a<this.list.length;a++){var b=this.list[a];if(b instanceof Pattern){b.reset()}}};var Prand=PChoose;
+/*!
+ * @depends Pattern.js
+ */
+var PGeometric=function(c,b,a){Pattern.call(this);this.start=c;this.value=c;this.step=b;this.repeats=a;this.position=0};extend(PGeometric,Pattern);PGeometric.prototype.next=function(){var a;if(this.position==0){a=this.value;this.position+=1}else{if(this.position<this.repeats){var b=this.valueOf(this.step);if(b!=null){this.value*=b;a=this.value;this.position+=1}else{a=null}}else{a=null}}return(a)};PGeometric.prototype.reset=function(){this.value=this.start;this.position=0;if(this.step instanceof Pattern){this.step.reset()}};var Pgeom=PGeometric;
+/*!
+ * @depends Pattern.js
+ */
+var PProxy=function(a){Pattern.call(this);if(a){this.pattern=a}};extend(PProxy,Pattern);PProxy.prototype.next=function(){var a;if(this.pattern){var a=this.pattern.next()}else{a=null}return a};var Pp=PProxy;
+/*!
+ * @depends Pattern.js
+ */
+var PRandom=function(a,c,b){Pattern.call(this);this.low=a;this.high=c;this.repeats=b;this.position=0};extend(PRandom,Pattern);PRandom.prototype.next=function(){var b;if(this.position<this.repeats){var a=this.valueOf(this.low);var c=this.valueOf(this.high);if(a!=null&&c!=null){b=a+Math.random()*(c-a);this.position+=1}else{b=null}}else{b=null}return(b)};PRandom.prototype.reset=function(){this.position=0};var Pwhite=PRandom;
+/*!
+ * @depends Pattern.js
+ */
+var PSequence=function(b,a,c){Pattern.call(this);this.list=b;this.repeats=a||1;this.position=0;this.offset=c||0};extend(PSequence,Pattern);PSequence.prototype.next=function(){var b;if(this.position<this.repeats*this.list.length){var a=(this.position+this.offset)%this.list.length;var c=this.list[a];var d=this.valueOf(c);if(d!=null){if(!(c instanceof Pattern)){this.position+=1}b=d}else{if(c instanceof Pattern){c.reset()}this.position+=1;b=this.next()}}else{b=null}return(b)};PSequence.prototype.reset=function(){this.position=0;for(var a=0;a<this.list.length;a++){var b=this.list[a];if(b instanceof Pattern){b.reset()}}};var Pseq=PSequence;
+/*!
+ * @depends Pattern.js
+ */
+var PSeries=function(b,a,c){Pattern.call(this);this.list=b;this.repeats=a||1;this.position=0;this.offset=c||0};extend(PSeries,Pattern);PSeries.prototype.next=function(){var b;if(this.position<this.repeats){var a=(this.position+this.offset)%this.list.length;var c=this.list[a];var d=this.valueOf(c);if(d!=null){if(!(c instanceof Pattern)){this.position+=1}b=d}else{if(c instanceof Pattern){c.reset()}this.position+=1;b=this.next()}}else{b=null}return(b)};PSeries.prototype.reset=function(){this.position=0;for(var a=0;a<this.list.length;a++){var b=this.list[a];if(b instanceof Pattern){b.reset()}}};var Pser=PSeries;
+/*!
+ * @depends Pattern.js
+ */
+var PShuffle=function(d,a){Pattern.call(this);this.list=[];while(d.length){var b=Math.floor(Math.random()*d.length);var c=d.splice(b,1);this.list.push(c)}this.repeats=a;this.position=0};extend(PShuffle,Pattern);PShuffle.prototype.next=function(){var b;if(this.position<this.repeats*this.list.length){var a=(this.position+this.offset)%this.list.length;var c=this.list[a];var d=this.valueOf(c);if(d!=null){if(!(c instanceof Pattern)){this.position+=1}b=d}else{if(c instanceof Pattern){c.reset()}this.position+=1;b=this.next()}}else{b=null}return(b)};var Pshuffle=PShuffle;var Scale=function(b,a){this.degrees=b;this.tuning=a||new EqualTemperamentTuning(12)};Scale.prototype.getFrequency=function(d,a,b){var c=a;b+=Math.floor(d/this.degrees.length);d%=this.degrees.length;c*=Math.pow(this.tuning.octaveRatio,b);c*=this.tuning.ratios[this.degrees[d]];return c};
+/*!
+ * @depends Scale.js
+ */
+var MajorScale=function(){Scale.call(this,[0,2,4,5,7,9,11])};extend(MajorScale,Scale);
+/*!
+ * @depends Scale.js
+ */
+var MinorScale=function(){Scale.call(this,[0,2,3,5,7,8,10])};extend(MinorScale,Scale);var Tuning=function(d,c){this.semitones=d;this.octaveRatio=c||2;this.ratios=[];var b=this.semitones.length;for(var a=0;a<b;a++){this.ratios.push(Math.pow(2,this.semitones[a]/b))}};
+/*!
+ * @depends Tuning.js
+ */
+var EqualTemperamentTuning=function(b){var c=[];for(var a=0;a<b;a++){c.push(a)}Tuning.call(this,c,2)};extend(EqualTemperamentTuning,Tuning);var Sink=this.Sink=function(d){function c(j,h,l,f){var e=c.sinks.list,g;for(g=0;g<e.length;g++){if(e[g].enabled){try{return new e[g](j,h,l,f)}catch(k){}}}throw c.Error(2)}function b(){}c.SinkClass=b;b.prototype=c.prototype={sampleRate:44100,channelCount:2,bufferSize:4096,writePosition:0,previousHit:0,ringOffset:0,channelMode:"interleaved",isReady:false,start:function(g,f,h,e){this.channelCount=isNaN(f)||f===null?this.channelCount:f;this.bufferSize=isNaN(h)||h===null?this.bufferSize:h;this.sampleRate=isNaN(e)||e===null?this.sampleRate:e;this.readFn=g;this.activeRecordings=[];this.previousHit=+new Date();c.EventEmitter.call(this);c.emit("init",[this].concat([].slice.call(arguments)))},process:function(g,h){this.emit("preprocess",arguments);if(this.ringBuffer){(this.channelMode==="interleaved"?this.ringSpin:this.ringSpinInterleaved).apply(this,arguments)}if(this.channelMode==="interleaved"){this.emit("audioprocess",arguments);if(this.readFn){this.readFn.apply(this,arguments)}}else{var e=c.deinterleave(g,this.channelCount),f=[e].concat([].slice.call(arguments,1));this.emit("audioprocess",f);if(this.readFn){this.readFn.apply(this,f)}c.interleave(e,this.channelCount,g)}this.emit("postprocess",arguments);this.previousHit=+new Date();this.writePosition+=g.length/h},getPlaybackTime:function(){return this.writePosition-this.bufferSize},ready:function(){if(this.isReady){return}this.isReady=true;this.emit("ready",[])}};function a(l,j,f,h,g){f=f||j.prototype;j.prototype=new c.SinkClass();j.prototype.type=l;j.enabled=!h;var e;for(e in f){if(f.hasOwnProperty(e)){j.prototype[e]=f[e]}}a[l]=j;a.list[g?"unshift":"push"](j)}c.sinks=c.devices=a;c.sinks.list=[];c.singleton=function(){var e=c.apply(null,arguments);c.singleton=function(){return e};return e};d.Sink=c;return c}(function(){return this}());void function(a){function b(){var c;for(c in b.prototype){if(b.prototype.hasOwnProperty(c)){this[c]=b.prototype[c]}}this._listeners={}}b.prototype={_listeners:null,emit:function(d,c){if(this._listeners[d]){for(var e=0;e<this._listeners[d].length;e++){this._listeners[d][e].apply(this,c)}}return this},on:function(c,d){this._listeners[c]=this._listeners[c]||[];this._listeners[c].push(d);return this},off:function(c,e){if(this._listeners[c]){if(!e){delete this._listeners[c];return this}for(var d=0;d<this._listeners[c].length;d++){if(this._listeners[c][d]===e){this._listeners[c].splice(d--,1)}}if(!this._listeners[c].length){delete this._listeners[c]}}return this}};a.EventEmitter=b;b.call(a)}(this.Sink);void function(a){a.doInterval=function(f,d){var e,c;function b(g){if(a.inlineWorker.working&&!g){e=a.inlineWorker('setInterval(function (){ postMessage("tic"); }, '+d+");");e.onmessage=function(){f()};c=function(){e.terminate()}}else{e=setInterval(f,d);c=function(){clearInterval(e)}}}if(a.inlineWorker.ready){b()}else{a.inlineWorker.on("ready",function(){b()})}return function(){if(!c){if(!a.inlineWorker.ready){a.inlineWorker.on("ready",function(){if(c){c()}})}}else{c()}}}}(this.Sink);void function(d){var b,h,j,f;void function(k,m){function l(p,q){var n,o=q.slice();for(n=o.shift();typeof n!=="undefined";n=o.shift()){n=Function("return typeof "+n+p+'=== "undefined" ? undefined : '+n+p)();if(n){return n}}}b=l("Blob",k);h=l("BlobBuilder",k);j=l("URL",m);f=l("btoa",[""])}(["","Moz","WebKit","MS"],["","webkit"]);var g=b&&j&&function(l,k){return j.createObjectURL(new b([l],{type:k}))};var c=h&&j&&function(l,k){var m=new h();m.append(l);return j.createObjectURL(m.getBlob(k))};var a=f&&function(l,k){return"data:"+k+";base64,"+f(l)};var e=g||c||a;if(!e){return}if(g){e.createBlob=g}if(c){e.createBlobBuilder=c}if(a){e.createData=a}if(b){e.Blob=b}if(h){e.BlobBuilder=h}if(j){e.URL=j}d.createDynURL=e;d.revokeDynURL=function(k){if(typeof k==="string"&&k.indexOf("data:")===0){return false}else{return j.revokeObjectURL(k)}}}(this.Sink);void function(a){function b(d){if(!b.hasOwnProperty(d)){throw b(1)}if(!(this instanceof b)){return new b(d)}var c;for(c in b[d]){if(b[d].hasOwnProperty(c)){this[c]=b[d][c]}}this.code=d}b.prototype=new Error();b.prototype.toString=function(){return"SinkError 0x"+this.code.toString(16)+": "+this.message};b[1]={message:"No such error code.",explanation:"The error code does not exist."};b[2]={message:"No audio sink available.",explanation:"The audio device may be busy, or no supported output API is available for this browser."};b[16]={message:"Buffer underflow.",explanation:"Trying to recover..."};b[17]={message:"Critical recovery fail.",explanation:"The buffer underflow has reached a critical point, trying to recover, but will probably fail anyway."};b[18]={message:"Buffer size too large.",explanation:"Unable to allocate the buffer due to excessive length, please try a smaller buffer. Buffer size should probably be smaller than the sample rate."};a.Error=b}(this.Sink);void function(b){var d=Object.defineProperty?function(g,e,f){Object.defineProperty(g,e,{value:f,configurable:true,writable:true})}:function(g,e,f){g[e]=f};function c(){d(this,"terminate",this._terminate);b.revokeDynURL(this._url);delete this._url;delete this._terminate;return this.terminate()}function a(e){function f(l,m,j){try{var k=l(m,"text/javascript");var o=new Worker(k);d(o,"_url",k);d(o,"_terminate",o.terminate);d(o,"terminate",c);if(a.type){return o}a.type=j;a.createURL=l;return o}catch(n){return null}}var h=b.createDynURL;var g;if(a.createURL){return f(a.createURL,e,a.type)}g=f(h.createBlob,e,"blob");if(g){return g}g=f(h.createBlobBuilder,e,"blobbuilder");if(g){return g}g=f(h.createData,e,"data");return g}b.EventEmitter.call(a);a.test=function(){a.ready=a.working=false;a.type="";a.createURL=null;var g=a("this.onmessage=function(e){postMessage(e.data)}");var f="inlineWorker";function e(h){if(a.ready){return}a.ready=true;a.working=h;a.emit("ready",[h]);a.off("ready");if(h&&g){g.terminate()}g=null}if(!g){setTimeout(function(){e(false)},0)}else{g.onmessage=function(h){e(h.data===f)};g.postMessage(f);setTimeout(function(){e(false)},1000)}};b.inlineWorker=a;a.test()}(this.Sink);void function(a){a.sinks("audiodata",function(){var m=this,d=0,k=null,j=new Audio(),h,g,e,f,l,b;m.start.apply(m,arguments);m.preBufferSize=isNaN(arguments[4])||arguments[4]===null?this.preBufferSize:arguments[4];function c(){if(k){h=j.mozWriteAudio(k);d+=h;if(h<k.length){k=k.subarray(h);return k}k=null}g=j.mozCurrentSampleOffset();e=Number(g+(l!==g?m.bufferSize:m.preBufferSize)*m.channelCount-d);if(g===l){m.emit("error",[a.Error(16)])}if(e>0||l===g){m.ready();try{f=new Float32Array(l===g?m.preBufferSize*m.channelCount:m.forceBufferSize?e<m.bufferSize*2?m.bufferSize*2:e:e)}catch(n){m.emit("error",[a.Error(18)]);m.kill();return}m.process(f,m.channelCount);h=m._audio.mozWriteAudio(f);if(h<f.length){k=f.subarray(h)}d+=h}l=g}j.mozSetup(m.channelCount,m.sampleRate);this._timers=[];this._timers.push(a.doInterval(function(){if(+new Date()-m.previousHit>2000){m._audio=j=new Audio();j.mozSetup(m.channelCount,m.sampleRate);d=0;m.emit("error",[a.Error(17)])}},1000));this._timers.push(a.doInterval(c,m.interval));m._bufferFill=c;m._audio=j},{bufferSize:24576,preBufferSize:24576,forceBufferSize:false,interval:100,kill:function(){while(this._timers.length){this._timers.shift()()}this.emit("kill")},getPlaybackTime:function(){return this._audio.mozCurrentSampleOffset()/this.channelCount}},false,true);a.sinks.moz=a.sinks.audiodata}(this.Sink);void function(a){a.sinks("dummy",function(){var b=this;b.start.apply(b,arguments);function c(){var d=new Float32Array(b.bufferSize*b.channelCount);b.process(d,b.channelCount)}b._kill=a.doInterval(c,b.bufferSize/b.sampleRate*1000);b._callback=c},{kill:function(){this._kill();this.emit("kill")}},true)}(this.Sink);(function(b,a){a=b.sinks;function d(f){var e=document.createElement("audio");if(f){e.src=f}return e}a("wav",function(){var e=this,j=new a.wav.wavAudio(),k=typeof k==="undefined"?audioLib.PCMData:k;e.start.apply(e,arguments);var g=new Float32Array(e.bufferSize*e.channelCount),h=new Float32Array(e.bufferSize*e.channelCount);if(!d().canPlayType("audio/wav; codecs=1")||!btoa){throw 0}function f(){if(e._audio.hasNextFrame){return}e.ready();b.memcpy(h,0,g,0);e.process(g,e.channelCount);e._audio.setSource("data:audio/wav;base64,"+btoa(audioLib.PCMData.encode({data:g,sampleRate:e.sampleRate,channelCount:e.channelCount,bytesPerSample:e.quality})));if(!e._audio.currentFrame.src){e._audio.nextClip()}}e.kill=b.doInterval(f,40);e._bufferFill=f;e._audio=j},{quality:1,bufferSize:22050,getPlaybackTime:function(){var e=this._audio;return(e.currentFrame?e.currentFrame.currentTime*this.sampleRate:0)+e.samples}});function c(){var e=this;e.currentFrame=d();e.nextFrame=d();e._onended=function(){e.samples+=e.bufferSize;e.nextClip()}}c.prototype={samples:0,nextFrame:null,currentFrame:null,_onended:null,hasNextFrame:false,nextClip:function(){var e=this.currentFrame;this.currentFrame=this.nextFrame;this.nextFrame=e;this.hasNextFrame=false;this.currentFrame.play()},setSource:function(e){this.nextFrame.src=e;this.nextFrame.addEventListener("ended",this._onended,true);this.hasNextFrame=true}};a.wav.wavAudio=c}(this.Sink));(function(a,b){var c=typeof window==="undefined"?null:window.webkitAudioContext||window.AudioContext;a("webaudio",function(l,k,g,m){var n=this,d=a.webaudio.getContext(),j=null,h=null,f=null;n.start.apply(n,arguments);j=d.createJavaScriptNode(n.bufferSize,0,n.channelCount);function e(v){var o=v.outputBuffer,q=o.numberOfChannels,s,p,r=o.length,w=o.size,t=new Array(q),u;n.ready();h=h&&h.length===r*q?h:new Float32Array(r*q);f=f&&f.length===h.length?f:new Float32Array(r*q);h.set(f);for(s=0;s<q;s++){t[s]=o.getChannelData(s)}n.process(h,n.channelCount);for(s=0;s<r;s++){for(p=0;p<q;p++){t[p][s]=h[s*n.channelCount+p]}}}n.sampleRate=d.sampleRate;j.onaudioprocess=e;j.connect(d.destination);n._context=d;n._node=j;n._callback=e;b.push(j)},{kill:function(){this._node.disconnect(0);for(var d=0;d<b.length;d++){if(b[d]===this._node){b.splice(d--,1)}}this._node=this._context=null;this.emit("kill")},getPlaybackTime:function(){return this._context.currentTime*this.sampleRate}},false,true);a.webkit=a.webaudio;a.webaudio.fix82795=b;a.webaudio.getContext=function(){var d=new c();a.webaudio.getContext=function(){return d};return d}}(this.Sink.sinks,[]));(function(a){a.sinks("worker",function(){var b=this,e=(function(){return this}()),c=null,h=null,d=null;b.start.apply(b,arguments);importScripts();function f(o){if(!b.isReady){b.initMSP(o)}b.ready();var m=b.channelCount,j=o.audioLength,p,k;c=c&&c.length===j*m?c:new Float32Array(j*m);h=h&&h.length===c.length?h:new Float32Array(j*m);d=d&&d.length===c.length?d:new Float32Array(j*m);c.set(d);h.set(d);b.process(c,b.channelCount);for(p=0;p<m;p++){for(k=0;k<j;k++){h[p*o.audioLength+k]=c[p+k*m]}}o.writeAudio(h)}function g(s){if(!b.isReady){b.initWA(s)}b.ready();var j=s.outputBuffer,m=j.numberOfChannels,p,k,o=j.length,t=j.size,q=new Array(m),r;c=c&&c.length===o*m?c:new Float32Array(o*m);d=d&&d.length===c.length?d:new Float32Array(o*m);c.set(d);for(p=0;p<m;p++){q[p]=j.getChannelData(p)}b.process(c,b.channelCount);for(p=0;p<o;p++){for(k=0;k<m;k++){q[k][p]=c[p*b.channelCount+k]}}}e.onprocessmedia=f;e.onaudioprocess=g;b._mspBufferFill=f;b._waBufferFill=g},{ready:false,initMSP:function(b){this.channelCount=b.audioChannels;this.sampleRate=b.audioSampleRate;this.bufferSize=b.audioLength*this.channelCount;this.ready=true;this.emit("ready",[])},initWA:function(d){var c=d.outputBuffer;this.channelCount=c.numberOfChannels;this.sampleRate=c.sampleRate;this.bufferSize=c.length*this.channelCount;this.ready=true;this.emit("ready",[])}})}(this.Sink));(function(a){a.deinterleave=function(c,g){var b=c.length,f=b/g,d=[],e,h;for(e=0;e<g;e++){d[e]=new Float32Array(f);for(h=0;h<f;h++){d[e][h]=c[h*g+e]}}return d};a.interleave=function(d,g,c){g=g||d.length;var b=d[0].length,f=d.length,e,h;c=c||new Float32Array(b*g);for(e=0;e<f;e++){for(h=0;h<b;h++){c[e+h*g]=d[e][h]}}return c};a.mix=function(e){var d=[].slice.call(arguments,1),b,f,g;for(g=0;g<d.length;g++){b=Math.max(e.length,d[g].length);for(f=0;f<b;f++){e[f]+=d[g][f]}}return e};a.resetBuffer=function(c){var b=c.length,d;for(d=0;d<b;d++){c[d]=0}return c};a.clone=function(d,b){var c=d.length,e;b=b||new Float32Array(c);for(e=0;e<c;e++){b[e]=d[e]}return b};a.createDeinterleaved=function(e,d){var b=new Array(d),c;for(c=0;c<d;c++){b[c]=new Float32Array(e)}return b};a.memcpy=function(f,b,g,d,e){f=f.subarray||f.slice?f:f.buffer;g=g.subarray||g.slice?g:g.buffer;f=b?f.subarray?f.subarray(b,e&&b+e):f.slice(b,e&&b+e):f;if(g.set){g.set(f,d)}else{for(var c=0;c<f.length;c++){g[c+d]=f[c]}}return g};a.memslice=function(b,d,c){return b.subarray?b.subarray(d,c):b.slice(d,c)};a.mempad=function(b,c,d){c=c.length?c:new (b.constructor)(c);a.memcpy(b,0,c,d);return c};a.linspace=function(h,c,d){var b,e,g,f;d=d.length?(b=d.length)&&d:Array(b=d);f=(c-h)/--b;for(g=h+f,e=1;e<b;e++,g+=f){d[e]=g}d[0]=h;d[b]=c;return d};a.ftoi=function(d,f,c){var e,b=Math.pow(2,f-1);c=c||new (d.constructor)(d.length);for(e=0;e<d.length;e++){c[e]=~~(b*d[e])}return c}}(this.Sink));(function(a){function b(e,d){a.EventEmitter.call(this);this.bufferSize=isNaN(e)||e===null?this.bufferSize:e;this.channelCount=isNaN(d)||d===null?this.channelCount:d;var c=this;this.callback=function(){return c.process.apply(c,arguments)};this.resetBuffer()}b.prototype={buffer:null,zeroBuffer:null,parentSink:null,bufferSize:4096,channelCount:2,offset:null,resetBuffer:function(){this.buffer=new Float32Array(this.bufferSize);this.zeroBuffer=new Float32Array(this.bufferSize)},process:function(c,e){if(this.offset===null){this.loadBuffer()}for(var d=0;d<c.length;d++){if(this.offset>=this.buffer.length){this.loadBuffer()}c[d]=this.buffer[this.offset++]}},loadBuffer:function(){this.offset=0;a.memcpy(this.zeroBuffer,0,this.buffer,0);this.emit("audioprocess",[this.buffer,this.channelCount])}};a.Proxy=b;a.prototype.createProxy=function(d){var c=new a.Proxy(d,this.channelCount);c.parentSink=this;this.on("audioprocess",c.callback);return c}}(this.Sink));(function(a){(function(){function b(c,d){if(c&&d){b[c]=d}else{if(c&&b[c] instanceof Function){a.interpolate=b[c]}}return b[c]}a.interpolation=b;b("linear",function(c,g){var f=Math.floor(g),e=f+1,d=g-f;e=e<c.length?e:0;return c[f]*(1-d)+c[e]*d});b("nearest",function(c,d){return d>=c.length-0.5?c[0]:c[Math.round(d)]});b("linear")}());a.resample=function(j,b,o,p,d){var h=arguments.length,f=h===2?b:h===3?b/o:p/b*d/o,g=j.length,c=Math.ceil(g/f),m=new Float32Array(c),k,e;for(k=0,e=0;k<g;k+=f){m[e++]=a.interpolate(j,k)}return m}}(this.Sink));void function(b){b.on("init",function(c){c.activeRecordings=[];c.on("postprocess",c.recordData)});b.prototype.activeRecordings=null;b.prototype.record=function(){var c=new b.Recording(this);this.emit("record",[c]);return c};b.prototype.recordData=function(d){var f=this.activeRecordings,e,c=f.length;for(e=0;e<c;e++){f[e].add(d)}};function a(c){this.boundTo=c;this.buffers=[];c.activeRecordings.push(this)}a.prototype={add:function(c){this.buffers.push(c)},clear:function(){this.buffers=[]},stop:function(){var c=this.boundTo.activeRecordings,d;for(d=0;d<c.length;d++){if(c[d]===this){c.splice(d--,1)}}},join:function(){var g=0,h=0,e=this.buffers,c,j,f,d=e.length;for(f=0;f<d;f++){g+=e[f].length}c=new Float32Array(g);for(f=0;f<d;f++){for(j=0;j<e[f].length;j++){c[h+j]=e[f][j]}h+=e[f].length}return c}};b.Recording=a}(this.Sink);void function(b){function a(){if(this.ringBuffer){(this.channelMode==="interleaved"?this.ringSpin:this.ringSpinInterleaved).apply(this,arguments)}}b.on("init",function(c){c.on("preprocess",a)});b.prototype.ringBuffer=null;b.prototype.ringSpin=function(e){var f=this.ringBuffer,d=e.length,c=f.length,h=this.ringOffset,g;for(g=0;g<d;g++){e[g]+=f[h];h=(h+1)%c}this.ringOffset=h};b.prototype.ringSpinDeinterleaved=function(j){var h=this.ringBuffer,g=j.length,c=h.length,f=h[0].length,o=c*f,e=this.ringOffset,k,d;for(k=0;k<g;k+=c){for(d=0;d<c;d++){j[k+d]+=h[d][e]}e=(e+1)%f}this.ringOffset=d}}(this.Sink);void function(a,b){b=a.prototype;a.on("init",function(c){c.asyncBuffers=[];c.syncBuffers=[];c.on("preprocess",c.writeBuffersSync);c.on("postprocess",c.writeBuffersAsync)});b.writeMode="async";b.asyncBuffers=b.syncBuffers=null;b.writeBuffersAsync=function(e){var d=this.asyncBuffers,c=e.length,f,h,g,k,j;if(d){for(g=0;g<d.length;g++){f=d[g];h=f.b.length;j=f.d;f.d-=Math.min(j,c);for(k=0;k+j<c&&k<h;k++){e[k+j]+=f.b[k]}f.b=f.b.subarray(k+j);if(g>=h){d.splice(g--,1)}}}};b.writeBuffersSync=function(e){var d=this.syncBuffers,c=e.length,f=0,g=0;for(;f<c&&d.length;f++){e[f]+=d[0][g];if(d[0].length<=g){d.splice(0,1);g=0;continue}g++}if(d.length){d[0]=d[0].subarray(g)}};b.writeBufferAsync=function(d,e){d=this.mode==="deinterleaved"?a.interleave(d,this.channelCount):d;var c=this.asyncBuffers;c.push({b:d,d:isNaN(e)?~~((+new Date()-this.previousHit)/1000*this.sampleRate):e});return c.length};b.writeBufferSync=function(d){d=this.mode==="deinterleaved"?a.interleave(d,this.channelCount):d;var c=this.syncBuffers;c.push(d);return c.length};b.writeBuffer=function(){return this[this.writeMode==="async"?"writeBufferAsync":"writeBufferSync"].apply(this,arguments)};b.getSyncWriteOffset=function(){var c=this.syncBuffers,e=0,d;for(d=0;d<c.length;d++){e+=c[d].length}return e}}(this.Sink); \ No newline at end of file