Last active
August 29, 2015 14:20
-
-
Save logsol/448c42942700efd6645e to your computer and use it in GitHub Desktop.
midi hack
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
node_modules/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Midi Hack | |
utilizing | |
Leap Motion / Node / JavaScript |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function AudioFileRequest(url, async) { | |
this.url = url; | |
if (typeof async == 'undefined' || async == null) { | |
async = true; | |
} | |
this.async = async; | |
var splitURL = url.split('.'); | |
this.extension = splitURL[splitURL.length - 1].toLowerCase(); | |
} | |
AudioFileRequest.prototype.onSuccess = function(decoded) { | |
}; | |
AudioFileRequest.prototype.onFailure = function(decoded) { | |
}; | |
AudioFileRequest.prototype.send = function() { | |
if (this.extension != 'wav' && | |
this.extension != 'aiff' && | |
this.extension != 'aif') { | |
this.onFailure(); | |
return; | |
} | |
var request = new XMLHttpRequest(); | |
request.open('GET', this.url, this.async); | |
request.overrideMimeType('text/plain; charset=x-user-defined'); | |
request.onreadystatechange = function(event) { | |
if (request.readyState == 4) { | |
if (request.status == 200 || request.status == 0) { | |
this.handleResponse(request.responseText); | |
} | |
else { | |
this.onFailure(); | |
} | |
} | |
}.bind(this); | |
request.send(null); | |
}; | |
AudioFileRequest.prototype.handleResponse = function(data) { | |
var decoder, decoded; | |
if (this.extension == 'wav') { | |
decoder = new WAVDecoder(); | |
decoded = decoder.decode(data); | |
} | |
else if (this.extension == 'aiff' || this.extension == 'aif') { | |
decoder = new AIFFDecoder(); | |
decoded = decoder.decode(data); | |
} | |
this.onSuccess(decoded); | |
}; | |
function Decoder() { | |
} | |
Decoder.prototype.readString = function(data, offset, length) { | |
return data.slice(offset, offset + length); | |
}; | |
Decoder.prototype.readIntL = function(data, offset, length) { | |
var value = 0; | |
for (var i = 0; i < length; i++) { | |
value = value + ((data.charCodeAt(offset + i) & 0xFF) * | |
Math.pow(2, 8 * i)); | |
} | |
return value; | |
}; | |
Decoder.prototype.readChunkHeaderL = function(data, offset) { | |
var chunk = {}; | |
chunk.name = this.readString(data, offset, 4); | |
chunk.length = this.readIntL(data, offset + 4, 4); | |
return chunk; | |
}; | |
Decoder.prototype.readIntB = function(data, offset, length) { | |
var value = 0; | |
for (var i = 0; i < length; i++) { | |
value = value + ((data.charCodeAt(offset + i) & 0xFF) * | |
Math.pow(2, 8 * (length - i - 1))); | |
} | |
return value; | |
}; | |
Decoder.prototype.readChunkHeaderB = function(data, offset) { | |
var chunk = {}; | |
chunk.name = this.readString(data, offset, 4); | |
chunk.length = this.readIntB(data, offset + 4, 4); | |
return chunk; | |
}; | |
Decoder.prototype.readFloatB = function(data, offset) { | |
var expon = this.readIntB(data, offset, 2); | |
var range = 1 << 16 - 1; | |
if (expon >= range) { | |
expon |= ~(range - 1); | |
} | |
var sign = 1; | |
if (expon < 0) { | |
sign = -1; | |
expon += range; | |
} | |
var himant = this.readIntB(data, offset + 2, 4); | |
var lomant = this.readIntB(data, offset + 6, 4); | |
var value; | |
if (expon == himant == lomant == 0) { | |
value = 0; | |
} | |
else if (expon == 0x7FFF) { | |
value = Number.MAX_VALUE; | |
} | |
else { | |
expon -= 16383; | |
value = (himant * 0x100000000 + lomant) * Math.pow(2, expon - 63); | |
} | |
return sign * value; | |
}; | |
function WAVDecoder(data) { | |
} | |
WAVDecoder.prototype.__proto__ = Decoder.prototype; | |
WAVDecoder.prototype.decode = function(data) { | |
var decoded = {}; | |
var offset = 0; | |
// Header | |
var chunk = this.readChunkHeaderL(data, offset); | |
offset += 8; | |
if (chunk.name != 'RIFF') { | |
console.error('File is not a WAV'); | |
return null; | |
} | |
var fileLength = chunk.length; | |
fileLength += 8; | |
var wave = this.readString(data, offset, 4); | |
offset += 4; | |
if (wave != 'WAVE') { | |
console.error('File is not a WAV'); | |
return null; | |
} | |
while (offset < fileLength) { | |
var chunk = this.readChunkHeaderL(data, offset); | |
offset += 8; | |
if (chunk.name == 'fmt ') { | |
// File encoding | |
var encoding = this.readIntL(data, offset, 2); | |
offset += 2; | |
if (encoding != 0x0001) { | |
// Only support PCM | |
console.error('Cannot decode non-PCM encoded WAV file'); | |
return null; | |
} | |
// Number of channels | |
var numberOfChannels = this.readIntL(data, offset, 2); | |
offset += 2; | |
// Sample rate | |
var sampleRate = this.readIntL(data, offset, 4); | |
offset += 4; | |
// Ignore bytes/sec - 4 bytes | |
offset += 4; | |
// Ignore block align - 2 bytes | |
offset += 2; | |
// Bit depth | |
var bitDepth = this.readIntL(data, offset, 2); | |
var bytesPerSample = bitDepth / 8; | |
offset += 2; | |
} | |
else if (chunk.name == 'data') { | |
// Data must come after fmt, so we are okay to use it's variables | |
// here | |
var length = chunk.length / (bytesPerSample * numberOfChannels); | |
var channels = []; | |
for (var i = 0; i < numberOfChannels; i++) { | |
channels.push(new Float32Array(length)); | |
} | |
for (var i = 0; i < numberOfChannels; i++) { | |
var channel = channels[i]; | |
for (var j = 0; j < length; j++) { | |
var index = offset; | |
index += (j * numberOfChannels + i) * bytesPerSample; | |
// Sample | |
var value = this.readIntL(data, index, bytesPerSample); | |
// Scale range from 0 to 2**bitDepth -> -2**(bitDepth-1) to | |
// 2**(bitDepth-1) | |
var range = 1 << bitDepth - 1; | |
if (value >= range) { | |
value |= ~(range - 1); | |
} | |
// Scale range to -1 to 1 | |
channel[j] = value / range; | |
} | |
} | |
offset += chunk.length; | |
} | |
else { | |
offset += chunk.length; | |
} | |
} | |
decoded.sampleRate = sampleRate; | |
decoded.bitDepth = bitDepth; | |
decoded.channels = channels; | |
decoded.length = length; | |
return decoded; | |
}; | |
function AIFFDecoder() { | |
} | |
AIFFDecoder.prototype.__proto__ = Decoder.prototype; | |
AIFFDecoder.prototype.decode = function(data) { | |
var decoded = {}; | |
var offset = 0; | |
// Header | |
var chunk = this.readChunkHeaderB(data, offset); | |
offset += 8; | |
if (chunk.name != 'FORM') { | |
console.error('File is not an AIFF'); | |
return null; | |
} | |
var fileLength = chunk.length; | |
fileLength += 8; | |
var aiff = this.readString(data, offset, 4); | |
offset += 4; | |
if (aiff != 'AIFF') { | |
console.error('File is not an AIFF'); | |
return null; | |
} | |
while (offset < fileLength) { | |
var chunk = this.readChunkHeaderB(data, offset); | |
offset += 8; | |
if (chunk.name == 'COMM') { | |
// Number of channels | |
var numberOfChannels = this.readIntB(data, offset, 2); | |
offset += 2; | |
// Number of samples | |
var length = this.readIntB(data, offset, 4); | |
offset += 4; | |
var channels = []; | |
for (var i = 0; i < numberOfChannels; i++) { | |
channels.push(new Float32Array(length)); | |
} | |
// Bit depth | |
var bitDepth = this.readIntB(data, offset, 2); | |
var bytesPerSample = bitDepth / 8; | |
offset += 2; | |
// Sample rate | |
var sampleRate = this.readFloatB(data, offset); | |
offset += 10; | |
} | |
else if (chunk.name == 'SSND') { | |
// Data offset | |
var dataOffset = this.readIntB(data, offset, 4); | |
offset += 4; | |
// Ignore block size | |
offset += 4; | |
// Skip over data offset | |
offset += dataOffset; | |
for (var i = 0; i < numberOfChannels; i++) { | |
var channel = channels[i]; | |
for (var j = 0; j < length; j++) { | |
var index = offset; | |
index += (j * numberOfChannels + i) * bytesPerSample; | |
// Sample | |
var value = this.readIntB(data, index, bytesPerSample); | |
// Scale range from 0 to 2**bitDepth -> -2**(bitDepth-1) to | |
// 2**(bitDepth-1) | |
var range = 1 << bitDepth - 1; | |
if (value >= range) { | |
value |= ~(range - 1); | |
} | |
// Scale range to -1 to 1 | |
channel[j] = value / range; | |
} | |
} | |
offset += chunk.length - dataOffset - 8; | |
} | |
else { | |
offset += chunk.length; | |
} | |
} | |
decoded.sampleRate = sampleRate; | |
decoded.bitDepth = bitDepth; | |
decoded.channels = channels; | |
decoded.length = length; | |
return decoded; | |
}; | |
/* | |
* @depends ../audiofile/audiofile.js | |
*/ | |
/** | |
* 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.createScriptProcessor(self.bufferSize, self.channelCount, 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); | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function playExample() { | |
var AudioletApp = function() { | |
this.audiolet = new Audiolet(); | |
}; | |
this.audioletApp = new AudioletApp(); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
var socket; | |
var connected; | |
var myClientId; | |
var myAudioletApp; | |
var gain = { | |
current: 0.000001, | |
target: 0.00000 | |
}; | |
// prevent double tap zoom | |
(function($) { | |
$.fn.nodoubletapzoom = function() { | |
$(this).bind('touchstart', function preventZoom(e) { | |
var t2 = e.timeStamp | |
, t1 = $(this).data('lastTouch') || t2 | |
, dt = t2 - t1 | |
, fingers = e.originalEvent.touches.length; | |
$(this).data('lastTouch', t2); | |
if (!dt || dt > 500 || fingers > 1) return; // not double-tap | |
e.preventDefault(); // double tap - prevent the zoom | |
// also synthesize click events we just swallowed up | |
$(this).trigger('click').trigger('click'); | |
}); | |
}; | |
})(jQuery); | |
function update(x, y) { | |
if(!connected) return; | |
var marker = document.getElementById('marker'); | |
marker.style.left = x + 'px'; | |
marker.style.top = y + 'px'; | |
x = x / window.innerWidth; | |
y = y / window.innerHeight; | |
socket.send( '{"x":' + x + ', "y":' + y + '}'); | |
} | |
function initClient () { | |
window.addEventListener('touchstart', function(ev){ | |
update(ev.touches[0].clientX, ev.touches[0].clientY); | |
}); | |
window.addEventListener('touchmove', function(ev){ | |
update(ev.touches[0].clientX, ev.touches[0].clientY); | |
}); | |
window.addEventListener('click', function(ev){ | |
update(ev.pageX, ev.pageY); | |
}); | |
$(document).on('touchmove', function(event){ | |
event.preventDefault() | |
}) | |
} | |
function initSocket () { | |
var options = { | |
'reconnect': true, | |
'reconnection delay': 500, | |
'max reconnection attempts': 100000, | |
'transports': [ | |
'websocket', | |
'flashsocket' | |
] | |
}; | |
socket = io.connect('/', options); | |
socket.on('connect', onConnect); | |
socket.on('disconnect', onDisconnect); | |
socket.on('message', onMessage); | |
} | |
function onConnect() { | |
console.log('connected'); | |
connected = true; | |
update(window.innerWidth / 2, window.innerHeight / 2); | |
} | |
function onDisconnect() { | |
connected = false; | |
marker.style.backgroundColor = '#eee'; | |
$("body").css("background-color", "#eee"); | |
$(".other").css("background-color", "#eee"); | |
} | |
function onMessage(message) { | |
var pack = message.split('|'); | |
var command = pack[0]; | |
var payload = pack[1]; | |
switch (command) { | |
case 'welcome': | |
myClientId = payload; | |
setColor(myClientId); | |
break; | |
case 'positions': | |
updatePositions(payload); | |
break; | |
} | |
} | |
function setColor(clientId) { | |
marker.style.backgroundColor = '#' + clientId.substr(0,6); | |
$("body").css("background-color", "#" + clientId.substr(0,6)); | |
} | |
function updatePositions(payload) { | |
var others = JSON.parse(payload); | |
$(".other").remove(); | |
for (var id in others) { | |
if (others.hasOwnProperty(id) && id != myClientId && id != 'leap') { | |
var otherDiv = $("<div class='other' id='" + id + "'></div>"); | |
$(otherDiv).css("left", others[id].x * window.innerWidth); | |
$(otherDiv).css("top", others[id].y * window.innerHeight); | |
$(otherDiv).css("background-color", "#" + id.substr(0,6)); | |
$("body").append(otherDiv); | |
} | |
} | |
$("#leap").css("left", others['leap'].x * window.innerWidth); | |
$("#leap").css("top", others['leap'].y * window.innerHeight); | |
applyDistance(others['leap'], others[myClientId]); | |
} | |
function applyDistance(leap, me) { | |
var xs = 0; | |
var ys = 0; | |
xs = me.x - leap.x; | |
xs = xs * xs; | |
ys = me.y - leap.y; | |
ys = ys * ys; | |
var distance = Math.sqrt(xs + ys); | |
if (distance < 0.2) { | |
gain.target = Math.pow(1 - distance * 5, 2); | |
} else { | |
gain.target = 0.0000001; | |
} | |
} | |
function initSound() { | |
var AudioletApp = function() { | |
this.audiolet = new Audiolet(); | |
this.sine = new WhiteNoise(this.audiolet, 440); | |
//this.modulator = new Saw(this.audiolet, 980); | |
//this.modulatorMulAdd = new MulAdd(this.audiolet, 200, 40); | |
this.filter = new LowPassFilter(this.audiolet, 300); | |
this.gain = new Gain(this.audiolet, 0.000001); | |
this.delay = new Delay(this.audiolet, 0.2, 0.2); | |
this.feedbackScaler = new Gain(this.audiolet, 0.5); | |
//this.modulator.connect(this.modulatorMulAdd); | |
//this.modulatorMulAdd.connect(this.sine); | |
this.sine.connect(this.filter); | |
this.filter.connect(this.gain); | |
this.gain.connect(this.delay); | |
this.delay.connect(this.audiolet.output); | |
//this.delay.connect(this.feedbackScaler); | |
//this.feedbackScaler.connect(this.delay); | |
}; | |
myAudioletApp = new AudioletApp(); | |
setInterval(function(){ | |
var step = (gain.target - gain.current) * 2 / 100; // 6% | |
gain.current += step; | |
myAudioletApp.gain.gain.value = gain.current; | |
}, 1); | |
} | |
window.onload = function () { | |
initClient(); | |
initSocket(); | |
initSound(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Leap</title> | |
<style type="text/css"> | |
#room { | |
position: absolute; | |
top: 5%; | |
left: 5%; | |
width: 90%; | |
height: 90%; | |
border: 1px dotted black; | |
background: white; | |
} | |
#marker { | |
width: 5%; | |
height: 5%; | |
background: #eee; | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
margin-left: -2.5%; | |
margin-top: -2.5%; | |
} | |
.other { | |
width: 5%; | |
height: 5%; | |
background: #eee; | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
margin-left: -2.5%; | |
margin-top: -2.5%; | |
} | |
#leap { | |
width: 2%; | |
height: 2%; | |
background: #ff0000; | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
margin-left: -1%; | |
margin-top: -1%; | |
border-radius: 3px; | |
} | |
</style> | |
<script src="jquery.js"></script> | |
<script src="/socket.io/socket.io.js"></script> | |
<script src="Audiolet.js"></script> | |
<script src="client.js"></script> | |
</head> | |
<body> | |
<div id="room"></div> | |
<div id="marker"></div> | |
<div id="leap"></div> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! jQuery v1.11.3 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ | |
!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.3",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b="length"in a&&a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\f]' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=ma(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=na(b);function qa(){}qa.prototype=d.filters=d.pseudos,d.setFilters=new qa,g=ga.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?ga.error(a):z(a,i).slice(0)};function ra(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1; | |
return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?m.queue(this[0],a):void 0===b?this:this.each(function(){var c=m.queue(this,a,b);m._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&m.dequeue(this,a)})},dequeue:function(a){return this.each(function(){m.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=m.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=m._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=["Top","Right","Bottom","Left"],U=function(a,b){return a=b||a,"none"===m.css(a,"display")||!m.contains(a.ownerDocument,a)},V=m.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===m.type(c)){e=!0;for(h in c)m.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,m.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(m(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav></:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="<input type='radio' checked='checked' name='t'/>",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function aa(){return!0}function ba(){return!1}function ca(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},fix:function(a){if(a[m.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=Z.test(e)?this.mouseHooks:Y.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new m.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=f.srcElement||y),3===a.target.nodeType&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,g.filter?g.filter(a,f):a},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button,g=b.fromElement;return null==a.pageX&&null!=b.clientX&&(d=a.target.ownerDocument||y,e=d.documentElement,c=d.body,a.pageX=b.clientX+(e&&e.scrollLeft||c&&c.scrollLeft||0)-(e&&e.clientLeft||c&&c.clientLeft||0),a.pageY=b.clientY+(e&&e.scrollTop||c&&c.scrollTop||0)-(e&&e.clientTop||c&&c.clientTop||0)),!a.relatedTarget&&g&&(a.relatedTarget=g===a.target?b.toElement:g),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==ca()&&this.focus)try{return this.focus(),!1}catch(a){}},delegateType:"focusin"},blur:{trigger:function(){return this===ca()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return m.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):void 0},_default:function(a){return m.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=m.extend(new m.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?m.event.trigger(e,null,b):m.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},m.removeEvent=y.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]===K&&(a[d]=null),a.detachEvent(d,c))},m.Event=function(a,b){return this instanceof m.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?aa:ba):this.type=a,b&&m.extend(this,b),this.timeStamp=a&&a.timeStamp||m.now(),void(this[m.expando]=!0)):new m.Event(a,b)},m.Event.prototype={isDefaultPrevented:ba,isPropagationStopped:ba,isImmediatePropagationStopped:ba,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=aa,a&&(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=aa,a&&(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=aa,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},m.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){m.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!m.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.submitBubbles||(m.event.special.submit={setup:function(){return m.nodeName(this,"form")?!1:void m.event.add(this,"click._submit keypress._submit",function(a){var b=a.target,c=m.nodeName(b,"input")||m.nodeName(b,"button")?b.form:void 0;c&&!m._data(c,"submitBubbles")&&(m.event.add(c,"submit._submit",function(a){a._submit_bubble=!0}),m._data(c,"submitBubbles",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&m.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){return m.nodeName(this,"form")?!1:void m.event.remove(this,"._submit")}}),k.changeBubbles||(m.event.special.change={setup:function(){return X.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(m.event.add(this,"propertychange._change",function(a){"checked"===a.originalEvent.propertyName&&(this._just_changed=!0)}),m.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),m.event.simulate("change",this,a,!0)})),!1):void m.event.add(this,"beforeactivate._change",function(a){var b=a.target;X.test(b.nodeName)&&!m._data(b,"changeBubbles")&&(m.event.add(b,"change._change",function(a){!this.parentNode||a.isSimulated||a.isTrigger||m.event.simulate("change",this.parentNode,a,!0)}),m._data(b,"changeBubbles",!0))})},handle:function(a){var b=a.target;return this!==b||a.isSimulated||a.isTrigger||"radio"!==b.type&&"checkbox"!==b.type?a.handleObj.handler.apply(this,arguments):void 0},teardown:function(){return m.event.remove(this,"._change"),!X.test(this.nodeName)}}),k.focusinBubbles||m.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){m.event.simulate(b,a.target,m.event.fix(a),!0)};m.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=m._data(d,b);e||d.addEventListener(a,c,!0),m._data(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=m._data(d,b)-1;e?m._data(d,b,e):(d.removeEventListener(a,c,!0),m._removeData(d,b))}}}),m.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(f in a)this.on(f,b,c,a[f],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=ba;else if(!d)return this;return 1===e&&(g=d,d=function(a){return m().off(a),g.apply(this,arguments)},d.guid=g.guid||(g.guid=m.guid++)),this.each(function(){m.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,m(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=ba),this.each(function(){m.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){m.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?m.event.trigger(a,b,c,!0):void 0}});function da(a){var b=ea.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}var ea="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",fa=/ jQuery\d+="(?:null|\d+)"/g,ga=new RegExp("<(?:"+ea+")[\\s/>]","i"),ha=/^\s+/,ia=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ja=/<([\w:]+)/,ka=/<tbody/i,la=/<|&#?\w+;/,ma=/<(?:script|style|link)/i,na=/checked\s*(?:[^=]|=\s*.checked.)/i,oa=/^$|\/(?:java|ecma)script/i,pa=/^true\/(.*)/,qa=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,ra={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:k.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},sa=da(y),ta=sa.appendChild(y.createElement("div"));ra.optgroup=ra.option,ra.tbody=ra.tfoot=ra.colgroup=ra.caption=ra.thead,ra.th=ra.td;function ua(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ua(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function va(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wa(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xa(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function ya(a){var b=pa.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function za(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Aa(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Ba(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xa(b).text=a.text,ya(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!ga.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ta.innerHTML=a.outerHTML,ta.removeChild(f=ta.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ua(f),h=ua(a),g=0;null!=(e=h[g]);++g)d[g]&&Ba(e,d[g]);if(b)if(c)for(h=h||ua(a),d=d||ua(f),g=0;null!=(e=h[g]);g++)Aa(e,d[g]);else Aa(a,f);return d=ua(f,"script"),d.length>0&&za(d,!i&&ua(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=da(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(la.test(f)){h=h||o.appendChild(b.createElement("div")),i=(ja.exec(f)||["",""])[1].toLowerCase(),l=ra[i]||ra._default,h.innerHTML=l[1]+f.replace(ia,"<$1></$2>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&ha.test(f)&&p.push(b.createTextNode(ha.exec(f)[0])),!k.tbody){f="table"!==i||ka.test(f)?"<table>"!==l[1]||ka.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ua(p,"input"),va),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ua(o.appendChild(f),"script"),g&&za(h),c)){e=0;while(f=h[e++])oa.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wa(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ua(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&za(ua(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ua(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fa,""):void 0;if(!("string"!=typeof a||ma.test(a)||!k.htmlSerialize&&ga.test(a)||!k.leadingWhitespace&&ha.test(a)||ra[(ja.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ia,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ua(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ua(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&na.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ua(i,"script"),xa),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ua(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,ya),j=0;f>j;j++)d=g[j],oa.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qa,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Ca,Da={};function Ea(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fa(a){var b=y,c=Da[a];return c||(c=Ea(a,b),"none"!==c&&c||(Ca=(Ca||m("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=(Ca[0].contentWindow||Ca[0].contentDocument).document,b.write(),b.close(),c=Ea(a,b),Ca.detach()),Da[a]=c),c}!function(){var a;k.shrinkWrapBlocks=function(){if(null!=a)return a;a=!1;var b,c,d;return c=y.getElementsByTagName("body")[0],c&&c.style?(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:1px;width:1px;zoom:1",b.appendChild(y.createElement("div")).style.width="5px",a=3!==b.offsetWidth),c.removeChild(d),a):void 0}}();var Ga=/^margin/,Ha=new RegExp("^("+S+")(?!px)[a-z%]+$","i"),Ia,Ja,Ka=/^(top|right|bottom|left)$/;a.getComputedStyle?(Ia=function(b){return b.ownerDocument.defaultView.opener?b.ownerDocument.defaultView.getComputedStyle(b,null):a.getComputedStyle(b,null)},Ja=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ia(a),g=c?c.getPropertyValue(b)||c[b]:void 0,c&&(""!==g||m.contains(a.ownerDocument,a)||(g=m.style(a,b)),Ha.test(g)&&Ga.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0===g?g:g+""}):y.documentElement.currentStyle&&(Ia=function(a){return a.currentStyle},Ja=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ia(a),g=c?c[b]:void 0,null==g&&h&&h[b]&&(g=h[b]),Ha.test(g)&&!Ka.test(b)&&(d=h.left,e=a.runtimeStyle,f=e&&e.left,f&&(e.left=a.currentStyle.left),h.left="fontSize"===b?"1em":g,g=h.pixelLeft+"px",h.left=d,f&&(e.left=f)),void 0===g?g:g+""||"auto"});function La(a,b){return{get:function(){var c=a();if(null!=c)return c?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d,e,f,g,h;if(b=y.createElement("div"),b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=d&&d.style){c.cssText="float:left;opacity:.5",k.opacity="0.5"===c.opacity,k.cssFloat=!!c.cssFloat,b.style.backgroundClip="content-box",b.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===b.style.backgroundClip,k.boxSizing=""===c.boxSizing||""===c.MozBoxSizing||""===c.WebkitBoxSizing,m.extend(k,{reliableHiddenOffsets:function(){return null==g&&i(),g},boxSizingReliable:function(){return null==f&&i(),f},pixelPosition:function(){return null==e&&i(),e},reliableMarginRight:function(){return null==h&&i(),h}});function i(){var b,c,d,i;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),b.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",e=f=!1,h=!0,a.getComputedStyle&&(e="1%"!==(a.getComputedStyle(b,null)||{}).top,f="4px"===(a.getComputedStyle(b,null)||{width:"4px"}).width,i=b.appendChild(y.createElement("div")),i.style.cssText=b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",i.style.marginRight=i.style.width="0",b.style.width="1px",h=!parseFloat((a.getComputedStyle(i,null)||{}).marginRight),b.removeChild(i)),b.innerHTML="<table><tr><td></td><td>t</td></tr></table>",i=b.getElementsByTagName("td"),i[0].style.cssText="margin:0;border:0;padding:0;display:none",g=0===i[0].offsetHeight,g&&(i[0].style.display="",i[1].style.display="none",g=0===i[0].offsetHeight),c.removeChild(d))}}}(),m.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var Ma=/alpha\([^)]*\)/i,Na=/opacity\s*=\s*([^)]*)/,Oa=/^(none|table(?!-c[ea]).+)/,Pa=new RegExp("^("+S+")(.*)$","i"),Qa=new RegExp("^([+-])=("+S+")","i"),Ra={position:"absolute",visibility:"hidden",display:"block"},Sa={letterSpacing:"0",fontWeight:"400"},Ta=["Webkit","O","Moz","ms"];function Ua(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=Ta.length;while(e--)if(b=Ta[e]+c,b in a)return b;return d}function Va(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=m._data(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&U(d)&&(f[g]=m._data(d,"olddisplay",Fa(d.nodeName)))):(e=U(d),(c&&"none"!==c||!e)&&m._data(d,"olddisplay",e?c:m.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}function Wa(a,b,c){var d=Pa.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Xa(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=m.css(a,c+T[f],!0,e)),d?("content"===c&&(g-=m.css(a,"padding"+T[f],!0,e)),"margin"!==c&&(g-=m.css(a,"border"+T[f]+"Width",!0,e))):(g+=m.css(a,"padding"+T[f],!0,e),"padding"!==c&&(g+=m.css(a,"border"+T[f]+"Width",!0,e)));return g}function Ya(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=Ia(a),g=k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=Ja(a,b,f),(0>e||null==e)&&(e=a.style[b]),Ha.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Xa(a,b,c||(g?"border":"content"),d,f)+"px"}m.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Ja(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":k.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=m.camelCase(b),i=a.style;if(b=m.cssProps[h]||(m.cssProps[h]=Ua(i,h)),g=m.cssHooks[b]||m.cssHooks[h],void 0===c)return g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b];if(f=typeof c,"string"===f&&(e=Qa.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(m.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||m.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),!(g&&"set"in g&&void 0===(c=g.set(a,c,d)))))try{i[b]=c}catch(j){}}},css:function(a,b,c,d){var e,f,g,h=m.camelCase(b);return b=m.cssProps[h]||(m.cssProps[h]=Ua(a.style,h)),g=m.cssHooks[b]||m.cssHooks[h],g&&"get"in g&&(f=g.get(a,!0,c)),void 0===f&&(f=Ja(a,b,d)),"normal"===f&&b in Sa&&(f=Sa[b]),""===c||c?(e=parseFloat(f),c===!0||m.isNumeric(e)?e||0:f):f}}),m.each(["height","width"],function(a,b){m.cssHooks[b]={get:function(a,c,d){return c?Oa.test(m.css(a,"display"))&&0===a.offsetWidth?m.swap(a,Ra,function(){return Ya(a,b,d)}):Ya(a,b,d):void 0},set:function(a,c,d){var e=d&&Ia(a);return Wa(a,c,d?Xa(a,b,d,k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,e),e):0)}}}),k.opacity||(m.cssHooks.opacity={get:function(a,b){return Na.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=m.isNumeric(b)?"alpha(opacity="+100*b+")":"",f=d&&d.filter||c.filter||"";c.zoom=1,(b>=1||""===b)&&""===m.trim(f.replace(Ma,""))&&c.removeAttribute&&(c.removeAttribute("filter"),""===b||d&&!d.filter)||(c.filter=Ma.test(f)?f.replace(Ma,e):f+" "+e)}}),m.cssHooks.marginRight=La(k.reliableMarginRight,function(a,b){return b?m.swap(a,{display:"inline-block"},Ja,[a,"marginRight"]):void 0}),m.each({margin:"",padding:"",border:"Width"},function(a,b){m.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+T[d]+b]=f[d]||f[d-2]||f[0];return e}},Ga.test(a)||(m.cssHooks[a+b].set=Wa)}),m.fn.extend({css:function(a,b){return V(this,function(a,b,c){var d,e,f={},g=0;if(m.isArray(b)){for(d=Ia(a),e=b.length;e>g;g++)f[b[g]]=m.css(a,b[g],!1,d);return f}return void 0!==c?m.style(a,b,c):m.css(a,b)},a,b,arguments.length>1)},show:function(){return Va(this,!0)},hide:function(){return Va(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){U(this)?m(this).show():m(this).hide()})}});function Za(a,b,c,d,e){ | |
return new Za.prototype.init(a,b,c,d,e)}m.Tween=Za,Za.prototype={constructor:Za,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(m.cssNumber[c]?"":"px")},cur:function(){var a=Za.propHooks[this.prop];return a&&a.get?a.get(this):Za.propHooks._default.get(this)},run:function(a){var b,c=Za.propHooks[this.prop];return this.options.duration?this.pos=b=m.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Za.propHooks._default.set(this),this}},Za.prototype.init.prototype=Za.prototype,Za.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=m.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){m.fx.step[a.prop]?m.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[m.cssProps[a.prop]]||m.cssHooks[a.prop])?m.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Za.propHooks.scrollTop=Za.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},m.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},m.fx=Za.prototype.init,m.fx.step={};var $a,_a,ab=/^(?:toggle|show|hide)$/,bb=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),cb=/queueHooks$/,db=[ib],eb={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=bb.exec(b),f=e&&e[3]||(m.cssNumber[a]?"":"px"),g=(m.cssNumber[a]||"px"!==f&&+d)&&bb.exec(m.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,m.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function fb(){return setTimeout(function(){$a=void 0}),$a=m.now()}function gb(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=T[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function hb(a,b,c){for(var d,e=(eb[b]||[]).concat(eb["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ib(a,b,c){var d,e,f,g,h,i,j,l,n=this,o={},p=a.style,q=a.nodeType&&U(a),r=m._data(a,"fxshow");c.queue||(h=m._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,n.always(function(){n.always(function(){h.unqueued--,m.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=m.css(a,"display"),l="none"===j?m._data(a,"olddisplay")||Fa(a.nodeName):j,"inline"===l&&"none"===m.css(a,"float")&&(k.inlineBlockNeedsLayout&&"inline"!==Fa(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",k.shrinkWrapBlocks()||n.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],ab.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||m.style(a,d)}else j=void 0;if(m.isEmptyObject(o))"inline"===("none"===j?Fa(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=m._data(a,"fxshow",{}),f&&(r.hidden=!q),q?m(a).show():n.done(function(){m(a).hide()}),n.done(function(){var b;m._removeData(a,"fxshow");for(b in o)m.style(a,b,o[b])});for(d in o)g=hb(q?r[d]:0,d,n),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function jb(a,b){var c,d,e,f,g;for(c in a)if(d=m.camelCase(c),e=b[d],f=a[c],m.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=m.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kb(a,b,c){var d,e,f=0,g=db.length,h=m.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=$a||fb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:m.extend({},b),opts:m.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:$a||fb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=m.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jb(k,j.opts.specialEasing);g>f;f++)if(d=db[f].call(j,a,k,j.opts))return d;return m.map(k,hb,j),m.isFunction(j.opts.start)&&j.opts.start.call(a,j),m.fx.timer(m.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}m.Animation=m.extend(kb,{tweener:function(a,b){m.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],eb[c]=eb[c]||[],eb[c].unshift(b)},prefilter:function(a,b){b?db.unshift(a):db.push(a)}}),m.speed=function(a,b,c){var d=a&&"object"==typeof a?m.extend({},a):{complete:c||!c&&b||m.isFunction(a)&&a,duration:a,easing:c&&b||b&&!m.isFunction(b)&&b};return d.duration=m.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in m.fx.speeds?m.fx.speeds[d.duration]:m.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){m.isFunction(d.old)&&d.old.call(this),d.queue&&m.dequeue(this,d.queue)},d},m.fn.extend({fadeTo:function(a,b,c,d){return this.filter(U).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=m.isEmptyObject(a),f=m.speed(b,c,d),g=function(){var b=kb(this,m.extend({},a),f);(e||m._data(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=m.timers,g=m._data(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&cb.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&m.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=m._data(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=m.timers,g=d?d.length:0;for(c.finish=!0,m.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),m.each(["toggle","show","hide"],function(a,b){var c=m.fn[b];m.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(gb(b,!0),a,d,e)}}),m.each({slideDown:gb("show"),slideUp:gb("hide"),slideToggle:gb("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){m.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),m.timers=[],m.fx.tick=function(){var a,b=m.timers,c=0;for($a=m.now();c<b.length;c++)a=b[c],a()||b[c]!==a||b.splice(c--,1);b.length||m.fx.stop(),$a=void 0},m.fx.timer=function(a){m.timers.push(a),a()?m.fx.start():m.timers.pop()},m.fx.interval=13,m.fx.start=function(){_a||(_a=setInterval(m.fx.tick,m.fx.interval))},m.fx.stop=function(){clearInterval(_a),_a=null},m.fx.speeds={slow:600,fast:200,_default:400},m.fn.delay=function(a,b){return a=m.fx?m.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a,b,c,d,e;b=y.createElement("div"),b.setAttribute("className","t"),b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=y.createElement("select"),e=c.appendChild(y.createElement("option")),a=b.getElementsByTagName("input")[0],d.style.cssText="top:1px",k.getSetAttribute="t"!==b.className,k.style=/top/.test(d.getAttribute("style")),k.hrefNormalized="/a"===d.getAttribute("href"),k.checkOn=!!a.value,k.optSelected=e.selected,k.enctype=!!y.createElement("form").enctype,c.disabled=!0,k.optDisabled=!e.disabled,a=y.createElement("input"),a.setAttribute("value",""),k.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),k.radioValue="t"===a.value}();var lb=/\r/g;m.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=m.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,m(this).val()):a,null==e?e="":"number"==typeof e?e+="":m.isArray(e)&&(e=m.map(e,function(a){return null==a?"":a+""})),b=m.valHooks[this.type]||m.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=m.valHooks[e.type]||m.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(lb,""):null==c?"":c)}}}),m.extend({valHooks:{option:{get:function(a){var b=m.find.attr(a,"value");return null!=b?b:m.trim(m.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&m.nodeName(c.parentNode,"optgroup"))){if(b=m(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=m.makeArray(b),g=e.length;while(g--)if(d=e[g],m.inArray(m.valHooks.option.get(d),f)>=0)try{d.selected=c=!0}catch(h){d.scrollHeight}else d.selected=!1;return c||(a.selectedIndex=-1),e}}}}),m.each(["radio","checkbox"],function(){m.valHooks[this]={set:function(a,b){return m.isArray(b)?a.checked=m.inArray(m(a).val(),b)>=0:void 0}},k.checkOn||(m.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var mb,nb,ob=m.expr.attrHandle,pb=/^(?:checked|selected)$/i,qb=k.getSetAttribute,rb=k.input;m.fn.extend({attr:function(a,b){return V(this,m.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){m.removeAttr(this,a)})}}),m.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===K?m.prop(a,b,c):(1===f&&m.isXMLDoc(a)||(b=b.toLowerCase(),d=m.attrHooks[b]||(m.expr.match.bool.test(b)?nb:mb)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=m.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void m.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=m.propFix[c]||c,m.expr.match.bool.test(c)?rb&&qb||!pb.test(c)?a[d]=!1:a[m.camelCase("default-"+c)]=a[d]=!1:m.attr(a,c,""),a.removeAttribute(qb?c:d)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&m.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),nb={set:function(a,b,c){return b===!1?m.removeAttr(a,c):rb&&qb||!pb.test(c)?a.setAttribute(!qb&&m.propFix[c]||c,c):a[m.camelCase("default-"+c)]=a[c]=!0,c}},m.each(m.expr.match.bool.source.match(/\w+/g),function(a,b){var c=ob[b]||m.find.attr;ob[b]=rb&&qb||!pb.test(b)?function(a,b,d){var e,f;return d||(f=ob[b],ob[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,ob[b]=f),e}:function(a,b,c){return c?void 0:a[m.camelCase("default-"+b)]?b.toLowerCase():null}}),rb&&qb||(m.attrHooks.value={set:function(a,b,c){return m.nodeName(a,"input")?void(a.defaultValue=b):mb&&mb.set(a,b,c)}}),qb||(mb={set:function(a,b,c){var d=a.getAttributeNode(c);return d||a.setAttributeNode(d=a.ownerDocument.createAttribute(c)),d.value=b+="","value"===c||b===a.getAttribute(c)?b:void 0}},ob.id=ob.name=ob.coords=function(a,b,c){var d;return c?void 0:(d=a.getAttributeNode(b))&&""!==d.value?d.value:null},m.valHooks.button={get:function(a,b){var c=a.getAttributeNode(b);return c&&c.specified?c.value:void 0},set:mb.set},m.attrHooks.contenteditable={set:function(a,b,c){mb.set(a,""===b?!1:b,c)}},m.each(["width","height"],function(a,b){m.attrHooks[b]={set:function(a,c){return""===c?(a.setAttribute(b,"auto"),c):void 0}}})),k.style||(m.attrHooks.style={get:function(a){return a.style.cssText||void 0},set:function(a,b){return a.style.cssText=b+""}});var sb=/^(?:input|select|textarea|button|object)$/i,tb=/^(?:a|area)$/i;m.fn.extend({prop:function(a,b){return V(this,m.prop,a,b,arguments.length>1)},removeProp:function(a){return a=m.propFix[a]||a,this.each(function(){try{this[a]=void 0,delete this[a]}catch(b){}})}}),m.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!m.isXMLDoc(a),f&&(b=m.propFix[b]||b,e=m.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=m.find.attr(a,"tabindex");return b?parseInt(b,10):sb.test(a.nodeName)||tb.test(a.nodeName)&&a.href?0:-1}}}}),k.hrefNormalized||m.each(["href","src"],function(a,b){m.propHooks[b]={get:function(a){return a.getAttribute(b,4)}}}),k.optSelected||(m.propHooks.selected={get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}}),m.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){m.propFix[this.toLowerCase()]=this}),k.enctype||(m.propFix.enctype="encoding");var ub=/[\t\r\n\f]/g;m.fn.extend({addClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j="string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).addClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ub," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=m.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j=0===arguments.length||"string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).removeClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ub," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?m.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(m.isFunction(a)?function(c){m(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=m(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===K||"boolean"===c)&&(this.className&&m._data(this,"__className__",this.className),this.className=this.className||a===!1?"":m._data(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(ub," ").indexOf(b)>=0)return!0;return!1}}),m.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){m.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),m.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var vb=m.now(),wb=/\?/,xb=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;m.parseJSON=function(b){if(a.JSON&&a.JSON.parse)return a.JSON.parse(b+"");var c,d=null,e=m.trim(b+"");return e&&!m.trim(e.replace(xb,function(a,b,e,f){return c&&b&&(d=0),0===d?a:(c=e||b,d+=!f-!e,"")}))?Function("return "+e)():m.error("Invalid JSON: "+b)},m.parseXML=function(b){var c,d;if(!b||"string"!=typeof b)return null;try{a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b))}catch(e){c=void 0}return c&&c.documentElement&&!c.getElementsByTagName("parsererror").length||m.error("Invalid XML: "+b),c};var yb,zb,Ab=/#.*$/,Bb=/([?&])_=[^&]*/,Cb=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Db=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Eb=/^(?:GET|HEAD)$/,Fb=/^\/\//,Gb=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Hb={},Ib={},Jb="*/".concat("*");try{zb=location.href}catch(Kb){zb=y.createElement("a"),zb.href="",zb=zb.href}yb=Gb.exec(zb.toLowerCase())||[];function Lb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(m.isFunction(c))while(d=f[e++])"+"===d.charAt(0)?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Mb(a,b,c,d){var e={},f=a===Ib;function g(h){var i;return e[h]=!0,m.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Nb(a,b){var c,d,e=m.ajaxSettings.flatOptions||{};for(d in b)void 0!==b[d]&&((e[d]?a:c||(c={}))[d]=b[d]);return c&&m.extend(!0,a,c),a}function Ob(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===e&&(e=a.mimeType||b.getResponseHeader("Content-Type"));if(e)for(g in h)if(h[g]&&h[g].test(e)){i.unshift(g);break}if(i[0]in c)f=i[0];else{for(g in c){if(!i[0]||a.converters[g+" "+i[0]]){f=g;break}d||(d=g)}f=f||d}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Pb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}m.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:zb,type:"GET",isLocal:Db.test(yb[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Jb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":m.parseJSON,"text xml":m.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Nb(Nb(a,m.ajaxSettings),b):Nb(m.ajaxSettings,a)},ajaxPrefilter:Lb(Hb),ajaxTransport:Lb(Ib),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=m.ajaxSetup({},b),l=k.context||k,n=k.context&&(l.nodeType||l.jquery)?m(l):m.event,o=m.Deferred(),p=m.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!j){j={};while(b=Cb.exec(f))j[b[1].toLowerCase()]=b[2]}b=j[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?f:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return i&&i.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||zb)+"").replace(Ab,"").replace(Fb,yb[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=m.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(c=Gb.exec(k.url.toLowerCase()),k.crossDomain=!(!c||c[1]===yb[1]&&c[2]===yb[2]&&(c[3]||("http:"===c[1]?"80":"443"))===(yb[3]||("http:"===yb[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=m.param(k.data,k.traditional)),Mb(Hb,k,b,v),2===t)return v;h=m.event&&k.global,h&&0===m.active++&&m.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!Eb.test(k.type),e=k.url,k.hasContent||(k.data&&(e=k.url+=(wb.test(e)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=Bb.test(e)?e.replace(Bb,"$1_="+vb++):e+(wb.test(e)?"&":"?")+"_="+vb++)),k.ifModified&&(m.lastModified[e]&&v.setRequestHeader("If-Modified-Since",m.lastModified[e]),m.etag[e]&&v.setRequestHeader("If-None-Match",m.etag[e])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+Jb+"; q=0.01":""):k.accepts["*"]);for(d in k.headers)v.setRequestHeader(d,k.headers[d]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(d in{success:1,error:1,complete:1})v[d](k[d]);if(i=Mb(Ib,k,b,v)){v.readyState=1,h&&n.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,i.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,c,d){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),i=void 0,f=d||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,c&&(u=Ob(k,v,c)),u=Pb(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(m.lastModified[e]=w),w=v.getResponseHeader("etag"),w&&(m.etag[e]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,h&&n.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),h&&(n.trigger("ajaxComplete",[v,k]),--m.active||m.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return m.get(a,b,c,"json")},getScript:function(a,b){return m.get(a,void 0,b,"script")}}),m.each(["get","post"],function(a,b){m[b]=function(a,c,d,e){return m.isFunction(c)&&(e=e||d,d=c,c=void 0),m.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),m._evalUrl=function(a){return m.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},m.fn.extend({wrapAll:function(a){if(m.isFunction(a))return this.each(function(b){m(this).wrapAll(a.call(this,b))});if(this[0]){var b=m(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&1===a.firstChild.nodeType)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return this.each(m.isFunction(a)?function(b){m(this).wrapInner(a.call(this,b))}:function(){var b=m(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=m.isFunction(a);return this.each(function(c){m(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){m.nodeName(this,"body")||m(this).replaceWith(this.childNodes)}).end()}}),m.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0||!k.reliableHiddenOffsets()&&"none"===(a.style&&a.style.display||m.css(a,"display"))},m.expr.filters.visible=function(a){return!m.expr.filters.hidden(a)};var Qb=/%20/g,Rb=/\[\]$/,Sb=/\r?\n/g,Tb=/^(?:submit|button|image|reset|file)$/i,Ub=/^(?:input|select|textarea|keygen)/i;function Vb(a,b,c,d){var e;if(m.isArray(b))m.each(b,function(b,e){c||Rb.test(a)?d(a,e):Vb(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==m.type(b))d(a,b);else for(e in b)Vb(a+"["+e+"]",b[e],c,d)}m.param=function(a,b){var c,d=[],e=function(a,b){b=m.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=m.ajaxSettings&&m.ajaxSettings.traditional),m.isArray(a)||a.jquery&&!m.isPlainObject(a))m.each(a,function(){e(this.name,this.value)});else for(c in a)Vb(c,a[c],b,e);return d.join("&").replace(Qb,"+")},m.fn.extend({serialize:function(){return m.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=m.prop(this,"elements");return a?m.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!m(this).is(":disabled")&&Ub.test(this.nodeName)&&!Tb.test(a)&&(this.checked||!W.test(a))}).map(function(a,b){var c=m(this).val();return null==c?null:m.isArray(c)?m.map(c,function(a){return{name:b.name,value:a.replace(Sb,"\r\n")}}):{name:b.name,value:c.replace(Sb,"\r\n")}}).get()}}),m.ajaxSettings.xhr=void 0!==a.ActiveXObject?function(){return!this.isLocal&&/^(get|post|head|put|delete|options)$/i.test(this.type)&&Zb()||$b()}:Zb;var Wb=0,Xb={},Yb=m.ajaxSettings.xhr();a.attachEvent&&a.attachEvent("onunload",function(){for(var a in Xb)Xb[a](void 0,!0)}),k.cors=!!Yb&&"withCredentials"in Yb,Yb=k.ajax=!!Yb,Yb&&m.ajaxTransport(function(a){if(!a.crossDomain||k.cors){var b;return{send:function(c,d){var e,f=a.xhr(),g=++Wb;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)void 0!==c[e]&&f.setRequestHeader(e,c[e]+"");f.send(a.hasContent&&a.data||null),b=function(c,e){var h,i,j;if(b&&(e||4===f.readyState))if(delete Xb[g],b=void 0,f.onreadystatechange=m.noop,e)4!==f.readyState&&f.abort();else{j={},h=f.status,"string"==typeof f.responseText&&(j.text=f.responseText);try{i=f.statusText}catch(k){i=""}h||!a.isLocal||a.crossDomain?1223===h&&(h=204):h=j.text?200:404}j&&d(h,i,j,f.getAllResponseHeaders())},a.async?4===f.readyState?setTimeout(b):f.onreadystatechange=Xb[g]=b:b()},abort:function(){b&&b(void 0,!0)}}}});function Zb(){try{return new a.XMLHttpRequest}catch(b){}}function $b(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}m.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return m.globalEval(a),a}}}),m.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),m.ajaxTransport("script",function(a){if(a.crossDomain){var b,c=y.head||m("head")[0]||y.documentElement;return{send:function(d,e){b=y.createElement("script"),b.async=!0,a.scriptCharset&&(b.charset=a.scriptCharset),b.src=a.url,b.onload=b.onreadystatechange=function(a,c){(c||!b.readyState||/loaded|complete/.test(b.readyState))&&(b.onload=b.onreadystatechange=null,b.parentNode&&b.parentNode.removeChild(b),b=null,c||e(200,"success"))},c.insertBefore(b,c.firstChild)},abort:function(){b&&b.onload(void 0,!0)}}}});var _b=[],ac=/(=)\?(?=&|$)|\?\?/;m.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=_b.pop()||m.expando+"_"+vb++;return this[a]=!0,a}}),m.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(ac.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&ac.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=m.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(ac,"$1"+e):b.jsonp!==!1&&(b.url+=(wb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||m.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,_b.push(e)),g&&m.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),m.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||y;var d=u.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=m.buildFragment([a],b,e),e&&e.length&&m(e).remove(),m.merge([],d.childNodes))};var bc=m.fn.load;m.fn.load=function(a,b,c){if("string"!=typeof a&&bc)return bc.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=m.trim(a.slice(h,a.length)),a=a.slice(0,h)),m.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(f="POST"),g.length>0&&m.ajax({url:a,type:f,dataType:"html",data:b}).done(function(a){e=arguments,g.html(d?m("<div>").append(m.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},m.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){m.fn[b]=function(a){return this.on(b,a)}}),m.expr.filters.animated=function(a){return m.grep(m.timers,function(b){return a===b.elem}).length};var cc=a.document.documentElement;function dc(a){return m.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}m.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=m.css(a,"position"),l=m(a),n={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=m.css(a,"top"),i=m.css(a,"left"),j=("absolute"===k||"fixed"===k)&&m.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),m.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(n.top=b.top-h.top+g),null!=b.left&&(n.left=b.left-h.left+e),"using"in b?b.using.call(a,n):l.css(n)}},m.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){m.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,m.contains(b,e)?(typeof e.getBoundingClientRect!==K&&(d=e.getBoundingClientRect()),c=dc(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===m.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),m.nodeName(a[0],"html")||(c=a.offset()),c.top+=m.css(a[0],"borderTopWidth",!0),c.left+=m.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-m.css(d,"marginTop",!0),left:b.left-c.left-m.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||cc;while(a&&!m.nodeName(a,"html")&&"static"===m.css(a,"position"))a=a.offsetParent;return a||cc})}}),m.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);m.fn[a]=function(d){return V(this,function(a,d,e){var f=dc(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?m(f).scrollLeft():e,c?e:m(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),m.each(["top","left"],function(a,b){m.cssHooks[b]=La(k.pixelPosition,function(a,c){return c?(c=Ja(a,b),Ha.test(c)?m(a).position()[b]+"px":c):void 0})}),m.each({Height:"height",Width:"width"},function(a,b){m.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){m.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return V(this,function(b,c,d){var e;return m.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?m.css(b,c,g):m.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),m.fn.size=function(){return this.length},m.fn.andSelf=m.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return m});var ec=a.jQuery,fc=a.$;return m.noConflict=function(b){return a.$===m&&(a.$=fc),b&&a.jQuery===m&&(a.jQuery=ec),m},typeof b===K&&(a.jQuery=a.$=m),m}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
0 info it worked if it ends with ok | |
1 verbose cli [ 'node', '/usr/local/bin/npm', 'start' ] | |
2 info using [email protected] | |
3 info using [email protected] | |
4 verbose run-script [ 'prestart', 'start', 'poststart' ] | |
5 info prestart [email protected] | |
6 info start [email protected] | |
7 verbose unsafe-perm in lifecycle true | |
8 info [email protected] Failed to exec start script | |
9 verbose stack Error: [email protected] start: `node server.js` | |
9 verbose stack Exit status 1 | |
9 verbose stack at EventEmitter.<anonymous> (/usr/local/lib/node_modules/npm/lib/utils/lifecycle.js:213:16) | |
9 verbose stack at EventEmitter.emit (events.js:110:17) | |
9 verbose stack at ChildProcess.<anonymous> (/usr/local/lib/node_modules/npm/lib/utils/spawn.js:14:12) | |
9 verbose stack at ChildProcess.emit (events.js:110:17) | |
9 verbose stack at maybeClose (child_process.js:1008:16) | |
9 verbose stack at Process.ChildProcess._handle.onexit (child_process.js:1080:5) | |
10 verbose pkgid [email protected] | |
11 verbose cwd /Users/logsol/Work/projects/js/midihack | |
12 error Darwin 13.4.0 | |
13 error argv "node" "/usr/local/bin/npm" "start" | |
14 error node v0.12.0 | |
15 error npm v2.5.1 | |
16 error code ELIFECYCLE | |
17 error [email protected] start: `node server.js` | |
17 error Exit status 1 | |
18 error Failed at the [email protected] start script 'node server.js'. | |
18 error This is most likely a problem with the midihack package, | |
18 error not with npm itself. | |
18 error Tell the author that this fails on your system: | |
18 error node server.js | |
18 error You can get their info via: | |
18 error npm owner ls midihack | |
18 error There is likely additional logging output above. | |
19 verbose exit [ 1, true ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "midihack", | |
"author": "logsol <[email protected]>", | |
"description": "leap motion room sound", | |
"version": "0.1.0", | |
"repository": { | |
"type": "git" | |
}, | |
"bugs": { | |
"email": "[email protected]" | |
}, | |
"main": "", | |
"dependencies": { | |
"socket.io": "= 0.9.6", | |
"node-static": ">= 0.6.0", | |
"requirejs": "= 2.0.4", | |
"leapjs": "= 0.6.2" | |
}, | |
"devDependencies": {}, | |
"optionalDependencies": {}, | |
"engine": "node >= 0.8.4", | |
"config": { "port": "1234" }, | |
"private": true | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var nodeStatic = require('node-static'), | |
http = require('http'), | |
fs = require('fs'), | |
io = require('socket.io'), | |
Leap = require('leapjs'); | |
var clients = {}; | |
var clientId = null; | |
var leapPosition = {}; | |
var fileServer = new nodeStatic.Server("./", { cache: 0, gzip: false }); | |
var server = http.createServer( | |
function (req, res) { | |
var fullBody = ''; | |
req.addListener('data', function(chunk) { | |
fullBody += chunk.toString(); | |
}); | |
req.addListener('error', function(err) { | |
console.log(''); | |
}); | |
req.addListener('end', function () { | |
switch(true) { | |
case req.url == '/client.js': | |
fileServer.serveFile('./client.js', 200, {}, req, res); | |
break; | |
case req.url == '/jquery.js': | |
fileServer.serveFile('./jquery.js', 200, {}, req, res); | |
break; | |
case req.url == '/Audiolet.js': | |
fileServer.serveFile('./Audiolet.js', 200, {}, req, res); | |
break; | |
case req.url == '/sw.mp3': | |
fileServer.serveFile('./sw.mp3', 200, {}, req, res); | |
break; | |
default: | |
fileServer.serveFile('./index.html', 200, {}, req, res); | |
break; | |
} | |
}); | |
} | |
); | |
server.once('error', function(err) { | |
if(err.code == 'EADDRINUSE') { | |
console.error('port already in use. Closing.'); | |
} else { | |
throw new Error(err); | |
} | |
}); | |
server.listen(1234); | |
var socket = io.listen(server); | |
socket.configure('development', function () { | |
this.set('log level', 3); | |
}); | |
socket.on('connection', function (link) { | |
clients[link.id] = new Client(link); | |
link.send("welcome|" + link.id); | |
link.on('message', function (message) { | |
var position = JSON.parse(message); | |
clients[link.id].setPosition(position); | |
broadcastPositions(); | |
}); | |
link.on('disconnect', function () { | |
delete clients[link.id]; | |
broadcastPositions(); | |
}); | |
}); | |
function broadcastPositions () { | |
var positions = {}; | |
for (var id in clients) { | |
if (clients.hasOwnProperty(id)) { | |
positions[id] = { | |
x: clients[id].x, | |
y: clients[id].y, | |
} | |
} | |
} | |
positions['leap'] = leapPosition; | |
for (var id in clients) { | |
if (clients.hasOwnProperty(id)) { | |
link = clients[id].link; | |
link.send("positions|" + JSON.stringify(positions)); | |
} | |
} | |
} | |
function Client (link) { | |
this.link = link; | |
this.id = link.id; | |
this.x = null; | |
this.y = null; | |
} | |
Client.prototype.setPosition = function(position) { | |
this.x = position.x; | |
this.y = position.y; | |
}; | |
function clip(x){ | |
return x<0 ? 0 : x>1 ? 1 : x; | |
} | |
var controllerOptions = {enableGestures: true}; | |
Leap.loop(controllerOptions, function(frame) { | |
if (frame.hands.length > 0) { | |
for (var i = 0; i < frame.hands.length; i++) { | |
var hand = frame.hands[i]; | |
if (hand.type != 'right') continue; | |
var x = clip((hand.palmPosition[0] + 300) / 600); | |
var y = clip((hand.palmPosition[2] - 300) / 600 * -1); | |
//var height = clip(hand.palmPosition[1] / 500); | |
//var grab = clip(hand.grabStrength); | |
//var pinch = clip(hand.pinchStrength); | |
leapPosition.x = x; | |
leapPosition.y = 1-y; | |
broadcastPositions(); | |
} | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment