summaryrefslogtreecommitdiff
path: root/public/js/vendor/Audiolet.js
diff options
context:
space:
mode:
Diffstat (limited to 'public/js/vendor/Audiolet.js')
-rw-r--r--public/js/vendor/Audiolet.js7281
1 files changed, 7281 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);
+