From dd4308eae83e7a86949fa677c36ebba6f2a30111 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Fri, 2 Aug 2013 21:02:15 -0400 Subject: grid stuff on audiolet branch --- app.js | 5 +- lib/room.js | 79 +- public/js/grain.js | 37 + public/js/vendor/audiolib.js | 5158 ++++++++++++++++++++++++++++++++++++++++++ views/grain.ejs | 65 + views/grid.ejs | 18 - 6 files changed, 5311 insertions(+), 51 deletions(-) create mode 100644 public/js/grain.js create mode 100644 public/js/vendor/audiolib.js create mode 100644 views/grain.ejs diff --git a/app.js b/app.js index ca5259e..b1428c5 100644 --- a/app.js +++ b/app.js @@ -46,7 +46,10 @@ var db = require( './lib/models' ), room = require( './lib/room' ); app.get( '/', function( req, res ) { - res.render( 'grid', {} ); + res.render( 'grain', {} ); +}); +app.get( '/grain', function( req, res ) { + res.render( 'grain', {} ); }); app.get( '/grid', function( req, res ) { res.render( 'grid', {} ); diff --git a/lib/room.js b/lib/room.js index 62e2e16..0aed7eb 100644 --- a/lib/room.js +++ b/lib/room.js @@ -18,37 +18,44 @@ function sanitizeJSON (json) { } } -function Grid(){ +function Param(state){ var base = this; - var tempo = 125; - var pattern = [ - [1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0], - [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0], - [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0], - [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0], - [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0], - [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0], - [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0], - [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0] - ]; - function init(){ - bind(); - } - function bind(){ - } - base.getState = function(){ - return { - 'pattern': pattern, - 'tempo': tempo - } + base.state = state || {}; + base.get = function(){ + return base.state; } - base.setNote = function(data){ - if (data.channel < pattern.length && data.step < pattern[data.channel].length) { - var state = data.state == 1 ? 1 : 0; - pattern[data.channel][data.step] = state; + base.set = function(param){ + if (data.key in state) { + base.state[data.key] = data.value; + return true; } + return false; } - init(); +} +function Grid(){ + this.param = new Param({ + 'tempo': 125, + 'pattern': [ + [1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0], + [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0], + [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0], + [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0], + [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0], + [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0], + [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0], + [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0] + ] + }); +} +function Grain(){ + this.param = new Param({ + 'sample': 'Clap.wav', + 'attack': 0, // grain envelope (attack) + 'sustain': 0, // grain envelope (hold) + 'step': 100, // % of envelope size used to find next grain + 'rate': 100, // % of envelope size used to emit next grain + // other things in fruity were step LFO, rate LFO, panning.. + }); } function Connection (socket) { @@ -62,8 +69,8 @@ function Connection (socket) { socket.on('event-mouse', mouse); socket.on('event-chat', chat); - socket.on('event-note', note); - socket.on('event-tempo', tempo); + listen('event-grid', grid); + listen('event-grain', grain); socket.on('disconnect', disconnect); } @@ -71,12 +78,22 @@ function Connection (socket) { socket.broadcast.emit("event-join", json); socket.emit("event-grid", grid.getState()); } + function listen(msg, obj){ + socket.on(msg, function (json){ + var data = sanitizeJSON(json); + if (data && 'key' in data && 'value' in data && obj.param.set(data)) {; + socket.broadcast.emit(msg, data); + } + }); + } + function mouse(json){ socket.broadcast.emit("event-mouse", json); } function chat(json){ socket.broadcast.emit("event-chat", json); } + function note(json){ var data = sanitizeJSON(json); if (data) { @@ -84,9 +101,6 @@ function Connection (socket) { socket.broadcast.emit("event-note", data); } } - function tempo(json){ - socket.broadcast.emit("event-tempo", json); - } function disconnect(json){ base = null; } @@ -94,6 +108,7 @@ function Connection (socket) { } var grid = new Grid (); +var grain = new Grain (); exports.connect = function(io, socket) { IO = IO || io; diff --git a/public/js/grain.js b/public/js/grain.js new file mode 100644 index 0000000..a0b5d2a --- /dev/null +++ b/public/js/grain.js @@ -0,0 +1,37 @@ + + +var bufferSize = 4096; // 65536 / 2; +var sampleRate = 44100; +var latency = 1000 * bufferSize / sampleRate; +var audioletReady = false; + +var samples = [ + 'KickDrum0001.wav', + 'Clap.wav', + 'Closed Hihat0001.wav', + 'Open Hihat0001.wav', + 'SnareDrum0001.wav', + 'Clav.wav', + 'Mid Tom0001.wav', + 'Rimshot.wav', +]; + + + var dev = audioLib.AudioDevice(function(buffer, ch){ + var l = buffer.length / ch, + smpl, i, n; + for (n=0; n} buffer The buffer to apply the effect to. + * @arg {UInt} min:1 !channelCount The amount of channels the buffer has. + * @arg {Array} default:buffer out The optional output buffer. + * @return {Array} The output buffer. +*/ + append: function (buffer, channelCount, out) { + var l = buffer.length, + i, n; + out = out || buffer; + channelCount = channelCount || this.channelCount; + for (i=0; i} buffer The buffer to apply the effect to. + * @arg {UInt} min:1 !channelCount The amount of channels the buffer has. + * @arg {Array} default:buffer !out The optional output buffer. + * @return {Array} The output buffer. +*/ + append: function (buffer, channelCount, out) { + var l = buffer.length, + i, n; + out = out || buffer; + channelCount = channelCount || this.channelCount; + for (i=0; i= 1) { + this.state = 1; + } + }, + function () { // Decay + this.value -= 1000 / this.sampleRate / this.decay * this.sustain; + if (this.value <= this.sustain) { + if (this.sustainTime === null) { + this.state = 2; + } else { + this._st = 0; + this.state = 4; + } + } + }, + function () { // Sustain + this.value = this.sustain; + }, + function () { // Release + this.value = Math.max(0, this.value - 1000 / this.sampleRate / this.release); + }, + function () { // Timed sustain + this.value = this.sustain; + if (this._st++ >= this.sampleRate * 0.001 * this.sustainTime) { + this._st = 0; + this.state = this.releaseTime === null ? 3 : 5; + } + }, + function () { // Timed release + this.value = Math.max(0, this.value - 1000 / this.sampleRate / this.release); + if (this._st++ >= this.sampleRate * 0.001 * this.releaseTime) { + this._st = 0; + this.state = 0; + } + } + ] +}; + +/** + * Creates a StepSequencer. + * + * @control + * + * @arg =!sampleRate + * @arg =!stepLength + * @arg =!steps + * @arg =!attack + * + * @param type:UInt units:Hz default:44100 sampleRate Sample Rate the apparatus operates on. + * @param type:Float min:0 units:ms default:200 stepLength The time a single step of the sequencer lasts. + * @param type:Array default:0 steps Array of steps (positive float) for the sequencer to iterate. + * @param type:Float min:0.0 max:1.0 default:0.0 attack The time the linear transition between the steps. Measured in steps. + * @param type:Float default:0.0 phase The current phase of the sequencer. +*/ +function StepSequencer (sampleRate, stepLength, steps, attack) { + var self = this, + phase = 0; + + this.sampleRate = isNaN(sampleRate) ? this.sampleRate : sampleRate; + this.stepLength = isNaN(stepLength) ? this.stepLength : stepLength; + this.steps = steps || [1, 0]; + this.attack = isNaN(attack) ? this.attack : attack; +} + +StepSequencer.prototype = { + sampleRate: 44100, + stepLength: 200, + steps: null, + attack: 0, + phase: 0, + /* The current value of the step sequencer */ + value: 0, + +/** + * Moves the step sequencer one sample further in sample time. + * + * @return {Number} The current value of the step sequencer. +*/ + generate: function () { + var self = this, + stepLength = self.sampleRate / 1000 * self.stepLength, + steps = self.steps, + sequenceLength = stepLength * steps.length, + step, overStep, prevStep, stepDiff, + val; + self.phase = (self.phase + 1) % sequenceLength; + step = self.phase / sequenceLength * steps.length; + overStep = step % 1; + step = ~~(step); + prevStep = (step || steps.length) - 1; + stepDiff = steps[step] - steps[prevStep]; + val = steps[step]; + if (overStep < self.attack) { + val -= stepDiff - stepDiff / self.attack * overStep; + } + self.value = val; + return val; + }, +/** + * Returns the current value of the step sequencer. + * + * @return {Number} The current value of the step sequencer. +*/ + getMix: function () { + return this.value; + }, +/** + * Triggers the gate for the step sequencer, resetting its phase to zero. + * + * @method StepSequencer +*/ + triggerGate: function () { + this.phase = 0; + } +}; + +/** + * UIControl is a tool for creating smooth, latency-balanced UI controls to interact with audio. + * + * @control + * + * @arg =!sampleRate + * @arg =!value + * + * @param type:UInt units:Hz default:44100 sampleRate Sample Rate the apparatus operates on. + * @param type:Number default:1 value The value of the UI control. +*/ +function UIControl (sampleRate, value) { + this.sampleRate = isNaN(sampleRate) ? this.sampleRate : sampleRate; + this.schedule = []; + this.reset(value); +} + +UIControl.prototype = { + sampleRate: 44100, + value: 1, + /* The internal schedule array of the UI control */ + schedule: null, + /* The internal clock of the UI control, indicating the previous time of a buffer callback */ + clock: 0, +/** + * Returns the current value of the UI control + * + * @return {Number} The current value of the UI control +*/ + getMix: function () { + return this.value; + }, + /** Moves the UI control one sample forward in the sample time */ + generate: function () { + var i; + for (i=0; i 1){ + this.coefs = { b0:b0, b1:b1, b2:b2, a1:a1, a2:a2 }; + } + } +}; + +/** + * Creates a Biquad Low-Pass Filter Effect + * + * @name LowPass + * @subeffect BiquadFilter BiquadLowPassFilter + * + * @arg =sampleRate + * @arg =cutoff + * @arg =Q + * + * @param type:UInt units:Hz sampleRate Sample Rate the apparatus operates on. + * @param type:Float units:Hz cutoff Low-pass cutoff frequency. + * @param type:Float min:0.0 max:1.0 Q Filter Q-factor (Q<0.5 filter underdamped, Q>0.5 filter overdamped) +*/ +BiquadFilter.LowPass = function (sampleRate, cutoff, Q) { + var w0 = 2* Math.PI*cutoff/sampleRate, + cosw0 = Math.cos(w0), + sinw0 = Math.sin(w0), + alpha = sinw0/(2*Q), + b0 = (1 - cosw0)/2, + b1 = 1 - cosw0, + b2 = b0, + a0 = 1 + alpha, + a1 = -2*cosw0, + a2 = 1 - alpha; + this.reset(sampleRate, b0/a0, b1/a0, b2/a0, a1/a0, a2/a0); +}; + +/** + * Creates a Biquad High-Pass Filter Effect + * + * @name HighPass + * @subeffect BiquadFilter BiquadHighPassFilter + * + * @arg =sampleRate + * @arg =cutoff + * @arg =Q + * + * @param type:UInt units:Hz sampleRate Sample Rate the apparatus operates on. + * @param type:Float units:Hz cutoff High-pass cutoff frequency. + * @param type:Float min:0.0 max:1.0 Q Filter Q-factor (Q<0.5 filter underdamped, Q>0.5 filter overdamped) +*/ +BiquadFilter.HighPass = function (sampleRate, cutoff, Q) { + var w0 = 2* Math.PI*cutoff/sampleRate, + cosw0 = Math.cos(w0), + sinw0 = Math.sin(w0), + alpha = sinw0/(2*Q), + b0 = (1 + cosw0)/2, + b1 = -(1 + cosw0), + b2 = b0, + a0 = 1 + alpha, + a1 = -2*cosw0, + a2 = 1 - alpha; + this.reset(sampleRate, b0/a0, b1/a0, b2/a0, a1/a0, a2/a0); +}; + +/** + * Creates a Biquad All-Pass Filter Effect + * + * @name AllPass + * @subeffect BiquadFilter BiquadAllPassFilter + * + * @arg =sampleRate + * @arg =f0 + * @arg =Q + * + * @param type:UInt units:Hz sampleRate Sample Rate the apparatus operates on. + * @param type:Float units:Hz min:0.0 f0 Significant frequency: filter will cause a phase shift of 180deg at f0. + * @param type:Float min:0.0 max:1.0 Q Filter Q-factor (Q<0.5 filter underdamped, Q>0.5 filter overdamped) +*/ +BiquadFilter.AllPass = function (sampleRate, f0, Q) { + var w0 = 2* Math.PI*f0/sampleRate, + cosw0 = Math.cos(w0), + sinw0 = Math.sin(w0), + alpha = sinw0/(2*Q), + b0 = 1 - alpha, + b1 = -2*cosw0, + b2 = 1 + alpha, + a0 = b2, + a1 = b1, + a2 = b0; + this.reset(sampleRate, b0/a0, b1/a0, b2/a0, a1/a0, a2/a0); +}; + +/** + * Creates a Biquad Band-Pass Filter Effect + * + * @name BandPass + * @subeffect BiquadFilter BiquadBandPassFilter + * + * @arg =sampleRate + * @arg =centerFreq + * @arg =bandwidthInOctaves + * + * @param type:UInt units:Hz sampleRate Sample Rate the apparatus operates on. + * @param type:Float units:Hz min:0.0 centerFreq Center frequency of filter: 0dB gain at center peak + * @param type:Float units:octaves min:0 bandwidthInOctaves Bandwidth of the filter (between -3dB points). +*/ +BiquadFilter.BandPass = function (sampleRate, centerFreq, bandwidthInOctaves) { + var w0 = 2* Math.PI*centerFreq/sampleRate, + cosw0 = Math.cos(w0), + sinw0 = Math.sin(w0), + toSinh = Math.log(2)/2 * bandwidthInOctaves * w0/sinw0, + alpha = sinw0*(Math.exp(toSinh) - Math.exp(-toSinh))/2, + b0 = alpha, + b1 = 0, + b2 = -alpha, + a0 = 1 + alpha, + a1 = -2 * cosw0, + a2 = 1 - alpha; + this.reset(sampleRate, b0/a0, b1/a0, b2/a0, a1/a0, a2/a0); +}; + +(function (classes, i) { + +for (i=0; i= buffer.length){ + bufferPos = 0; + } + buffer[bufferPos] = s; + self.osc.generate(); + + var delay = self.delayTime + self.osc.getMix() * self.depth; + delay *= self.sampleRate / 1000; + delay = bufferPos - Math.floor(delay); + while(delay < 0){ + delay += buffer.length; + } + + sample = buffer[delay]; + return sample; + }; + self.getMix = function () { + return sample; + }; + + calcCoeff(); +} + +/** + * Creates a Comb Filter effect. + * Defaults to Freeverb defaults. + * + * @effect + * + * @arg =!sampleRate + * @arg =!delaySize + * @arg =!feedback + * @arg =!damping + * + * @param type:UInt units:Hz default:44100 sampleRate Sample Rate the apparatus operates on. + * @param type:UInt units:samples default:1200 delaySize Size of the delay line buffer. + * @param type:Float min:0.0 max:0.0 default:0.84 feedback Amount of feedback for the CombFilter. + * @param type:Float min:0.0 max:0.0 default:0.2 damping Amount of damping for the CombFilter. +*/ +function CombFilter (sampleRate, delaySize, feedback, damping) { + var self = this; + self.sampleRate = sampleRate; + self.buffer = new Float32Array(isNaN(delaySize) ? 1200 : delaySize); + self.bufferSize = self.buffer.length; + self.feedback = isNaN(feedback) ? self.feedback : feedback; + self.damping = isNaN(damping) ? self.damping : damping; + self.invDamping = 1 - self.damping; +} + +CombFilter.prototype = { + sample: 0.0, + index: 0, + store: 0, + + feedback: 0.84, + damping: 0.2, + + pushSample: function (s) { + var self = this; + self.sample = self.buffer[self.index]; + self.store = self.sample * self.invDamping + self.store * self.damping; + self.buffer[self.index++] = s + self.store * self.feedback; + if (self.index >= self.bufferSize) { + self.index = 0; + } + return self.sample; + }, + getMix: function () { + return this.sample; + }, + reset: function () { + this.index = this.store = 0; + this.samples = 0.0; + this.buffer = new Float32Array(this.bufferSize); + }, + setParam: function (param, value) { + switch (param) { + case 'damping': + this.damping = value; + this.invDamping = 1 - value; + break; + default: + this[param] = value; + break; + } + } +}; + +/** + * Creates a Compressor Effect + * + * @effect + * + * @arg =!sampleRate + * @arg =!scaleBy + * @arg =!gain + * + * @param type:UInt units:Hz default:44100 sampleRate Sample Rate the apparatus operates on. + * @param type:UInt min:1 scaleBy Signal scaling factor. If mixing n unscaled waveforms, use scaleBy=n. + * @param type:Float min:0.0 max:2.0 default:0.5 gain Gain factor. +*/ +function Compressor (sampleRate, scaleBy, gain) { + var self = this, + sample = 0.0; + self.sampleRate = sampleRate; + self.scale = scaleBy || 1; + self.gain = isNaN(gain) ? 0.5 : gain; + self.pushSample = function (s) { + s /= self.scale; + sample = (1 + self.gain) * s - self.gain * s * s * s; + return sample; + }; + self.getMix = function () { + return sample; + }; +} + +/** + * Creates a Convolution effect. + * + * @effect + * + * @arg =!sampleRate + * @arg =!kernels + * + * @param type:UInt units:Hz default:44100 sampleRate Sample Rate the apparatus operates on. + * @param type:AudioBuffer default:[0] kernels The kernels for the convolution effect. +*/ +function Convolution (sampleRate, kernels) { + this.sampleRate = isNaN(sampleRate) ? this.sampleRate : sampleRate; + this.setParam('kernels', kernels || new Float32Array(1)); +} + +Convolution.prototype = { + sampleRate: 44100, + sample: 0, + pos: 0, + + kernels: null, + buffer: null, + +/* +This is a very suboptimal implementation... +*/ + + pushSample: function (s) { + var p, i, l; + + this.sample = 0; + + this.buffer[this.pos] = s; + + for (i=0, l=this.buffer.length, p=this.pos+l; i self.time * self._rstf){ + self.bufferPos = 0; + } + self.sample = buffer[self.bufferPos]; + buffer[self.bufferPos] *= self.feedback; + return self.sample; + }, +/** + * Returns the current output of the Delay. + * + * @return {Float32} Current output of the Delay. +*/ + getMix: function () { + return this.sample; + }, +/** + * Changes the time value of the Delay and resamples the delay line accordingly. + * + * Requires sink.js + * + * @method Delay + * + * @arg {Uint} time The new time value for the Delay. + * @return {AudioBuffer} The new delay line audio buffer. +*/ + resample: function (time) { + var self = this, + ratio = self.time / time; + self.buffer = audioLib.Sink.resample(self.buffer, time); + self.time = time; + self.bufferPos = Math.round(ratio * self.bufferPos); + return self.buffer; + }, +/** + * Resets the delay line, to recover from sample rate changes or such. + * + * @arg {Number} sampleRate The new sample rate. (Optional) + * @arg {Boolean} resample Determines whether to resample and apply the old buffer. (Requires Sink) + * @return {AudioBuffer} The new delay line audio buffer. +*/ + reset: function (sampleRate, resample) { + var self = this, + buf = self.buffer, + i, ratio; + sampleRate = isNaN(sampleRate) ? self.sampleRate : sampleRate; + ratio = self.sampleRate / sampleRate; + self.buffer = new Float32Array(sampleRate * Delay.MAX_DELAY); + self.bufferPos = Math.round(ratio * self.bufferPos); + self._rstf = 1 / 1000 * sampleRate; + if (resample) { + buf = audioLib.Sink.resample(buf, ratio); + for (i=0; i 0.4) { + smpl = 0.4; + } else if (smpl < -0.4) { + smpl = -0.4; + } + lpf1.pushSample(smpl); + hpf2.pushSample(lpf1.getMix(0)); + smpl = hpf2.getMix(1) * this.master; + return smpl; + }; + this.getMix = function () { + return smpl; + }; +} + +/** + * Creates a Reverb Effect, based on the Freeverb algorithm + * + * @effect Reverb + * + * @arg =!sampleRate + * @arg =!channelCount + * @arg =!wet + * @arg =!dry + * @arg =!roomSize + * @arg =!damping + * @arg {Object} !tuningOverride Freeverb tuning overwrite object. + * + * @param type:UInt units:Hz default:44100 sampleRate Sample Rate the apparatus operates on. + * @param type:UInt min:1 default:2 channelCount The channel count of the Reverb. + * @param type:Float default:0.5 wet The gain of the reverb signal output. + * @param type:Float default:0.55 dry The gain of the original signal output. + * @param type:Float min:0.0 max:1.0 default:0.5 roomSize The size of the simulated reverb area. + * @param type:Float min:0.0 max:1.0 default:0.2223 damping Reverberation damping parameter. +*/ +function Freeverb (sampleRate, channelCount, wet, dry, roomSize, damping, tuningOverride) { + var self = this; + self.sampleRate = sampleRate; + self.channelCount = isNaN(channelCount) ? self.channelCount : channelCount; + self.wet = isNaN(wet) ? self.wet: wet; + self.dry = isNaN(dry) ? self.dry: dry; + self.roomSize = isNaN(roomSize) ? self.roomSize: roomSize; + self.damping = isNaN(damping) ? self.damping: damping; + self.tuning = new Freeverb.Tuning(tuningOverride || self.tuning); + + self.sample = (function () { + var sample = [], + c; + for (c=0; c= self.bufferSize) { + self.index = 0; + } + return self.sample; + }, + getMix: function () { + return this.sample; + }, + reset: function () { + this.index = 0; + this.sample = 0.0; + this.buffer = new Float32Array(this.bufferSize); + } +}; + +/** + * Creates a Gain Controller effect. + * + * @effect + * + * @arg =!sampleRate + * @arg =!gain + * + * @param type:UInt units:Hz default:44100 sampleRate Sample Rate the apparatus operates on. + * @param type:UInt default:1 gain The gain for the gain controller. +*/ +function GainController (sampleRate, gain) { + this.sampleRate = isNaN(sampleRate) ? this.sampleRate : sampleRate; + this.gain = isNaN(gain) ? this.gain : gain; +} + +GainController.prototype = { + sampleRate: 44100, + gain: 1, + /* The current output sample of the gain controller */ + sample: 0, +/** + * Processes provided sample, moves the gain controller one sample forward in the sample time. + * + * @arg {Number} s The input sample for the gain controller. + * @return {Number} The current output sample of the controller. +*/ + pushSample: function (s) { + this.sample = s * this.gain; + return this.sample; + }, +/** + * Returns the current output sample of the controller. + * + * @return {Number} The current output sample of the controller. +*/ + getMix: function () { + return this.sample; + } +}; + +/** + * Creates a IIRFilter effect. + * Adapted from Corban Brook's dsp.js + * + * @effect + * + * @arg =!sampleRate + * @arg =!cutoff + * @arg =!resonance + * @arg =!type + * + * @param type:UInt units:Hz default:44100 sampleRate Sample Rate the apparatus operates on. + * @param type:Float units:Hz min:40.0 default:20000 cutoff The cutoff frequency of the IIRFilter. + * @param type:Float min:0.0 max:1.0 default:0.1 resonance The resonance of the IIRFilter. + * @param type:UInt default:0 type The type of the filter (LowPass, HighPass, BandPass, Notch). +*/ +function IIRFilter (sampleRate, cutoff, resonance, type) { + var self = this, + f = [0.0, 0.0, 0.0, 0.0], + freq, damp, + prevCut, prevReso, + + sin = Math.sin, + min = Math.min, + pow = Math.pow; + + self.cutoff = isNaN(cutoff) ? 20000 : cutoff; // > 40 + self.resonance = !resonance ? 0.1 : resonance; // 0.0 - 1.0 + self.samplerate = isNaN(sampleRate) ? 44100 : sampleRate; + self.type = type || 0; + + function calcCoeff () { + freq = 2 * sin(Math.PI * min(0.25, self.cutoff / (self.samplerate * 2))); + damp = min(2 * (1 - pow(self.resonance, 0.25)), min(2, 2 / freq - freq * 0.5)); + } + + self.pushSample = function (sample) { + if (prevCut !== self.cutoff || prevReso !== self.resonance){ + calcCoeff(); + prevCut = self.cutoff; + prevReso = self.resonance; + } + + f[3] = sample - damp * f[2]; + f[0] = f[0] + freq * f[2]; + f[1] = f[3] - f[0]; + f[2] = freq * f[1] + f[2]; + + f[3] = sample - damp * f[2]; + f[0] = f[0] + freq * f[2]; + f[1] = f[3] - f[0]; + f[2] = freq * f[1] + f[2]; + + return f[self.type]; + }; + + self.getMix = function (type) { + return f[type || self.type]; + }; +} + +/** + * Creates a dynamic amplitude limiter. + * + * Requires [[Amplitude]]. + * + * @effect + * + * @arg =!sampleRate + * @arg =!threshold + * @arg =!attack + * @arg =!release + * + * @param type:UInt units:Hz default:44100 sampleRate Sample Rate the apparatus operates on. + * @param type:Float min:0.0 default:0.95 threshold The amplitude threshold after which to start limiting. + * @param type:Float min:0.0 default:0.01 attack The speed on which the amplitude metering reacts. + * @param type:Float min:0.0 default:0.01 release The speed on which the amplitude metering cools down. +*/ +function Limiter(sampleRate, threshold, attack, release){ + this.sampleRate = isNaN(sampleRate) ? this.sampleRate : sampleRate; + this.threshold = isNaN(threshold) ? this.threshold : threshold; + this.attack = isNaN(attack) ? this.attack : attack; + this.release = isNaN(release) ? this.release : release; + this._amplitude = new audioLib.Amplitude(this.sampleRate, this.attack, this.release); +} + +Limiter.prototype = { + sampleRate: 44100, + threshold: 0.95, + attack: 0.01, + release: 0.01, + /* The Amplitude meter on which the limiting is based. */ + __amplitude: null, + /* The current output of the effect. */ + sample: 0, +/** + * Processes a sample, moving the effect one sample further in sample-time. + * + * @arg {Float32} sample The sample to process. + * @arg {Uint} channel The channel on which the sample is. (Only if multi-channel) + * @return {Float32} The current output of the effect. (Only if single-channel) +*/ + pushSample: function(s){ + var d = this._amplitude.pushSample(s) - this.threshold; + this.sample = d > 0 ? s / (1 + d) : s; + return this.sample; + }, +/** + * Returns the current output of the effect. + * + * @arg {Uint} channel The channel for which to get the sample. + * @return {Float32} The current output of the effect. +*/ + getMix: function(){ + return this.sample; + }, +/** + * Sets a parameter of the effect, making necessary relative calculations. + * + * @arg {String} param The parameter name. + * @arg {Object} value The new value of the parameter. +*/ + setParam: function(param, value){ + switch(param){ + case 'attack': + case 'release': + this._amplitude[param] = value; + this[param] = value; + break; + default: + this[param] = value; + } + } +}; + +/** + * Creates a LP12Filter effect. + * Adapted from Corban Brook's dsp.js + * + * @effect + * + * @arg =!sampleRate + * @arg =!cutoff + * @arg =!resonance + * + * @param type:UInt units:Hz default:44100 sampleRate Sample Rate the apparatus operates on. + * @param type:Float units:Hz min:40 default:20000 cutoff The cutoff frequency of the filter. + * @param type:Float min:1.0 max:20.0 default:1 resonance The resonance of the filter. +*/ +function LP12Filter(samplerate, cutoff, resonance){ + var self = this, + vibraSpeed = 0, + vibraPos = 0, + pi2 = Math.PI * 2, + w, q, r, c, + prevCut, prevReso; + + self.cutoff = !cutoff ? 20000 : cutoff; // > 40 + self.resonance = !resonance ? 1 : resonance; // 1 - 20 + self.samplerate = samplerate; + + function calcCoeff(){ + w = pi2 * self.cutoff / self.samplerate; + q = 1.0 - w / (2 * (self.resonance + 0.5 / (1.0 + w)) + w - 2); + r = q * q; + c = r + 1 - 2 * Math.cos(w) * q; + } + + self.pushSample = function(sample){ + if (prevCut !== self.cutoff || prevReso !== self.resonance){ + calcCoeff(); + prevCut = self.cutoff; + prevReso = self.resonance; + } + vibraSpeed += (sample - vibraPos) * c; + vibraPos += vibraSpeed; + vibraSpeed *= r; + return vibraPos; + }; + + self.getMix = function(){ + return vibraPos; + }; + + calcCoeff(); +} + +/** + * @generator + * + * @arg =!sampleRate + * @arg =!color + * + * @param type:UInt units:Hz default:44100 sampleRate Sample Rate the apparatus operates on. + * @param type:String default:white color The color of the noise. + * @param type:Float default:0 value The current value of the noise. +*/ +function Noise () { + this.reset.apply(this, arguments); +} + +Noise.prototype = { + /* The sample rate of the Noise. */ + sampleRate: 44100, + /* The color of the Noise. */ + color: 'white', + b0: 0, + b1: 0, + b2: 0, + b3: 0, + b4: 0, + b5: 0, + c1: null, + c2: null, + c3: null, + c4: null, + q: 15, + q0: null, + q1: null, + /* Brown seed. */ + brownQ: 0, + /* Current value of the Noise. */ + value: 0, + + reset: function (sampleRate, color) { + this.sampleRate = isNaN(sampleRate) ? this.sampleRate : sampleRate; + this.color = typeof color === 'string' ? color : this.color; + this.c1 = (1 << this.q) - 1; + this.c2 = (~~(this.c1 /3)) + 1; + this.c3 = 1 / this.c1; + this.c1 = this.c2 * 6; + this.c4 = 3 * (this.c2 - 1); + this.q0 = Math.exp(-200 * Math.PI / this.sampleRate); + this.q1 = 1 - this.q0; + }, + + generate: function () { + this.value = this[this.color](); + }, + + getMix: function () { + return this.value; + }, +/** + * Returns the white noise output of the noise generator. + * + * @method Noise + * + * @return {Float} White noise. +*/ + white: function () { + var r = Math.random(); + return (r * this.c1 - this.c4) * this.c3; + }, +/** + * Returns the pink noise output of the noise generator. + * + * @method Noise + * + * @return {Float} Pink noise. +*/ + pink: function () { + var w = this.white(); + this.b0 = 0.997 * this.b0 + 0.029591 * w; + this.b1 = 0.985 * this.b1 + 0.032534 * w; + this.b2 = 0.950 * this.b2 + 0.048056 * w; + this.b3 = 0.850 * this.b3 + 0.090579 * w; + this.b4 = 0.620 * this.b4 + 0.108990 * w; + this.b5 = 0.250 * this.b5 + 0.255784 * w; + return 0.55 * (this.b0 + this.b1 + this.b2 + this.b3 + this.b4 + this.b5); + }, +/** + * Returns the brown noise output of the noise generator. + * + * @method Noise + * + * @return {Float} Brown noise. +*/ + brown: function () { + var w = this.white(); + this.brownQ = (this.q1 * w + this.q0 * this.brownQ); + return 6.2 * this.brownQ; + } +}; + +/** + * Creates a new Oscillator. + * + * @generator + * + * @arg =!sampleRate + * @arg =!frequency + * + * @param type:UInt units:Hz default:44100 sampleRate Sample Rate the apparatus operates on. + * @param type:Float units:Hz min:0 default:440 frequency The frequency of the Oscillator. + * @param type:Float min:0.0 max:1.0 default:0.0 phaseOffset The phase offset of the Oscillator. + * @param type:Float min:0.0 max:1.0 default:0.5 pulseWidth The pulse width of the Oscillator. + * @param type:String|UInt default:sine waveShape The wave shape of the Oscillator. + * @param type:Float default:0 fm The frequency modulation of the Oscillator. +*/ + +function Oscillator (sampleRate, freq) { + var self = this; + self.frequency = isNaN(freq) ? 440 : freq; + self.waveTable = new Float32Array(1); + self.sampleRate = sampleRate; + self.waveShapes = self.waveShapes.slice(0); +} + +(function (FullPI, waveshapeNames, proto, i) { + +proto = Oscillator.prototype = { + sampleRate: 44100, + frequency: 440, + phaseOffset: 0, + pulseWidth: 0.5, + fm: 0, + waveShape: 'sine', + /* Phase of the Oscillator */ + phase: 0, +/* The relative of phase of the Oscillator (pulsewidth, phase offset, etc applied). */ + _p: 0, + +/** + * Moves the Oscillator's phase forward by one sample. +*/ + generate: function () { + var self = this, + f = +self.frequency, + pw = self.pulseWidth, + p = self.phase; + f += f * self.fm; + self.phase = (p + f / self.sampleRate / 2) % 1; + p = (self.phase + self.phaseOffset) % 1; + self._p = p < pw ? p / pw : (p-pw) / (1-pw); + }, +/** + * Returns the output signal sample of the Oscillator. + * + * @return {Float} The output signal sample. +*/ + getMix: function () { + return this[this.waveShape](); + }, +/** + * Returns the relative phase of the Oscillator (pulsewidth, phaseoffset, etc applied). + * + * @return {Float} The relative phase. +*/ + getPhase: function () { + return this._p; + }, +/** + * Resets the Oscillator phase (AND RELATIVE PHASE) to a specified value. + * + * @arg {Float} phase The phase to reset the values to. (Optional, defaults to 0). +*/ + reset: function (p) { + this.phase = this._p = isNaN(p) ? 0 : p; + }, +/** + * Specifies a wavetable for the Oscillator. + * + * @method Oscillator + * + * @arg {Array} wavetable The wavetable to be assigned to the Oscillator. + * @return {Boolean} Succesfulness of the operation. +*/ + setWavetable: function (wt) { + this.waveTable = wt; + return true; + }, +/** + * Returns sine wave output of the Oscillator. + * + * Phase for the zero crossings of the function: 0.0, 0.5 + * + * @method Oscillator + * + * @return {Float} Sample. +*/ + sine: function () { + return Math.sin(this._p * FullPI); + }, +/** + * Returns triangle wave output of the Oscillator, phase zero representing the top of the triangle. + * + * Phase for the zero crossings of the function: 0.25, 0.75 + * + * @method Oscillator + * + * @return {Float} Sample. +*/ + triangle: function () { + return this._p < 0.5 ? 4 * this._p - 1 : 3 - 4 * this._p; + }, +/** + * Returns square wave output of the Oscillator, phase zero being the first position of the positive side. + * + * Phase for the zero crossings of the function: 0.0, 0.5 + * + * @method Oscillator + * + * @return {Float} Sample. +*/ + square: function () { + return this._p < 0.5 ? -1 : 1; + }, +/** + * Returns sawtooth wave output of the Oscillator, phase zero representing the negative peak. + * + * Phase for the zero crossings of the function: 0.5 + * + * @method Oscillator + * + * @return {Float} Sample. +*/ + sawtooth: function () { + return 1 - this._p * 2; + }, +/** + * Returns invert sawtooth wave output of the Oscillator, phase zero representing the positive peak. + * + * Phase for the zero crossings of the function: 0.5 + * + * @method Oscillator + * + * @return {Float} Sample. +*/ + invSawtooth: function () { + return this._p * 2 - 1; + }, +/** + * Returns pulse wave output of the Oscillator, phase zero representing slope starting point. + * + * Phase for the zero crossings of the function: 0.125, 0.325 + * + * @method Oscillator + * + * @return {Float} Sample. +*/ + pulse: function () { + return this._p < 0.5 ? + this._p < 0.25 ? + this._p * 8 - 1 : + 1 - (this._p - 0.25) * 8 : + -1; + }, +/** + * Returns wavetable output of the Oscillator. + * + * Requires sink.js + * + * @method Oscillator + * + * @return {Float} Sample. +*/ + wavetable: function () { + return audioLib.Sink.interpolate(this.waveTable, this._p * this.waveTable.length); + }, + + waveShapes: [] +}; + +for (i=0; i self.maxVoices) { + end = self.voices.shift(); + if (end.onend) end.onend(); + } + return note; + }, +/** + * Moves all the voices one sample position further and disbands the voices that have ended. +*/ + generate: function () { + var voices = this.voices, + i, voice; + for (i=0; i voice.l && voices.splice(i--, 1) && voice.onend) { + voice.onend(); + } + } + }, +/** + * Returns the mix of the voices, by a specific channel. + * + * @arg {Int} channel The number of the channel to be returned. (Optional) + * @return {Float32} The current output of the Sampler's channel number channel. +*/ + getMix: function (ch) { + var voices = this.voices, + smpl = 0, + i; + ch = ch || 0; + if (this.samples[ch]) { + for (i=0; i> 8, size - 1) : ''; + } + + function convertToBinaryBE (num, size) { // I don't think this is right + return size ? convertToBinaryBE(num >> 8, size - 1) + fromCharCode(255 - num & 255) : ''; + } + + function convertToBinary (num, size, bigEndian) { + return bigEndian ? convertToBinaryBE(num, size) : convertToBinaryLE(num, size); + } + + function convertFromBinary (str, bigEndian) { + var l = str.length, + last = l - 1, + n = 0, + pow = Math.pow, + i; + if (bigEndian) { + for (i=0; i intMask ? (num - bitMask) * invSemiMask : num * invIntMask; + } : function (str, bigEndian) { + return convertFromBinary(str, bigEndian) * invIntMask; + } + : + signed ? function (str, bigEndian) { + var num = convertFromBinary(str, bigEndian); + return num > intMask ? num - bitMask : num; + } : function (str, bigEndian) { + return convertFromBinary(str, bigEndian); + }; + } + + Binary.convertToBinary = convertToBinary; + Binary.convertFromBinary = convertFromBinary; + // these are deprecated because JS doesn't support 64 bit uint, so the conversion can't be performed. +/* + Binary.fromQ64 = Binary(64, y, y, y); + Binary.toQ64 = Binary(64, y, y, n); +*/ + Binary.fromQ32 = Binary(32, y, y, y); + Binary.toQ32 = Binary(32, y, y, n); + Binary.fromQ24 = Binary(24, y, y, y); + Binary.toQ24 = Binary(24, y, y, n); + Binary.fromQ16 = Binary(16, y, y, y); + Binary.toQ16 = Binary(16, y, y, n); + Binary.fromQ8 = Binary( 8, y, y, y); + Binary.toQ8 = Binary( 8, y, y, n); + Binary.fromInt32 = Binary(32, y, n, y); + Binary.toInt32 = Binary(32, y, n, n); + Binary.fromInt16 = Binary(16, y, n, y); + Binary.toInt16 = Binary(16, y, n, n); + Binary.fromInt8 = Binary( 8, y, n, y); + Binary.toInt8 = Binary( 8, y, n, n); + Binary.fromUint32 = Binary(32, n, n, y); + Binary.toUint32 = Binary(32, n, n, n); + Binary.fromUint16 = Binary(16, n, n, y); + Binary.toUint16 = Binary(16, n, n, n); + Binary.fromUint8 = Binary( 8, n, n, y); + Binary.toUint8 = Binary( 8, n, n, n); + + global.Binary = Binary; +}(this, Math)); +(function (global, Binary) { + +function Stream (data) { + this.data = data; +} + +var proto = Stream.prototype = { + read: function (length) { + var self = this, + data = self.data.substr(0, length); + self.skip(length); + return data; + }, + skip: function (length) { + var self = this, + data = self.data = self.data.substr(length); + self.pointer += length; + return data.length; + }, + readBuffer: function (buffer, bitCount, type) { + var self = this, + converter = 'read' + type + bitCount, + byteCount = bitCount / 8, + l = buffer.length, + i = 0; + while (self.data && i < l) { + buffer[i++] = self[converter](); + } + return i; + } + }, + i, match; + +function newType (type, bitCount, fn) { + var l = bitCount / 8; + proto['read' + type + bitCount] = function (bigEndian) { + return fn(this.read(l), bigEndian); + }; +} + +for (i in Binary) { + match = /to([a-z]+)([0-9]+)/i.exec(i); + if (match) newType(match[1], match[2], Binary[i]); +} + +global.Stream = Stream; +Stream.newType = newType; + +}(this, this.Binary)); +this.PCMData = (function (Binary, Stream) { + +var ByteArray = typeof Uint8Array === 'undefined' ? Array : Uint8Array; + +function PCMData (data) { + return (typeof data === 'string' ? PCMData.decode : PCMData.encode)(data); +} + +PCMData.decodeFrame = function (frame, bitCount, result) { + if (bitCount === 8) { + var buffer = new ByteArray(result.length); + (new Stream(frame)).readBuffer(buffer, 8, 'Uint'); + for (bitCount=0; bitCount 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); +(function (Sink, sinks) { + +sinks = Sink.sinks; + +function newAudio (src) { + var audio = document.createElement('audio'); + if (src) { + audio.src = src; + } + return audio; +} + +/* TODO: Implement a 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)); +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 (sinks, fixChrome82795) { + +var AudioContext = typeof window === 'undefined' ? null : window.webkitAudioContext || window.AudioContext; + +/** + * A sink class for the Web Audio API +*/ + +sinks('webaudio', function (readFn, channelCount, bufferSize, sampleRate) { + var self = this, + context = sinks.webaudio.getContext(), + node = null, + soundData = null, + zeroBuffer = null; + self.start.apply(self, arguments); + node = context.createJavaScriptNode(self.bufferSize, 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= 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= 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)); +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= 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 this.sample ? this.attack : this.release) * (this.sample - s) + s); + return this.sample; + }, +/** + * Returns the current output of the effect. + * + * @arg {UInt} channel The channel for which to get the sample. + * @return {Float} The current output of the effect. +*/ + getMix: function () { + return this.sample; + } +}; + +/* Copyright (c) 2012, Jens Nockert , Jussi Kalliokoski + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/*jshint */ +/*global Float64Array */ + +var FFT = function () { +"use strict"; + +function butterfly2(output, outputOffset, outputStride, fStride, state, m) { + var t = state.twiddle; + + for (var i = 0; i < m; i++) { + var s0_r = output[2 * ((outputOffset) + (outputStride) * (i))], s0_i = output[2 * ((outputOffset) + (outputStride) * (i)) + 1]; + var s1_r = output[2 * ((outputOffset) + (outputStride) * (i + m))], s1_i = output[2 * ((outputOffset) + (outputStride) * (i + m)) + 1]; + + var t1_r = t[2 * ((0) + (fStride) * (i))], t1_i = t[2 * ((0) + (fStride) * (i)) + 1]; + + var v1_r = s1_r * t1_r - s1_i * t1_i, v1_i = s1_r * t1_i + s1_i * t1_r; + + var r0_r = s0_r + v1_r, r0_i = s0_i + v1_i; + var r1_r = s0_r - v1_r, r1_i = s0_i - v1_i; + + output[2 * ((outputOffset) + (outputStride) * (i))] = r0_r; + output[2 * ((outputOffset) + (outputStride) * (i)) + 1] = r0_i; + output[2 * ((outputOffset) + (outputStride) * (i + m))] = r1_r; + output[2 * ((outputOffset) + (outputStride) * (i + m)) + 1] = r1_i; + } +} + +function butterfly3(output, outputOffset, outputStride, fStride, state, m) { + var t = state.twiddle; + var m1 = m, m2 = 2 * m; + var fStride1 = fStride, fStride2 = 2 * fStride; + + var e = t[2 * ((0) + (fStride) * (m)) + 1]; + + for (var i = 0; i < m; i++) { + var s0_r = output[2 * ((outputOffset) + (outputStride) * (i))], s0_i = output[2 * ((outputOffset) + (outputStride) * (i)) + 1]; + + var s1_r = output[2 * ((outputOffset) + (outputStride) * (i + m1))], s1_i = output[2 * ((outputOffset) + (outputStride) * (i + m1)) + 1]; + var t1_r = t[2 * ((0) + (fStride1) * (i))], t1_i = t[2 * ((0) + (fStride1) * (i)) + 1]; + var v1_r = s1_r * t1_r - s1_i * t1_i, v1_i = s1_r * t1_i + s1_i * t1_r; + + var s2_r = output[2 * ((outputOffset) + (outputStride) * (i + m2))], s2_i = output[2 * ((outputOffset) + (outputStride) * (i + m2)) + 1]; + var t2_r = t[2 * ((0) + (fStride2) * (i))], t2_i = t[2 * ((0) + (fStride2) * (i)) + 1]; + var v2_r = s2_r * t2_r - s2_i * t2_i, v2_i = s2_r * t2_i + s2_i * t2_r; + + var i0_r = v1_r + v2_r, i0_i = v1_i + v2_i; + + var r0_r = s0_r + i0_r, r0_i = s0_i + i0_i; + output[2 * ((outputOffset) + (outputStride) * (i))] = r0_r; + output[2 * ((outputOffset) + (outputStride) * (i)) + 1] = r0_i; + + var i1_r = s0_r - i0_r * 0.5; + var i1_i = s0_i - i0_i * 0.5; + + var i2_r = (v1_r - v2_r) * e; + var i2_i = (v1_i - v2_i) * e; + + var r1_r = i1_r - i2_i; + var r1_i = i1_i + i2_r; + output[2 * ((outputOffset) + (outputStride) * (i + m1))] = r1_r; + output[2 * ((outputOffset) + (outputStride) * (i + m1)) + 1] = r1_i; + + var r2_r = i1_r + i2_i; + var r2_i = i1_i - i2_r; + output[2 * ((outputOffset) + (outputStride) * (i + m2))] = r2_r; + output[2 * ((outputOffset) + (outputStride) * (i + m2)) + 1] = r2_i; + } +} + +function butterfly4(output, outputOffset, outputStride, fStride, state, m) { + var r1_r, r1_i, r3_r, r3_i; + var t = state.twiddle; + var m1 = m, m2 = 2 * m, m3 = 3 * m; + var fStride1 = fStride, fStride2 = 2 * fStride, fStride3 = 3 * fStride; + + for (var i = 0; i < m; i++) { + var s0_r = output[2 * ((outputOffset) + (outputStride) * (i))], s0_i = output[2 * ((outputOffset) + (outputStride) * (i)) + 1]; + + var s1_r = output[2 * ((outputOffset) + (outputStride) * (i + m1))], s1_i = output[2 * ((outputOffset) + (outputStride) * (i + m1)) + 1]; + var t1_r = t[2 * ((0) + (fStride1) * (i))], t1_i = t[2 * ((0) + (fStride1) * (i)) + 1]; + var v1_r = s1_r * t1_r - s1_i * t1_i, v1_i = s1_r * t1_i + s1_i * t1_r; + + var s2_r = output[2 * ((outputOffset) + (outputStride) * (i + m2))], s2_i = output[2 * ((outputOffset) + (outputStride) * (i + m2)) + 1]; + var t2_r = t[2 * ((0) + (fStride2) * (i))], t2_i = t[2 * ((0) + (fStride2) * (i)) + 1]; + var v2_r = s2_r * t2_r - s2_i * t2_i, v2_i = s2_r * t2_i + s2_i * t2_r; + + var s3_r = output[2 * ((outputOffset) + (outputStride) * (i + m3))], s3_i = output[2 * ((outputOffset) + (outputStride) * (i + m3)) + 1]; + var t3_r = t[2 * ((0) + (fStride3) * (i))], t3_i = t[2 * ((0) + (fStride3) * (i)) + 1]; + var v3_r = s3_r * t3_r - s3_i * t3_i, v3_i = s3_r * t3_i + s3_i * t3_r; + + var i0_r = s0_r + v2_r, i0_i = s0_i + v2_i; + var i1_r = s0_r - v2_r, i1_i = s0_i - v2_i; + var i2_r = v1_r + v3_r, i2_i = v1_i + v3_i; + var i3_r = v1_r - v3_r, i3_i = v1_i - v3_i; + + var r0_r = i0_r + i2_r, r0_i = i0_i + i2_i; + + if (state.inverse) { + r1_r = i1_r - i3_i; + r1_i = i1_i + i3_r; + } else { + r1_r = i1_r + i3_i; + r1_i = i1_i - i3_r; + } + + var r2_r = i0_r - i2_r, r2_i = i0_i - i2_i; + + if (state.inverse) { + r3_r = i1_r + i3_i; + r3_i = i1_i - i3_r; + } else { + r3_r = i1_r - i3_i; + r3_i = i1_i + i3_r; + } + + output[2 * ((outputOffset) + (outputStride) * (i))] = r0_r; + output[2 * ((outputOffset) + (outputStride) * (i)) + 1] = r0_i; + output[2 * ((outputOffset) + (outputStride) * (i + m1))] = r1_r; + output[2 * ((outputOffset) + (outputStride) * (i + m1)) + 1] = r1_i; + output[2 * ((outputOffset) + (outputStride) * (i + m2))] = r2_r; + output[2 * ((outputOffset) + (outputStride) * (i + m2)) + 1] = r2_i; + output[2 * ((outputOffset) + (outputStride) * (i + m3))] = r3_r; + output[2 * ((outputOffset) + (outputStride) * (i + m3)) + 1] = r3_i; + } +} + +function butterfly(output, outputOffset, outputStride, fStride, state, m, p) { + var q1, x0_r, x0_i, k; + var t = state.twiddle, n = state.n, scratch = new Float64Array(2 * p); + + for (var u = 0; u < m; u++) { + for (q1 = 0, k = u; q1 < p; q1++, k += m) { + x0_r = output[2 * ((outputOffset) + (outputStride) * (k))]; + x0_i = output[2 * ((outputOffset) + (outputStride) * (k)) + 1]; + scratch[2 * (q1)] = x0_r; + scratch[2 * (q1) + 1] = x0_i; + } + + for (q1 = 0, k = u; q1 < p; q1++, k += m) { + var tOffset = 0; + + x0_r = scratch[2 * (0)]; + x0_i = scratch[2 * (0) + 1]; + output[2 * ((outputOffset) + (outputStride) * (k))] = x0_r; + output[2 * ((outputOffset) + (outputStride) * (k)) + 1] = x0_i; + + for (var q = 1; q < p; q++) { + tOffset = (tOffset + fStride * k) % n; + + var s0_r = output[2 * ((outputOffset) + (outputStride) * (k))], s0_i = output[2 * ((outputOffset) + (outputStride) * (k)) + 1]; + + var s1_r = scratch[2 * (q)], s1_i = scratch[2 * (q) + 1]; + var t1_r = t[2 * (tOffset)], t1_i = t[2 * (tOffset) + 1]; + var v1_r = s1_r * t1_r - s1_i * t1_i, v1_i = s1_r * t1_i + s1_i * t1_r; + + var r0_r = s0_r + v1_r, r0_i = s0_i + v1_i; + output[2 * ((outputOffset) + (outputStride) * (k))] = r0_r; + output[2 * ((outputOffset) + (outputStride) * (k)) + 1] = r0_i; + } + } + } +} + +function work(output, outputOffset, outputStride, f, fOffset, fStride, inputStride, factors, state) { + var i, x0_r, x0_i; + var p = factors.shift(); + var m = factors.shift(); + + if (m == 1) { + for (i = 0; i < p * m; i++) { + x0_r = f[2 * ((fOffset) + (fStride * inputStride) * (i))]; + x0_i = f[2 * ((fOffset) + (fStride * inputStride) * (i)) + 1]; + output[2 * ((outputOffset) + (outputStride) * (i))] = x0_r; + output[2 * ((outputOffset) + (outputStride) * (i)) + 1] = x0_i; + } + } else { + for (i = 0; i < p; i++) { + work(output, outputOffset + outputStride * i * m, outputStride, f, fOffset + i * fStride * inputStride, fStride * p, inputStride, factors.slice(), state); + } + } + + switch (p) { + case 2: butterfly2(output, outputOffset, outputStride, fStride, state, m); break; + case 3: butterfly3(output, outputOffset, outputStride, fStride, state, m); break; + case 4: butterfly4(output, outputOffset, outputStride, fStride, state, m); break; + default: butterfly(output, outputOffset, outputStride, fStride, state, m, p); break; + } +} + +var complex = function (n, inverse) { + if (arguments.length < 2) { + throw new RangeError("You didn't pass enough arguments, passed `" + arguments.length + "'"); + } + + n = ~~n; + inverse = !!inverse; + + if (n < 1) { + throw new RangeError("n is outside range, should be positive integer, was `" + n + "'"); + } + + this.inputBuffer = new Float64Array(2 * n); + this.outputBuffer = new Float64Array(2 * n); + + var state = { + n: n, + inverse: inverse, + + factors: [], + twiddle: new Float64Array(2 * n), + scratch: new Float64Array(2 * n) + }; + + var t = state.twiddle, theta = 2 * Math.PI / n; + + var i, phase; + + for (i = 0; i < n; i++) { + if (inverse) { + phase = theta * i; + } else { + phase = -theta * i; + } + + t[2 * (i)] = Math.cos(phase); + t[2 * (i) + 1] = Math.sin(phase); + } + + var p = 4, v = Math.floor(Math.sqrt(n)); + + while (n > 1) { + while (n % p) { + switch (p) { + case 4: p = 2; break; + case 2: p = 3; break; + default: p += 2; break; + } + + if (p > v) { + p = n; + } + } + + n /= p; + + state.factors.push(p); + state.factors.push(n); + } + + this.state = state; + + this.resetFT(); +}; + +complex.prototype.process = function (output, input, t) { + this.process_explicit(output || this.outputBuffer, 0, 1, input || this.inputBuffer, 0, 1, t); +}; + +complex.prototype.process_explicit = function(output, outputOffset, outputStride, input, inputOffset, inputStride, t) { + var i, x0_r, x0_i; + outputStride = ~~outputStride; + inputStride = ~~inputStride; + + t = t || this.inputType; + var type = t === 'real' ? t : 'complex'; + + if (outputStride < 1) { + throw new RangeError("outputStride is outside range, should be positive integer, was `" + outputStride + "'"); + } + + if (inputStride < 1) { + throw new RangeError("inputStride is outside range, should be positive integer, was `" + inputStride + "'"); + } + + if (type == 'real') { + for (i = 0; i < this.state.n; i++) { + x0_r = input[inputOffset + inputStride * i]; + x0_i = 0.0; + + this.state.scratch[2 * (i)] = x0_r; + this.state.scratch[2 * (i) + 1] = x0_i; + } + + work(output, outputOffset, outputStride, this.state.scratch, 0, 1, 1, this.state.factors.slice(), this.state); + } else { + if (input == output) { + work(this.state.scratch, 0, 1, input, inputOffset, 1, inputStride, this.state.factors.slice(), this.state); + + for (i = 0; i < this.state.n; i++) { + x0_r = this.state.scratch[2 * (i)]; + x0_i = this.state.scratch[2 * (i) + 1]; + + output[2 * ((outputOffset) + (outputStride) * (i))] = x0_r; + output[2 * ((outputOffset) + (outputStride) * (i)) + 1] = x0_i; + } + } else { + work(output, outputOffset, outputStride, input, inputOffset, 1, inputStride, this.state.factors.slice(), this.state); + } + } +}; + +complex.prototype.__super = complex; + +/** + * A Fast Fourier Transform module. + * + * @name FFT + * @processor + * + * @arg =!sampleRate + * @arg =!bufferSize + * + * @param type:UInt units:Hz default:44100 sampleRate Sample Rate the apparatus operates on. + * @param type:UInt default:4096 bufferSize The buffer size of the FFT. + * @param type:String min:0.0 default:forward method The direction to do the FFT to. +*/ +function FFT (sampleRate, n, inverse) { + var args = [].slice.call(arguments); + args[0] = this.sampleRate = isNaN(sampleRate) || sampleRate === null ? this.sampleRate : sampleRate; + args[1] = this.bufferSize = isNaN(n) || n === null ? this.bufferSize : n; + args[2] = !!inverse; + + this.__super.apply(this, args.slice(1)); +} + +FFT.prototype = complex.prototype; +FFT.prototype.inputType = 'real'; +FFT.prototype.sampleRate = 44100; +FFT.prototype.bufferSize = 4096; + +FFT.prototype.resetFT = function (s) { + this.inputBuffer = new Float64Array(2 * this.bufferSize); + this.outputBuffer = new Float64Array(2 * this.bufferSize); + this.spectrum = new Float64Array(this.bufferSize / 2); + + this.bandWidth = this.sampleRate / this.bufferSize / 2; + this.peakBand = 0; + this.peak = 0; + + this.sample = 0; + this.offset = 0; + this.maxOffset = this.inputType === 'real' ? this.bufferSize : this.bufferSize * 2; + + this.pushSample = this._pushSample; +}; + +FFT.prototype.pushSample = function (s) { + this.resetFT(); + + return this.pushSample(s); +}; + +FFT.prototype._pushSample = function (s) { + this.inputBuffer[this.offset] = s; + this.sample = s; + + this.offset = (this.offset + 1) % this.maxOffset; + if (!this.offset) this.process(); + + return s; +}; + +FFT.prototype.getMix = function () { + return this.sample; +}; + +/** + * Gets the frequency of a specified band. + * + * @name getBandFrequency + * @method FFT + * + * @param {Number} index The index of the band. + * @return {Number} The frequency. +*/ + +FFT.prototype.getBandFrequency = function (index) { + return this.bandWidth * index + this.bandWidth * 0.5; +}; + +/** + * Calculate the spectrum for the FFT buffer. + * + * @name calculateSpectrum + * @method FFT +*/ +FFT.prototype.calculateSpectrum = function () { + var i, n, rr, ii, mag; + var spectrum = this.spectrum; + var buffer = this.outputBuffer; + var bSi = 2 / this.bufferSize; + var l = this.bufferSize / 2; + + for (i=0, n=2; i this.peak) { + this.peakBand = i; + this.peak = mag; + } + + this.spectrum[i] = mag; + } +}; + +FFT.prototype._process = FFT.prototype.process; +FFT.prototype.process = function () { + this._process.apply(this, arguments); + this.calculateSpectrum(); +}; + +return FFT; + +}(); + +/* + wrapper-end.js + Please note that this file is of invalid syntax if standalone. +*/ + +/* Controls */ +audioLib.ADSREnvelope = ADSREnvelope; +audioLib.StepSequencer = StepSequencer; +audioLib.UIControl = UIControl; + +/* Effects */ +audioLib.BiquadFilter = BiquadFilter; +audioLib.BitCrusher = BitCrusher; +audioLib.Chorus = Chorus; +audioLib.CombFilter = CombFilter; +audioLib.Compressor = Compressor; +audioLib.Convolution = Convolution; +audioLib.Delay = Delay; +audioLib.Distortion = Distortion; +audioLib.GainController = GainController; +audioLib.IIRFilter = IIRFilter; +audioLib.LP12Filter = LP12Filter; +audioLib.Limiter = Limiter; +audioLib.Reverb = Freeverb; + +/* Geneneration */ +audioLib.Noise = Noise; +audioLib.Oscillator = Oscillator; +audioLib.Sampler = Sampler; + +/* Processing */ +audioLib.Amplitude = Amplitude; +audioLib.FFT = FFT; + +/* Miscellaneous */ + +/* FIXME: The should be templated somehow as well */ +audioLib.AudioDevice = audioLib.Sink = (function () { return this; } () ).Sink; +audioLib.Automation = Automation; +audioLib.BufferEffect = BufferEffect; +audioLib.EffectClass = EffectClass; +audioLib.GeneratorClass = GeneratorClass; +audioLib.codecs = audioLib.Codec = Codec; +audioLib.plugins = Plugin; + +/* Trigger the ready event (all is registered) */ + +while (onready.list.length) { + onready.list.shift().call(audioLib); +} +onready = null; + +/* Handle inheritance */ + +void function (names, i) { + function effects (name, effect, prototype, argNames) { + var proto, k; + + if (effect) { + prototype = prototype || effect.prototype; + proto = effect.prototype = new EffectClass(); + proto.name = proto.fxid = name; + + effects[name] = __class(name, effect, argNames); + + for (k in prototype) { + if (prototype.hasOwnProperty(k)){ + proto[k] = prototype[k]; + } + } + + for (k in EffectClass) { + if (k !== 'prototype' && EffectClass.hasOwnProperty(k)) { + effects[name][k] = EffectClass[k]; + } + } + } + + return effects[name]; + } + + + + audioLib.effects = effects; + + for (i=0; i + + + + + + + + +
+

warpFX

+ mute +
+
+ +
+ +
+ +
+
+ + +
+ diff --git a/views/grid.ejs b/views/grid.ejs index 04d8cb3..add0da1 100644 --- a/views/grid.ejs +++ b/views/grid.ejs @@ -53,24 +53,6 @@
-
-- cgit v1.2.3-70-g09d2