diff options
Diffstat (limited to 'public/js/vendor/Audiolet.js')
| -rw-r--r-- | public/js/vendor/Audiolet.js | 680 |
1 files changed, 498 insertions, 182 deletions
diff --git a/public/js/vendor/Audiolet.js b/public/js/vendor/Audiolet.js index b7be12e..66dc9e8 100644 --- a/public/js/vendor/Audiolet.js +++ b/public/js/vendor/Audiolet.js @@ -1,4 +1,19 @@ /** + * 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); +}; + + +/** * A variable size multi-channel audio buffer. * * @constructor @@ -836,21 +851,6 @@ AudioletInput.prototype.toString = function() { /** - * 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 @@ -2604,6 +2604,80 @@ CrossFade.prototype.toString = function() { */ /** + * 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 + */ + +/** * Damped comb filter * * **Inputs** @@ -2704,80 +2778,6 @@ DampedCombFilter.prototype.toString = function() { */ /** - * 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** @@ -2934,98 +2934,6 @@ DiscontinuityDetector.prototype.toString = function() { */ /** - * 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** @@ -3161,6 +3069,98 @@ FFT.prototype.toString = function() { * @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 + */ + /* * Multiply values * @@ -5634,6 +5634,322 @@ var EqualTemperamentTuning = function(pitchesPerOctave) { }; extend(EqualTemperamentTuning, Tuning); +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; +}; + var Sink = this.Sink = function (global) { /** @@ -6431,7 +6747,7 @@ sinks('webaudio', function (readFn, channelCount, bufferSize, sampleRate) { soundData = null, zeroBuffer = null; self.start.apply(self, arguments); - node = context.createJavaScriptNode(self.bufferSize, 0, self.channelCount); + node = context.createJavaScriptNode(self.bufferSize, self.channelCount, self.channelCount); function bufferFill(e) { var outputBuffer = e.outputBuffer, |
