/* audiolib.js Jussi Kalliokoski https://github.com/jussi-kalliokoski/audiolib.js MIT license */ /* wrapper-start.js Please note that the file is not of valid syntax when standalone. */ this.audioLib = (function AUDIOLIB (global, Math, Object, Array) { function onready (callback) { onready.list.push(callback); } onready.list = []; var arrayType = global.Float32Array || Array, audioLib = this; function Float32Array (length) { var array = new arrayType(length); array.subarray = array.subarray || array.slice; return array; } audioLib.Float32Array = Float32Array; var __define = (function () { if (Object.defineProperty) { return Object.defineProperty; } else if (Object.prototype.__defineGetter__) { return function(obj, prop, desc){ desc.get && obj.__defineGetter__(prop, desc.get); desc.set && obj.__defineSetter__(prop, desc.set); } } }()); function __defineConst (obj, prop, value, enumerable) { if (__define) { __define(obj, prop, { get: function () { return value; }, enumerable: !!enumerable }); } else { // Cheap... obj[prop] = value; } } __defineConst(audioLib, '__define', __define); __defineConst(audioLib, '__defineConst', __defineConst); function __extend (obj) { var args = arguments, l = args.length, i, n; for (i=1; 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} 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