diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..4fdf69e --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,21 @@ +{ + "env": { + "browser": true, + "node": true + }, + "extends": "eslint:recommended", + "rules": { + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "double" + ], + "semi": [ + "error", + "always" + ] + } +} diff --git a/.gitignore b/.gitignore index b0dcffa..38d0df3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ examples/audio .project *~ .idea +node_modules/ +npm-debug.log diff --git a/Makefile b/Makefile index 1ac12e9..4b47878 100644 --- a/Makefile +++ b/Makefile @@ -7,9 +7,9 @@ VERSION ?= $(error Specify a version for your release (e.g., VERSION=0.5)) FREQUENCY ?= 440 benchmark: - ${JSSHELL} -m -j -p -e 'var FREQUENCY=${FREQUENCY};' -f dsp.js -f ./bench/bench.js -f ./bench/dft.js - ${JSSHELL} -m -j -p -e 'var FREQUENCY=${FREQUENCY};' -f dsp.js -f ./bench/bench.js -f ./bench/fft.js - ${JSSHELL} -m -j -p -e 'var FREQUENCY=${FREQUENCY};' -f dsp.js -f ./bench/bench.js -f ./bench/rfft.js + ${JSSHELL} -m -j -p -e 'var FREQUENCY=${FREQUENCY};' -f ./dist/dsp.js -f ./bench/bench.js -f ./bench/dft.js + ${JSSHELL} -m -j -p -e 'var FREQUENCY=${FREQUENCY};' -f ./dist/dsp.js -f ./bench/bench.js -f ./bench/fft.js + ${JSSHELL} -m -j -p -e 'var FREQUENCY=${FREQUENCY};' -f ./dist/dsp.js -f ./bench/bench.js -f ./bench/rfft.js ${JSSHELL} -m -j -p -f dsp.js -f ./bench/bench.js -f ./bench/deinterleave.js clean: diff --git a/bench/bench.js b/bench/bench.js index 7894b94..b4268e4 100644 --- a/bench/bench.js +++ b/bench/bench.js @@ -1,12 +1,12 @@ -function benchmark(func, loopCount) { +module.exports = function benchmark(func, loopCount) { loopCount = loopCount || 10000; var start = Date.now(); - for (var i = 0; i < loopCount; i++) { + for (var i = 0; i < loopCount; i++) { func(); } var end = Date.now(); return end - start; -} +}; diff --git a/bench/deinterleave.js b/bench/deinterleave.js index 930d399..ff4abf1 100644 --- a/bench/deinterleave.js +++ b/bench/deinterleave.js @@ -1,32 +1,37 @@ +/* global Float32Array */ +/* eslint-disable no-console */ +var benchmark = require("./bench"); +var dsp = require(".."); var bufferSize = 2048; -var sampleRate = 44100; +// var sampleRate = 44100; var buffer1 = new Float32Array(bufferSize); var buffer2 = new Float32Array(bufferSize); var buffer3 = new Float32Array(bufferSize); var buffer4 = new Float32Array(bufferSize); -for (var i = 0; i < bufferSize; i++) { +var i; +for (i = 0; i < bufferSize; i++) { buffer1[i] = (i % 2 === 0) ? -Math.random() : Math.random(); } -for (var i = 0; i < bufferSize; i++) { +for (i = 0; i < bufferSize; i++) { buffer2[i] = (i % 2 === 0) ? -Math.random() : Math.random(); } -for (var i = 0; i < bufferSize; i++) { +for (i = 0; i < bufferSize; i++) { buffer3[i] = (i % 2 === 0) ? -Math.random() : Math.random(); } -for (var i = 0; i < bufferSize; i++) { +for (i = 0; i < bufferSize; i++) { buffer4[i] = (i % 2 === 0) ? -Math.random() : Math.random(); } var channel; var temp; -var duration = benchmark(function() { - channel = DSP.deinterleave(DSP.MIX, buffer1); +var duration = benchmark(function() { + channel = dsp.DSP.deinterleave(dsp.DSP.MIX, buffer1); // cycle buffers temp = buffer1; @@ -36,5 +41,5 @@ var duration = benchmark(function() { buffer4 = temp; }, 100000); -print("Channel length: " + channel.length); -print("100000 iterations: " + (duration) + " ms (" + ((duration) / 100000) + "ms per iter)\n"); +console.log("Channel length: " + channel.length); +console.log("100000 iterations: " + (duration) + " ms (" + ((duration) / 100000) + "ms per iter)\n"); diff --git a/bench/dft.js b/bench/dft.js index 40ac61e..a2c0c73 100644 --- a/bench/dft.js +++ b/bench/dft.js @@ -1,9 +1,13 @@ +/* global FREQUENCY */ +/* eslint-disable no-console */ +var benchmark = require("./bench"); +var dsp = require(".."); var bufferSize = 2048; var sampleRate = 44100; var frequency = FREQUENCY || 440; -var dft = new DFT(bufferSize, sampleRate); -var osc = new Oscillator(DSP.SAW, frequency, 1.0, bufferSize, sampleRate); +var dft = new dsp.DFT(bufferSize, sampleRate); +var osc = new dsp.Oscillator(dsp.DSP.SAW, frequency, 1.0, bufferSize, sampleRate); var signal = osc.generate(); var duration = benchmark(function() { dft.forward(signal); }, 20); @@ -16,5 +20,5 @@ for (var i = 0; i < dft.spectrum.length; i++) { var peakFreq = dft.getBandFrequency(dft.peakBand); -print("Detected peak: " + peakFreq + " Hz (error " + Math.abs(peakFreq - frequency) + " Hz)"); -print("20 DFTs: " + (duration) + " ms (" + ((duration) / 20) + "ms per DFT)\n"); +console.log("Detected peak: " + peakFreq + " Hz (error " + Math.abs(peakFreq - frequency) + " Hz)"); +console.log("20 DFTs: " + (duration) + " ms (" + ((duration) / 20) + "ms per DFT)\n"); diff --git a/bench/fft.js b/bench/fft.js index 2a0fe58..95a5fe3 100644 --- a/bench/fft.js +++ b/bench/fft.js @@ -1,9 +1,13 @@ +/* global FREQUENCY */ +/* eslint-disable no-console */ +var benchmark = require("./bench"); +var dsp = require(".."); var bufferSize = 2048; var sampleRate = 44100; var frequency = FREQUENCY || 440; -var fft = new FFT(bufferSize, sampleRate); -var osc = new Oscillator(DSP.SAW, frequency, 1.0, bufferSize, sampleRate); +var fft = new dsp.FFT(bufferSize, sampleRate); +var osc = new dsp.Oscillator(dsp.DSP.SAW, frequency, 1.0, bufferSize, sampleRate); var signal = osc.generate(); var duration = benchmark(function() { fft.forward(signal); }); @@ -16,5 +20,5 @@ for (var i = 0; i < fft.spectrum.length; i++) { var peakFreq = fft.getBandFrequency(fft.peakBand); -print("Detected peak: " + peakFreq + " Hz (error " + Math.abs(peakFreq - frequency) + " Hz)"); -print("10000 FFTs: " + (duration) + " ms (" + ((duration) / 10000) + "ms per FFT)\n"); +console.log("Detected peak: " + peakFreq + " Hz (error " + Math.abs(peakFreq - frequency) + " Hz)"); +console.log("10000 FFTs: " + (duration) + " ms (" + ((duration) / 10000) + "ms per FFT)\n"); diff --git a/bench/index.js b/bench/index.js new file mode 100644 index 0000000..ca506a5 --- /dev/null +++ b/bench/index.js @@ -0,0 +1,6 @@ +/* eslint-disable */ +FREQUENCY = 440; +require("./deinterleave"); +require("./dft"); +require("./fft"); +require("./rfft"); diff --git a/bench/rfft.js b/bench/rfft.js index bc428d4..b70ad3d 100644 --- a/bench/rfft.js +++ b/bench/rfft.js @@ -1,9 +1,13 @@ +/* global FREQUENCY */ +/* eslint-disable no-console */ +var benchmark = require("./bench"); +var dsp = require(".."); var bufferSize = 2048; var sampleRate = 44100; var frequency = FREQUENCY || 440; -var fft = new RFFT(bufferSize, sampleRate); -var osc = new Oscillator(DSP.SAW, frequency, 1.0, bufferSize, sampleRate); +var fft = new dsp.RFFT(bufferSize, sampleRate); +var osc = new dsp.Oscillator(dsp.DSP.SAW, frequency, 1.0, bufferSize, sampleRate); var signal = osc.generate(); var duration = benchmark(function() { fft.forward(signal); }); @@ -16,5 +20,5 @@ for (var i = 0; i < fft.spectrum.length; i++) { var peakFreq = fft.getBandFrequency(fft.peakBand); -print("Detected peak: " + peakFreq + " Hz (error " + Math.abs(peakFreq - frequency) + " Hz)"); -print("10000 FFTs: " + (duration) + " ms (" + ((duration) / 10000) + "ms per FFT)\n"); +console.log("Detected peak: " + peakFreq + " Hz (error " + Math.abs(peakFreq - frequency) + " Hz)"); +console.log("10000 FFTs: " + (duration) + " ms (" + ((duration) / 10000) + "ms per FFT)\n"); diff --git a/dsp.js b/dist/dsp.js similarity index 85% rename from dsp.js rename to dist/dsp.js index 7188235..2fb4a3d 100644 --- a/dsp.js +++ b/dist/dsp.js @@ -1,950 +1,1536 @@ -/* - * DSP.js - a comprehensive digital signal processing library for javascript - * - * Created by Corban Brook on 2010-01-01. - * Copyright 2010 Corban Brook. All rights reserved. +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o this.attack && this.samplesProcessed <= this.decay ) { + amplitude = 1 + (this.sustainLevel - 1) * ((this.samplesProcessed - this.attack) / (this.decay - this.attack)); + } else if ( this.samplesProcessed > this.decay && this.samplesProcessed <= this.sustain ) { + amplitude = this.sustainLevel; + } else if ( this.samplesProcessed > this.sustain && this.samplesProcessed <= this.release ) { + amplitude = this.sustainLevel + (0 - this.sustainLevel) * ((this.samplesProcessed - this.sustain) / (this.release - this.sustain)); } -} -setupTypedArray("Float64Array", "WebGLFloatArray"); -setupTypedArray("Int32Array", "WebGLIntArray"); -setupTypedArray("Uint16Array", "WebGLUnsignedShortArray"); -setupTypedArray("Uint8Array", "WebGLUnsignedByteArray"); + return sample * amplitude; +}; +/** + * Get current value + * @return {Number} amplitude + */ +ADSR.prototype.value = function() { + var amplitude = 0; -//////////////////////////////////////////////////////////////////////////////// -// DSP UTILITY FUNCTIONS // -//////////////////////////////////////////////////////////////////////////////// + if ( this.samplesProcessed <= this.attack ) { + amplitude = 0 + (1 - 0) * ((this.samplesProcessed - 0) / (this.attack - 0)); + } else if ( this.samplesProcessed > this.attack && this.samplesProcessed <= this.decay ) { + amplitude = 1 + (this.sustainLevel - 1) * ((this.samplesProcessed - this.attack) / (this.decay - this.attack)); + } else if ( this.samplesProcessed > this.decay && this.samplesProcessed <= this.sustain ) { + amplitude = this.sustainLevel; + } else if ( this.samplesProcessed > this.sustain && this.samplesProcessed <= this.release ) { + amplitude = this.sustainLevel + (0 - this.sustainLevel) * ((this.samplesProcessed - this.sustain) / (this.release - this.sustain)); + } + + return amplitude; +}; /** - * Inverts the phase of a signal - * - * @param {Array} buffer A sample buffer - * - * @returns The inverted sample buffer + * Process a buffer + * @param {Array} buffer */ -DSP.invert = function(buffer) { - for (var i = 0, len = buffer.length; i < len; i++) { - buffer[i] *= -1; +ADSR.prototype.process = function(buffer) { + for ( var i = 0; i < buffer.length; i++ ) { + buffer[i] *= this.value(); + + this.samplesProcessed++; } return buffer; }; /** - * Converts split-stereo (dual mono) sample buffers into a stereo interleaved sample buffer - * - * @param {Array} left A sample buffer - * @param {Array} right A sample buffer - * - * @returns The stereo interleaved buffer + * Test if the envelope is active + * @return {Boolean} */ -DSP.interleave = function(left, right) { - if (left.length !== right.length) { - throw "Can not interleave. Channel lengths differ."; - } - - var stereoInterleaved = new Float64Array(left.length * 2); - - for (var i = 0, len = left.length; i < len; i++) { - stereoInterleaved[2*i] = left[i]; - stereoInterleaved[2*i+1] = right[i]; +ADSR.prototype.isActive = function() { + if ( this.samplesProcessed > this.release || this.samplesProcessed === -1 ) { + return false; + } else { + return true; } - - return stereoInterleaved; }; /** - * Converts a stereo-interleaved sample buffer into split-stereo (dual mono) sample buffers - * - * @param {Array} buffer A stereo-interleaved sample buffer - * - * @returns an Array containing left and right channels + * Disable the envelope */ -DSP.deinterleave = (function() { - var left, right, mix, deinterleaveChannel = []; - - deinterleaveChannel[DSP.MIX] = function(buffer) { - for (var i = 0, len = buffer.length/2; i < len; i++) { - mix[i] = (buffer[2*i] + buffer[2*i+1]) / 2; - } - return mix; - }; - - deinterleaveChannel[DSP.LEFT] = function(buffer) { - for (var i = 0, len = buffer.length/2; i < len; i++) { - left[i] = buffer[2*i]; - } - return left; - }; - - deinterleaveChannel[DSP.RIGHT] = function(buffer) { - for (var i = 0, len = buffer.length/2; i < len; i++) { - right[i] = buffer[2*i+1]; - } - return right; - }; - - return function(channel, buffer) { - left = left || new Float64Array(buffer.length/2); - right = right || new Float64Array(buffer.length/2); - mix = mix || new Float64Array(buffer.length/2); +ADSR.prototype.disable = function() { + this.samplesProcessed = -1; +}; - if (buffer.length/2 !== left.length) { - left = new Float64Array(buffer.length/2); - right = new Float64Array(buffer.length/2); - mix = new Float64Array(buffer.length/2); - } +module.exports = ADSR; - return deinterleaveChannel[channel](buffer); - }; -}()); +},{}],2:[function(require,module,exports){ +/* global Float64Array */ +var DSP = require("./dsp"); +var sinh = require("./sinh"); /** - * Separates a channel from a stereo-interleaved sample buffer - * - * @param {Array} buffer A stereo-interleaved sample buffer - * @param {Number} channel A channel constant (LEFT, RIGHT, MIX) + * Biquad filter * - * @returns an Array containing a signal mono sample buffer - */ -DSP.getChannel = DSP.deinterleave; - -/** - * Helper method (for Reverb) to mix two (interleaved) samplebuffers. It's possible - * to negate the second buffer while mixing and to perform a volume correction - * on the final signal. + * Created by Ricard Marxer on 2010-05-23. + * Copyright 2010 Ricard Marxer. All rights reserved. * - * @param {Array} sampleBuffer1 Array containing Float values or a Float64Array - * @param {Array} sampleBuffer2 Array containing Float values or a Float64Array - * @param {Boolean} negate When true inverts/flips the audio signal - * @param {Number} volumeCorrection When you add multiple sample buffers, use this to tame your signal ;) + * Implementation based on: + * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt * - * @returns A new Float64Array interleaved buffer. + * @param {Number} type + * @param {Number} sampleRate + * @constructor */ -DSP.mixSampleBuffers = function(sampleBuffer1, sampleBuffer2, negate, volumeCorrection){ - var outputSamples = new Float64Array(sampleBuffer1); +function Biquad(type, sampleRate) { + this.Fs = sampleRate; + this.type = type; // type of the filter + this.parameterType = DSP.Q; // type of the parameter - for(var i = 0; i peak) ? Math.abs(buffer[i]) : peak; - } - - return peak; -}; + this.b2 = 0; + this.a2 = 0; -// Fourier Transform Module used by DFT, FFT, RFFT -function FourierTransform(bufferSize, sampleRate) { - this.bufferSize = bufferSize; - this.sampleRate = sampleRate; - this.bandwidth = 2 / bufferSize * sampleRate / 2; + this.b0a0 = this.b0 / this.a0; + this.b1a0 = this.b1 / this.a0; + this.b2a0 = this.b2 / this.a0; + this.a1a0 = this.a1 / this.a0; + this.a2a0 = this.a2 / this.a0; - this.spectrum = new Float64Array(bufferSize/2); - this.real = new Float64Array(bufferSize); - this.imag = new Float64Array(bufferSize); + this.f0 = 3000; // "wherever it"s happenin", man." Center Frequency or + // Corner Frequency, or shelf midpoint frequency, depending + // on which filter type. The "significant frequency". - this.peakBand = 0; - this.peak = 0; + this.dBgain = 12; // used only for peaking and shelving filters - /** - * Calculates the *middle* frequency of an FFT band. - * - * @param {Number} index The index of the FFT band. - * - * @returns The middle frequency in Hz. - */ - this.getBandFrequency = function(index) { - return this.bandwidth * index + this.bandwidth / 2; - }; + this.Q = 1; // the EE kind of definition, except for peakingEQ in which A*Q is + // the classic EE Q. That adjustment in definition was made so that + // a boost of N dB followed by a cut of N dB for identical Q and + // f0/Fs results in a precisely flat unity gain filter or "wire". - this.calculateSpectrum = function() { - var spectrum = this.spectrum, - real = this.real, - imag = this.imag, - bSi = 2 / this.bufferSize, - sqrt = Math.sqrt, - rval, - ival, - mag; + this.BW = -3; // the bandwidth in octaves (between -3 dB frequencies for BPF + // and notch or between midpoint (dBgain/2) gain frequencies for + // peaking EQ - for (var i = 0, N = bufferSize/2; i < N; i++) { - rval = real[i]; - ival = imag[i]; - mag = bSi * sqrt(rval * rval + ival * ival); + this.S = 1; // a "shelf slope" parameter (for shelving EQ only). When S = 1, + // the shelf slope is as steep as it can be and remain monotonically + // increasing or decreasing gain with frequency. The shelf slope, in + // dB/octave, remains proportional to S for all other values for a + // fixed f0/Fs and dBgain. - if (mag > this.peak) { - this.peakBand = i; - this.peak = mag; - } + this.coefficients = function() { + var b = [this.b0, this.b1, this.b2]; + var a = [this.a0, this.a1, this.a2]; + return {b: b, a:a}; + }; - spectrum[i] = mag; - } + this.setFilterType = function(type) { + this.type = type; + this.recalculateCoefficients(); }; -} -/** - * DFT is a class for calculating the Discrete Fourier Transform of a signal. - * - * @param {Number} bufferSize The size of the sample buffer to be computed - * @param {Number} sampleRate The sampleRate of the buffer (eg. 44100) - * - * @constructor - */ -function DFT(bufferSize, sampleRate) { - FourierTransform.call(this, bufferSize, sampleRate); + this.setSampleRate = function(rate) { + this.Fs = rate; + this.recalculateCoefficients(); + }; - var N = bufferSize/2 * bufferSize; - var TWO_PI = 2 * Math.PI; + this.setQ = function(q) { + this.parameterType = DSP.Q; + this.Q = Math.max(Math.min(q, 115.0), 0.001); + this.recalculateCoefficients(); + }; - this.sinTable = new Float64Array(N); - this.cosTable = new Float64Array(N); + this.setBW = function(bw) { + this.parameterType = DSP.BW; + this.BW = bw; + this.recalculateCoefficients(); + }; - for (var i = 0; i < N; i++) { - this.sinTable[i] = Math.sin(i * TWO_PI / bufferSize); - this.cosTable[i] = Math.cos(i * TWO_PI / bufferSize); - } -} + this.setS = function(s) { + this.parameterType = DSP.S; + this.S = Math.max(Math.min(s, 5.0), 0.0001); + this.recalculateCoefficients(); + }; -/** - * Performs a forward transform on the sample buffer. - * Converts a time domain signal to frequency domain spectra. - * - * @param {Array} buffer The sample buffer - * - * @returns The frequency spectrum array - */ -DFT.prototype.forward = function(buffer) { - var real = this.real, - imag = this.imag, - rval, - ival; + this.setF0 = function(freq) { + this.f0 = freq; + this.recalculateCoefficients(); + }; - for (var k = 0; k < this.bufferSize/2; k++) { - rval = 0.0; - ival = 0.0; + this.setDbGain = function(g) { + this.dBgain = g; + this.recalculateCoefficients(); + }; - for (var n = 0; n < buffer.length; n++) { - rval += this.cosTable[k*n] * buffer[n]; - ival += this.sinTable[k*n] * buffer[n]; + this.recalculateCoefficients = function() { + var A; + if (type === DSP.PEAKING_EQ || type === DSP.LOW_SHELF || type === DSP.HIGH_SHELF ) { + A = Math.pow(10, (this.dBgain/40)); // for peaking and shelving EQ filters only + } else { + A = Math.sqrt( Math.pow(10, (this.dBgain/20)) ); } - real[k] = rval; - imag[k] = ival; - } - - return this.calculateSpectrum(); -}; + var w0 = DSP.TWO_PI * this.f0 / this.Fs; + var cosw0 = Math.cos(w0); + var sinw0 = Math.sin(w0); -/** - * FFT is a class for calculating the Discrete Fourier Transform of a signal - * with the Fast Fourier Transform algorithm. - * - * @param {Number} bufferSize The size of the sample buffer to be computed. Must be power of 2 - * @param {Number} sampleRate The sampleRate of the buffer (eg. 44100) - * - * @constructor - */ -function FFT(bufferSize, sampleRate) { - FourierTransform.call(this, bufferSize, sampleRate); - - this.reverseTable = new Uint32Array(bufferSize); + var alpha = 0; - var limit = 1; - var bit = bufferSize >> 1; + switch (this.parameterType) { + case DSP.Q: + alpha = sinw0/(2*this.Q); + break; - var i; + case DSP.BW: + alpha = sinw0 * sinh( Math.LN2/2 * this.BW * w0/sinw0 ); + break; - while (limit < bufferSize) { - for (i = 0; i < limit; i++) { - this.reverseTable[i + limit] = this.reverseTable[i] + bit; + case DSP.S: + alpha = sinw0/2 * Math.sqrt( (A + 1/A)*(1/this.S - 1) + 2 ); + break; } - limit = limit << 1; - bit = bit >> 1; - } + /** + FYI: The relationship between bandwidth and Q is + 1/Q = 2*sinh(ln(2)/2*BW*w0/sin(w0)) (digital filter w BLT) + or 1/Q = 2*sinh(ln(2)/2*BW) (analog filter prototype) - this.sinTable = new Float64Array(bufferSize); - this.cosTable = new Float64Array(bufferSize); + The relationship between shelf slope and Q is + 1/Q = sqrt((A + 1/A)*(1/S - 1) + 2) + */ - for (i = 0; i < bufferSize; i++) { - this.sinTable[i] = Math.sin(-Math.PI/i); - this.cosTable[i] = Math.cos(-Math.PI/i); - } -} + var coeff; -/** - * Performs a forward transform on the sample buffer. - * Converts a time domain signal to frequency domain spectra. - * - * @param {Array} buffer The sample buffer. Buffer Length must be power of 2 - * - * @returns The frequency spectrum array - */ -FFT.prototype.forward = function(buffer) { - // Locally scope variables for speed up - var bufferSize = this.bufferSize, - cosTable = this.cosTable, - sinTable = this.sinTable, - reverseTable = this.reverseTable, - real = this.real, - imag = this.imag, - spectrum = this.spectrum; + switch (this.type) { + case DSP.LPF: // H(s) = 1 / (s^2 + s/Q + 1) + this.b0 = (1 - cosw0)/2; + this.b1 = 1 - cosw0; + this.b2 = (1 - cosw0)/2; + this.a0 = 1 + alpha; + this.a1 = -2 * cosw0; + this.a2 = 1 - alpha; + break; - var k = Math.floor(Math.log(bufferSize) / Math.LN2); + case DSP.HPF: // H(s) = s^2 / (s^2 + s/Q + 1) + this.b0 = (1 + cosw0)/2; + this.b1 = -(1 + cosw0); + this.b2 = (1 + cosw0)/2; + this.a0 = 1 + alpha; + this.a1 = -2 * cosw0; + this.a2 = 1 - alpha; + break; - if (Math.pow(2, k) !== bufferSize) { throw "Invalid buffer size, must be a power of 2."; } - if (bufferSize !== buffer.length) { throw "Supplied buffer is not the same size as defined FFT. FFT Size: " + bufferSize + " Buffer Size: " + buffer.length; } + case DSP.BPF_CONSTANT_SKIRT: // H(s) = s / (s^2 + s/Q + 1) (constant skirt gain, peak gain = Q) + this.b0 = sinw0/2; + this.b1 = 0; + this.b2 = -sinw0/2; + this.a0 = 1 + alpha; + this.a1 = -2*cosw0; + this.a2 = 1 - alpha; + break; - var halfSize = 1, - phaseShiftStepReal, - phaseShiftStepImag, - currentPhaseShiftReal, - currentPhaseShiftImag, - off, - tr, - ti, - tmpReal, - i; - - for (i = 0; i < bufferSize; i++) { - real[i] = buffer[reverseTable[i]]; - imag[i] = 0; - } - - while (halfSize < bufferSize) { - //phaseShiftStepReal = Math.cos(-Math.PI/halfSize); - //phaseShiftStepImag = Math.sin(-Math.PI/halfSize); - phaseShiftStepReal = cosTable[halfSize]; - phaseShiftStepImag = sinTable[halfSize]; - - currentPhaseShiftReal = 1; - currentPhaseShiftImag = 0; + case DSP.BPF_CONSTANT_PEAK: // H(s) = (s/Q) / (s^2 + s/Q + 1) (constant 0 dB peak gain) + this.b0 = alpha; + this.b1 = 0; + this.b2 = -alpha; + this.a0 = 1 + alpha; + this.a1 = -2*cosw0; + this.a2 = 1 - alpha; + break; - for (var fftStep = 0; fftStep < halfSize; fftStep++) { - i = fftStep; + case DSP.NOTCH: // H(s) = (s^2 + 1) / (s^2 + s/Q + 1) + this.b0 = 1; + this.b1 = -2*cosw0; + this.b2 = 1; + this.a0 = 1 + alpha; + this.a1 = -2*cosw0; + this.a2 = 1 - alpha; + break; - while (i < bufferSize) { - off = i + halfSize; - tr = (currentPhaseShiftReal * real[off]) - (currentPhaseShiftImag * imag[off]); - ti = (currentPhaseShiftReal * imag[off]) + (currentPhaseShiftImag * real[off]); + case DSP.APF: // H(s) = (s^2 - s/Q + 1) / (s^2 + s/Q + 1) + this.b0 = 1 - alpha; + this.b1 = -2*cosw0; + this.b2 = 1 + alpha; + this.a0 = 1 + alpha; + this.a1 = -2*cosw0; + this.a2 = 1 - alpha; + break; - real[off] = real[i] - tr; - imag[off] = imag[i] - ti; - real[i] += tr; - imag[i] += ti; + case DSP.PEAKING_EQ: // H(s) = (s^2 + s*(A/Q) + 1) / (s^2 + s/(A*Q) + 1) + this.b0 = 1 + alpha*A; + this.b1 = -2*cosw0; + this.b2 = 1 - alpha*A; + this.a0 = 1 + alpha/A; + this.a1 = -2*cosw0; + this.a2 = 1 - alpha/A; + break; - i += halfSize << 1; - } + case DSP.LOW_SHELF: // H(s) = A * (s^2 + (sqrt(A)/Q)*s + A)/(A*s^2 + (sqrt(A)/Q)*s + 1) + coeff = sinw0 * Math.sqrt( (A^2 + 1)*(1/this.S - 1) + 2*A ); + this.b0 = A*((A+1) - (A-1)*cosw0 + coeff); + this.b1 = 2*A*((A-1) - (A+1)*cosw0); + this.b2 = A*((A+1) - (A-1)*cosw0 - coeff); + this.a0 = (A+1) + (A-1)*cosw0 + coeff; + this.a1 = -2*((A-1) + (A+1)*cosw0); + this.a2 = (A+1) + (A-1)*cosw0 - coeff; + break; - tmpReal = currentPhaseShiftReal; - currentPhaseShiftReal = (tmpReal * phaseShiftStepReal) - (currentPhaseShiftImag * phaseShiftStepImag); - currentPhaseShiftImag = (tmpReal * phaseShiftStepImag) + (currentPhaseShiftImag * phaseShiftStepReal); + case DSP.HIGH_SHELF: // H(s) = A * (A*s^2 + (sqrt(A)/Q)*s + 1)/(s^2 + (sqrt(A)/Q)*s + A) + coeff = sinw0 * Math.sqrt( (A^2 + 1)*(1/this.S - 1) + 2*A ); + this.b0 = A*((A+1) + (A-1)*cosw0 + coeff); + this.b1 = -2*A*((A-1) + (A+1)*cosw0); + this.b2 = A*((A+1) + (A-1)*cosw0 - coeff); + this.a0 = (A+1) - (A-1)*cosw0 + coeff; + this.a1 = 2*((A-1) - (A+1)*cosw0); + this.a2 = (A+1) - (A-1)*cosw0 - coeff; + break; } - halfSize = halfSize << 1; - } - - return this.calculateSpectrum(); -}; - -FFT.prototype.inverse = function(real, imag) { - // Locally scope variables for speed up - var bufferSize = this.bufferSize, - cosTable = this.cosTable, - sinTable = this.sinTable, - reverseTable = this.reverseTable, - spectrum = this.spectrum; - - real = real || this.real; - imag = imag || this.imag; + this.b0a0 = this.b0/this.a0; + this.b1a0 = this.b1/this.a0; + this.b2a0 = this.b2/this.a0; + this.a1a0 = this.a1/this.a0; + this.a2a0 = this.a2/this.a0; + }; - var halfSize = 1, - phaseShiftStepReal, - phaseShiftStepImag, - currentPhaseShiftReal, - currentPhaseShiftImag, - off, - tr, - ti, - tmpReal, - i; + this.process = function(buffer) { + //y[n] = (b0/a0)*x[n] + (b1/a0)*x[n-1] + (b2/a0)*x[n-2] + // - (a1/a0)*y[n-1] - (a2/a0)*y[n-2] - for (i = 0; i < bufferSize; i++) { - imag[i] *= -1; - } + var len = buffer.length; + var output = new Float64Array(len); - var revReal = new Float64Array(bufferSize); - var revImag = new Float64Array(bufferSize); - - for (i = 0; i < real.length; i++) { - revReal[i] = real[reverseTable[i]]; - revImag[i] = imag[reverseTable[i]]; - } - - real = revReal; - imag = revImag; + for ( var i=0; i>> 1, - nm1 = bufferSize - 1, - i = 1, r = 0, h; + real[k] = rval; + imag[k] = ival; + } - dest[0] = source[0]; + return this.calculateSpectrum(); +}; - do { - r += halfSize; - dest[i] = source[r]; - dest[r] = source[i]; - - i++; +module.exports = DFT; - h = halfSize << 1; - while (h = h >> 1, !((r ^= h) & h)); +},{"./fourier":6}],4:[function(require,module,exports){ +/* global Float64Array */ - if (r >= i) { - dest[i] = source[r]; - dest[r] = source[i]; +//////////////////////////////////////////////////////////////////////////////// +// CONSTANTS // +//////////////////////////////////////////////////////////////////////////////// - dest[nm1-i] = source[nm1-r]; - dest[nm1-r] = source[nm1-i]; - } - i++; - } while (i < halfSize); - dest[nm1] = source[nm1]; - }; +/** + * DSP is an object which contains general purpose utility functions and constants + */ +var DSP = { + // Channels + LEFT: 0, + RIGHT: 1, + MIX: 2, - this.generateReverseTable = function () { - var bufferSize = this.bufferSize, - halfSize = bufferSize >>> 1, - nm1 = bufferSize - 1, - i = 1, r = 0, h; + // Waveforms + SINE: 1, + TRIANGLE: 2, + SAW: 3, + SQUARE: 4, - this.reverseTable[0] = 0; + // Filters + LOWPASS: 0, + HIGHPASS: 1, + BANDPASS: 2, + NOTCH: 3, - do { - r += halfSize; - - this.reverseTable[i] = r; - this.reverseTable[r] = i; + // Window functions + BARTLETT: 1, + BARTLETTHANN: 2, + BLACKMAN: 3, + COSINE: 4, + GAUSS: 5, + HAMMING: 6, + HANN: 7, + LANCZOS: 8, + RECTANGULAR: 9, + TRIANGULAR: 10, - i++; + // Loop modes + OFF: 0, + FW: 1, + BW: 2, + FWBW: 3, - h = halfSize << 1; - while (h = h >> 1, !((r ^= h) & h)); + // Math + TWO_PI: 2*Math.PI +}; - if (r >= i) { - this.reverseTable[i] = r; - this.reverseTable[r] = i; +// Setup arrays for platforms which do not support byte arrays +function setupTypedArray(name, fallback) { + // check if TypedArray exists + // typeof on Minefield and Chrome return function, typeof on Webkit returns object. + if (typeof this[name] !== "function" && typeof this[name] !== "object") { + // nope.. check if WebGLArray exists + if (typeof this[fallback] === "function" && typeof this[fallback] !== "object") { + this[name] = this[fallback]; + } else { + // nope.. set as Native JS array + this[name] = function(obj) { + if (obj instanceof Array) { + return obj; + } else if (typeof obj === "number") { + return new Array(obj); + } + }; + } + } +} - this.reverseTable[nm1-i] = nm1-r; - this.reverseTable[nm1-r] = nm1-i; - } - i++; - } while (i < halfSize); +setupTypedArray("Float64Array", "WebGLFloatArray"); +setupTypedArray("Int32Array", "WebGLIntArray"); +setupTypedArray("Uint16Array", "WebGLUnsignedShortArray"); +setupTypedArray("Uint8Array", "WebGLUnsignedByteArray"); - this.reverseTable[nm1] = nm1; + +//////////////////////////////////////////////////////////////////////////////// +// DSP UTILITY FUNCTIONS // +//////////////////////////////////////////////////////////////////////////////// + +/** + * Inverts the phase of a signal + * + * @param {Array} buffer A sample buffer + * + * @returns The inverted sample buffer + */ +DSP.invert = function(buffer) { + for (var i = 0, len = buffer.length; i < len; i++) { + buffer[i] *= -1; + } + + return buffer; +}; + +/** + * Converts split-stereo (dual mono) sample buffers into a stereo interleaved sample buffer + * + * @param {Array} left A sample buffer + * @param {Array} right A sample buffer + * + * @returns The stereo interleaved buffer + */ +DSP.interleave = function(left, right) { + if (left.length !== right.length) { + throw "Can not interleave. Channel lengths differ."; + } + + var stereoInterleaved = new Float64Array(left.length * 2); + + for (var i = 0, len = left.length; i < len; i++) { + stereoInterleaved[2*i] = left[i]; + stereoInterleaved[2*i+1] = right[i]; + } + + return stereoInterleaved; +}; + +/** + * Converts a stereo-interleaved sample buffer into split-stereo (dual mono) sample buffers + * + * @param {Array} buffer A stereo-interleaved sample buffer + * + * @returns an Array containing left and right channels + */ +DSP.deinterleave = (function() { + var left, right, mix, deinterleaveChannel = []; + + deinterleaveChannel[DSP.MIX] = function(buffer) { + for (var i = 0, len = buffer.length/2; i < len; i++) { + mix[i] = (buffer[2*i] + buffer[2*i+1]) / 2; + } + return mix; }; - this.generateReverseTable(); -} + deinterleaveChannel[DSP.LEFT] = function(buffer) { + for (var i = 0, len = buffer.length/2; i < len; i++) { + left[i] = buffer[2*i]; + } + return left; + }; + deinterleaveChannel[DSP.RIGHT] = function(buffer) { + for (var i = 0, len = buffer.length/2; i < len; i++) { + right[i] = buffer[2*i+1]; + } + return right; + }; -// Ordering of output: -// -// trans[0] = re[0] (==zero frequency, purely real) -// trans[1] = re[1] -// ... -// trans[n/2-1] = re[n/2-1] -// trans[n/2] = re[n/2] (==nyquist frequency, purely real) -// -// trans[n/2+1] = im[n/2-1] -// trans[n/2+2] = im[n/2-2] -// ... -// trans[n-1] = im[1] + return function(channel, buffer) { + left = left || new Float64Array(buffer.length/2); + right = right || new Float64Array(buffer.length/2); + mix = mix || new Float64Array(buffer.length/2); -RFFT.prototype.forward = function(buffer) { - var n = this.bufferSize, - spectrum = this.spectrum, - x = this.trans, - TWO_PI = 2*Math.PI, - sqrt = Math.sqrt, - i = n >>> 1, - bSi = 2 / n, - n2, n4, n8, nn, - t1, t2, t3, t4, - i1, i2, i3, i4, i5, i6, i7, i8, - st1, cc1, ss1, cc3, ss3, - e, - a, - rval, ival, mag; + if (buffer.length/2 !== left.length) { + left = new Float64Array(buffer.length/2); + right = new Float64Array(buffer.length/2); + mix = new Float64Array(buffer.length/2); + } - this.reverseBinPermute(x, buffer); + return deinterleaveChannel[channel](buffer); + }; +}()); - /* - var reverseTable = this.reverseTable; +/** + * Separates a channel from a stereo-interleaved sample buffer + * + * @param {Array} buffer A stereo-interleaved sample buffer + * @param {Number} channel A channel constant (LEFT, RIGHT, MIX) + * + * @returns an Array containing a signal mono sample buffer + */ +DSP.getChannel = DSP.deinterleave; - for (var k = 0, len = reverseTable.length; k < len; k++) { - x[k] = buffer[reverseTable[k]]; +/** + * Helper method (for Reverb) to mix two (interleaved) samplebuffers. It's possible + * to negate the second buffer while mixing and to perform a volume correction + * on the final signal. + * + * @param {Array} sampleBuffer1 Array containing Float values or a Float64Array + * @param {Array} sampleBuffer2 Array containing Float values or a Float64Array + * @param {Boolean} negate When true inverts/flips the audio signal + * @param {Number} volumeCorrection When you add multiple sample buffers, use this to tame your signal ;) + * + * @returns A new Float64Array interleaved buffer. + */ +DSP.mixSampleBuffers = function(sampleBuffer1, sampleBuffer2, negate, volumeCorrection){ + var outputSamples = new Float64Array(sampleBuffer1); + + for(var i = 0; i>> 1; + return Math.sqrt(total / n); +}; - while((nn = nn >>> 1)) { - ix = 0; - n2 = n2 << 1; - id = n2 << 1; - n4 = n2 >>> 2; - n8 = n2 >>> 3; - do { - if(n4 !== 1) { - for(i0 = ix; i0 < n; i0 += id) { - i1 = i0; - i2 = i1 + n4; - i3 = i2 + n4; - i4 = i3 + n4; - - //diffsum3_r(x[i3], x[i4], t1); // {a, b, s} <--| {a, b-a, a+b} - t1 = x[i3] + x[i4]; - x[i4] -= x[i3]; - //sumdiff3(x[i1], t1, x[i3]); // {a, b, d} <--| {a+b, b, a-b} - x[i3] = x[i1] - t1; - x[i1] += t1; - - i1 += n8; - i2 += n8; - i3 += n8; - i4 += n8; - - //sumdiff(x[i3], x[i4], t1, t2); // {s, d} <--| {a+b, a-b} - t1 = x[i3] + x[i4]; - t2 = x[i3] - x[i4]; - - t1 = -t1 * Math.SQRT1_2; - t2 *= Math.SQRT1_2; - - // sumdiff(t1, x[i2], x[i4], x[i3]); // {s, d} <--| {a+b, a-b} - st1 = x[i2]; - x[i4] = t1 + st1; - x[i3] = t1 - st1; - - //sumdiff3(x[i1], t2, x[i2]); // {a, b, d} <--| {a+b, b, a-b} - x[i2] = x[i1] - t2; - x[i1] += t2; - } - } else { - for(i0 = ix; i0 < n; i0 += id) { - i1 = i0; - i2 = i1 + n4; - i3 = i2 + n4; - i4 = i3 + n4; - - //diffsum3_r(x[i3], x[i4], t1); // {a, b, s} <--| {a, b-a, a+b} - t1 = x[i3] + x[i4]; - x[i4] -= x[i3]; - - //sumdiff3(x[i1], t1, x[i3]); // {a, b, d} <--| {a+b, b, a-b} - x[i3] = x[i1] - t1; - x[i1] += t1; - } - } - - ix = (id << 1) - n2; - id = id << 2; - } while (ix < n); - - e = TWO_PI / n2; +/** + * Find Peak of signal + * @param {Array} buffer + */ +DSP.Peak = function(buffer) { + var peak = 0; - for (var j = 1; j < n8; j++) { - a = j * e; - ss1 = Math.sin(a); - cc1 = Math.cos(a); + for (var i = 0, n = buffer.length; i < n; i++) { + peak = (Math.abs(buffer[i]) > peak) ? Math.abs(buffer[i]) : peak; + } - //ss3 = sin(3*a); cc3 = cos(3*a); - cc3 = 4*cc1*(cc1*cc1-0.75); - ss3 = 4*ss1*(0.75-ss1*ss1); - - ix = 0; id = n2 << 1; - do { - for (i0 = ix; i0 < n; i0 += id) { - i1 = i0 + j; - i2 = i1 + n4; - i3 = i2 + n4; - i4 = i3 + n4; - - i5 = i0 + n4 - j; - i6 = i5 + n4; - i7 = i6 + n4; - i8 = i7 + n4; - - //cmult(c, s, x, y, &u, &v) - //cmult(cc1, ss1, x[i7], x[i3], t2, t1); // {u,v} <--| {x*c-y*s, x*s+y*c} - t2 = x[i7]*cc1 - x[i3]*ss1; - t1 = x[i7]*ss1 + x[i3]*cc1; - - //cmult(cc3, ss3, x[i8], x[i4], t4, t3); - t4 = x[i8]*cc3 - x[i4]*ss3; - t3 = x[i8]*ss3 + x[i4]*cc3; - - //sumdiff(t2, t4); // {a, b} <--| {a+b, a-b} - st1 = t2 - t4; - t2 += t4; - t4 = st1; - - //sumdiff(t2, x[i6], x[i8], x[i3]); // {s, d} <--| {a+b, a-b} - //st1 = x[i6]; x[i8] = t2 + st1; x[i3] = t2 - st1; - x[i8] = t2 + x[i6]; - x[i3] = t2 - x[i6]; - - //sumdiff_r(t1, t3); // {a, b} <--| {a+b, b-a} - st1 = t3 - t1; - t1 += t3; - t3 = st1; - - //sumdiff(t3, x[i2], x[i4], x[i7]); // {s, d} <--| {a+b, a-b} - //st1 = x[i2]; x[i4] = t3 + st1; x[i7] = t3 - st1; - x[i4] = t3 + x[i2]; - x[i7] = t3 - x[i2]; - - //sumdiff3(x[i1], t1, x[i6]); // {a, b, d} <--| {a+b, b, a-b} - x[i6] = x[i1] - t1; - x[i1] += t1; - - //diffsum3_r(t4, x[i5], x[i2]); // {a, b, s} <--| {a, b-a, a+b} - x[i2] = t4 + x[i5]; - x[i5] -= t4; - } - - ix = (id << 1) - n2; - id = id << 2; - - } while (ix < n); + return peak; +}; + +/** + * Magnitude to decibels + * + * Created by Ricard Marxer on 2010-05-23. + * Copyright 2010 Ricard Marxer. All rights reserved. + * + * @param {Array} @buffer The array of magnitudes to convert to decibels + * @returns the array in decibels + * + */ +DSP.mag2db = function(buffer) { + var minDb = -120; + var minMag = Math.pow(10.0, minDb / 20.0); + + var log = Math.log; + var max = Math.max; + + var result = new Float64Array(buffer.length); + for (var i=0; i on 2010-05-23. + * Copyright 2010 Ricard Marxer. All rights reserved. + * + * Calculates the frequency response at the given points. + * + * @param {Number} b The coefficients of the filter + * @param {Number} a The coefficients of the filter + * @param {Number} w The points (normally between -PI and PI) where to calculate the frequency response + * + * @returns the frequency response in magnitude + */ +DSP.freqz = function(b, a, w) { + var i, j; + + if (!w) { + w = new Float64Array(200); + for (i=0;i this.peak) { - this.peakBand = i; - this.peak = mag; + var sqrt = Math.sqrt; + var cos = Math.cos; + var sin = Math.sin; + + for (i=0; i> 1; + + var i; + + while (limit < bufferSize) { + for (i = 0; i < limit; i++) { + this.reverseTable[i + limit] = this.reverseTable[i] + bit; } - }; - - this.loadComplete = function() { - // convert flexible js array into a fast typed array - self.samples = new Float64Array(self.samples); - self.loaded = true; - }; - - this.loadMetaData = function() { - self.duration = audio.duration; - }; - - audio.addEventListener("MozAudioAvailable", this.loadSamples, false); - audio.addEventListener("loadedmetadata", this.loadMetaData, false); - audio.addEventListener("ended", this.loadComplete, false); - audio.muted = true; - audio.src = file; - audio.play(); + + limit = limit << 1; + bit = bit >> 1; + } + + this.sinTable = new Float64Array(bufferSize); + this.cosTable = new Float64Array(bufferSize); + + for (i = 0; i < bufferSize; i++) { + this.sinTable[i] = Math.sin(-Math.PI/i); + this.cosTable[i] = Math.cos(-Math.PI/i); + } } -Sampler.prototype.applyEnvelope = function() { - this.envelope.process(this.signal); - return this.signal; +/** + * Performs a forward transform on the sample buffer. + * Converts a time domain signal to frequency domain spectra. + * + * @param {Array} buffer The sample buffer. Buffer Length must be power of 2 + * + * @returns The frequency spectrum array + */ +FFT.prototype.forward = function(buffer) { + // Locally scope variables for speed up + var bufferSize = this.bufferSize, + cosTable = this.cosTable, + sinTable = this.sinTable, + reverseTable = this.reverseTable, + real = this.real, + imag = this.imag; + + var k = Math.floor(Math.log(bufferSize) / Math.LN2); + + if (Math.pow(2, k) !== bufferSize) { throw "Invalid buffer size, must be a power of 2."; } + if (bufferSize !== buffer.length) { throw "Supplied buffer is not the same size as defined FFT. FFT Size: " + bufferSize + " Buffer Size: " + buffer.length; } + + var halfSize = 1, + phaseShiftStepReal, + phaseShiftStepImag, + currentPhaseShiftReal, + currentPhaseShiftImag, + off, + tr, + ti, + tmpReal, + i; + + for (i = 0; i < bufferSize; i++) { + real[i] = buffer[reverseTable[i]]; + imag[i] = 0; + } + + while (halfSize < bufferSize) { + //phaseShiftStepReal = Math.cos(-Math.PI/halfSize); + //phaseShiftStepImag = Math.sin(-Math.PI/halfSize); + phaseShiftStepReal = cosTable[halfSize]; + phaseShiftStepImag = sinTable[halfSize]; + + currentPhaseShiftReal = 1; + currentPhaseShiftImag = 0; + + for (var fftStep = 0; fftStep < halfSize; fftStep++) { + i = fftStep; + + while (i < bufferSize) { + off = i + halfSize; + tr = (currentPhaseShiftReal * real[off]) - (currentPhaseShiftImag * imag[off]); + ti = (currentPhaseShiftReal * imag[off]) + (currentPhaseShiftImag * real[off]); + + real[off] = real[i] - tr; + imag[off] = imag[i] - ti; + real[i] += tr; + imag[i] += ti; + + i += halfSize << 1; + } + + tmpReal = currentPhaseShiftReal; + currentPhaseShiftReal = (tmpReal * phaseShiftStepReal) - (currentPhaseShiftImag * phaseShiftStepImag); + currentPhaseShiftImag = (tmpReal * phaseShiftStepImag) + (currentPhaseShiftImag * phaseShiftStepReal); + } + + halfSize = halfSize << 1; + } + + return this.calculateSpectrum(); +}; + +/** + * Performs a inverse FFT transformation + * Converts a frequency domain spectra to a time domain signal + * + * @param {Array} real + * @param {Array} imag + * + * @returns The time domain signal + */ +FFT.prototype.inverse = function(real, imag) { + // Locally scope variables for speed up + var bufferSize = this.bufferSize, + cosTable = this.cosTable, + sinTable = this.sinTable, + reverseTable = this.reverseTable; + + real = real || this.real; + imag = imag || this.imag; + + var halfSize = 1, + phaseShiftStepReal, + phaseShiftStepImag, + currentPhaseShiftReal, + currentPhaseShiftImag, + off, + tr, + ti, + tmpReal, + i; + + for (i = 0; i < bufferSize; i++) { + imag[i] *= -1; + } + + var revReal = new Float64Array(bufferSize); + var revImag = new Float64Array(bufferSize); + + for (i = 0; i < real.length; i++) { + revReal[i] = real[reverseTable[i]]; + revImag[i] = imag[reverseTable[i]]; + } + + real = revReal; + imag = revImag; + + while (halfSize < bufferSize) { + phaseShiftStepReal = cosTable[halfSize]; + phaseShiftStepImag = sinTable[halfSize]; + currentPhaseShiftReal = 1; + currentPhaseShiftImag = 0; + + for (var fftStep = 0; fftStep < halfSize; fftStep++) { + i = fftStep; + + while (i < bufferSize) { + off = i + halfSize; + tr = (currentPhaseShiftReal * real[off]) - (currentPhaseShiftImag * imag[off]); + ti = (currentPhaseShiftReal * imag[off]) + (currentPhaseShiftImag * real[off]); + + real[off] = real[i] - tr; + imag[off] = imag[i] - ti; + real[i] += tr; + imag[i] += ti; + + i += halfSize << 1; + } + + tmpReal = currentPhaseShiftReal; + currentPhaseShiftReal = (tmpReal * phaseShiftStepReal) - (currentPhaseShiftImag * phaseShiftStepImag); + currentPhaseShiftImag = (tmpReal * phaseShiftStepImag) + (currentPhaseShiftImag * phaseShiftStepReal); + } + + halfSize = halfSize << 1; + } + + var buffer = new Float64Array(bufferSize); // this should be reused instead + for (i = 0; i < bufferSize; i++) { + buffer[i] = real[i] / bufferSize; + } + + return buffer; +}; + +module.exports = FFT; + +},{"./fourier":6}],6:[function(require,module,exports){ +/* global Float64Array */ + +/** + * A Mixin for every fourier module + * Fourier Transform Module used by DFT, FFT, RFFT + * @private + */ +module.exports = function FourierTransform(bufferSize, sampleRate) { + this.bufferSize = bufferSize; + this.sampleRate = sampleRate; + this.bandwidth = 2 / bufferSize * sampleRate / 2; + + this.spectrum = new Float64Array(bufferSize/2); + this.real = new Float64Array(bufferSize); + this.imag = new Float64Array(bufferSize); + + this.peakBand = 0; + this.peak = 0; + + /** + * Calculates the *middle* frequency of an FFT band. + * + * @param {Number} index The index of the FFT band. + * + * @returns The middle frequency in Hz. + */ + this.getBandFrequency = function(index) { + return this.bandwidth * index + this.bandwidth / 2; + }; + + /** + * Calculate the spectrum (amplitude magnitudes) + */ + this.calculateSpectrum = function() { + var spectrum = this.spectrum, + real = this.real, + imag = this.imag, + bSi = 2 / this.bufferSize, + sqrt = Math.sqrt, + rval, + ival, + mag; + + for (var i = 0, N = bufferSize/2; i < N; i++) { + rval = real[i]; + ival = imag[i]; + mag = bSi * sqrt(rval * rval + ival * ival); + + if (mag > this.peak) { + this.peakBand = i; + this.peak = mag; + } + + spectrum[i] = mag; + } + }; +}; + +},{}],7:[function(require,module,exports){ +/* global Float64Array */ +var DSP = require("./dsp"); +var Biquad = require("./biquad"); + +/** + * Create a Graphical Equalizer + * + * Implementation of a graphic equalizer with a configurable bands-per-octave + * and minimum and maximum frequencies + * + * Created by Ricard Marxer on 2010-05-23. + * Copyright 2010 Ricard Marxer. All rights reserved. + * + * @constructor + * @param {SampleRate} + * @example + * var eq = new GraphicalEq(44100) + */ +function GraphicalEq(sampleRate) { + this.FS = sampleRate; + this.minFreq = 40.0; + this.maxFreq = 16000.0; + + this.bandsPerOctave = 1.0; + + this.filters = []; + this.freqzs = []; + + this.calculateFreqzs = true; + + this.recalculateFilters = function() { + var bandCount = Math.round(Math.log(this.maxFreq/this.minFreq) * this.bandsPerOctave/ Math.LN2); + + this.filters = []; + for (var i=0; i (this.filters.length-1)) { + throw "The band index of the graphical equalizer is out of bounds."; + } + + if (!gain) { + throw "A gain must be passed."; + } + + this.filters[bandIndex].setDbGain(gain); + this.recalculateFreqz(bandIndex); + }; + + this.recalculateFreqz = function(bandIndex) { + if (!this.calculateFreqzs) { + return; + } + + if (bandIndex < 0 || bandIndex > (this.filters.length-1)) { + throw "The band index of the graphical equalizer is out of bounds. " + bandIndex + " is out of [" + 0 + ", " + this.filters.length-1 + "]"; + } + + if (!this.w) { + this.w = new Float64Array(400); + for (var i=0; i 1.0 ) { + temp = 1.0; + } else if ( temp < -1.0 ) { + temp = -1.0; + } else if ( temp != temp ) { + temp = 1; + } + + buffer[i] = temp; + */ + + if (this.envelope) { + buffer[i] = (buffer[i] * (1 - this.envelope.value())) + (this.vibraPos * this.envelope.value()); + this.envelope.samplesProcessed++; + } else { + buffer[i] = this.vibraPos; + } + } + }; +}; + +/** + * Add an envelope to the filter + * @param {ADSR} envelope + */ +IIRFilter.LP12.prototype.addEnvelope = function(envelope) { + this.envelope = envelope; +}; + +module.exports = IIRFilter; + +},{"./adsr":1,"./dsp":4}],9:[function(require,module,exports){ +/* global Float64Array */ +var ADSR = require("./adsr"); + +/** + * IIRFilter2 + * + * @constructor + * @param {Number} type + * @param {Number} cutoff + * @param {Number} resonance + * @param {Number} sampleRate + */ +function IIRFilter2(type, cutoff, resonance, sampleRate) { + this.type = type; + this.cutoff = cutoff; + this.resonance = resonance; + this.sampleRate = sampleRate; + + this.f = new Float64Array(4); + this.f[0] = 0.0; // lp + this.f[1] = 0.0; // hp + this.f[2] = 0.0; // bp + this.f[3] = 0.0; // br + + this.calcCoeff = function(cutoff, resonance) { + this.freq = 2 * Math.sin(Math.PI * Math.min(0.25, cutoff/(this.sampleRate*2))); + this.damp = Math.min(2 * (1 - Math.pow(resonance, 0.25)), Math.min(2, 2/this.freq - this.freq * 0.5)); + }; + + this.calcCoeff(cutoff, resonance); +} + +/** + * Process a buffer + * @param {Array} buffer + */ +IIRFilter2.prototype.process = function(buffer) { + var input, output; + var f = this.f; + + for ( var i = 0; i < buffer.length; i++ ) { + input = buffer[i]; + + // first pass + f[3] = input - this.damp * f[2]; + f[0] = f[0] + this.freq * f[2]; + f[1] = f[3] - f[0]; + f[2] = this.freq * f[1] + f[2]; + output = 0.5 * f[this.type]; + + // second pass + f[3] = input - this.damp * f[2]; + f[0] = f[0] + this.freq * f[2]; + f[1] = f[3] - f[0]; + f[2] = this.freq * f[1] + f[2]; + output += 0.5 * f[this.type]; + + if (this.envelope) { + buffer[i] = (buffer[i] * (1 - this.envelope.value())) + (output * this.envelope.value()); + this.envelope.samplesProcessed++; + } else { + buffer[i] = output; + } + } +}; + +/** + * Add an envelope to the filter + * @param {ADSR} envelope + */ +IIRFilter2.prototype.addEnvelope = function(envelope) { + if ( envelope instanceof ADSR ) { + this.envelope = envelope; + } else { + throw "This is not an envelope."; + } +}; + +/** + * Set filter parameters + * @param {Number} cutoff + * @param {Number} resonance + */ +IIRFilter2.prototype.set = function(cutoff, resonance) { + this.calcCoeff(cutoff, resonance); +}; + +module.exports = IIRFilter2; + +},{"./adsr":1}],10:[function(require,module,exports){ +"use strict"; + +/* + * DSP.js - a comprehensive digital signal processing library for javascript + * + * Created by Corban Brook on 2010-01-01. + * Copyright 2010 Corban Brook. All rights reserved. + * + */ +var DSPJS = { + DSP: require("./dsp"), + DFT: require("./dft"), + FFT: require("./fft"), + RFFT: require("./rfft"), + Sampler: require("./sampler"), + Oscillator: require("./oscillator"), + ADSR: require("./adsr"), + IIRFilter: require("./iir-filter"), + IIRFilter2: require("./iir-filter2"), + WindowFunction: require("./window-function"), + sinh: require("./sinh"), + Biquad: require("./biquad"), + GraphicalEq: require("./graphical-eq"), + MultiDelay: require("./multi-delay"), + SingleDelay: require("./single-delay"), + Reverb: require("./reverb") +}; + +if (typeof module === "object" && module.exports) module.exports = DSPJS; +if (typeof window !== "undefined") { + Object.keys(DSPJS).forEach(function (k) { + window[k] = DSPJS[k]; + }); +} + +},{"./adsr":1,"./biquad":2,"./dft":3,"./dsp":4,"./fft":5,"./graphical-eq":7,"./iir-filter":8,"./iir-filter2":9,"./multi-delay":11,"./oscillator":12,"./reverb":13,"./rfft":14,"./sampler":15,"./single-delay":16,"./sinh":17,"./window-function":18}],11:[function(require,module,exports){ +/* global Float64Array */ +/** + * MultiDelay effect by Almer Thie (http://code.almeros.com). + * Copyright 2010 Almer Thie. All rights reserved. + * Example: http://code.almeros.com/code-examples/delay-firefox-audio-api/ + * + * This is a delay that feeds it's own delayed signal back into its circular + * buffer. Also known as a CombFilter. + * + * Compatible with interleaved stereo (or more channel) buffers and + * non-interleaved mono buffers. + * + * @param {Number} maxDelayInSamplesSize Maximum possible delay in samples (size of circular buffer) + * @param {Number} delayInSamples Initial delay in samples + * @param {Number} masterVolume Initial master volume. Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) + * @param {Number} delayVolume Initial feedback delay volume. Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) + * + * @constructor + */ +function MultiDelay(maxDelayInSamplesSize, delayInSamples, masterVolume, delayVolume) { + this.delayBufferSamples = new Float64Array(maxDelayInSamplesSize); // The maximum size of delay + this.delayInputPointer = delayInSamples; + this.delayOutputPointer = 0; + + this.delayInSamples = delayInSamples; + this.masterVolume = masterVolume; + this.delayVolume = delayVolume; +} + +/** + * Change the delay time in samples. + * + * @param {Number} delayInSamples Delay in samples + */ +MultiDelay.prototype.setDelayInSamples = function (delayInSamples) { + this.delayInSamples = delayInSamples; + + this.delayInputPointer = this.delayOutputPointer + delayInSamples; + + if (this.delayInputPointer >= this.delayBufferSamples.length-1) { + this.delayInputPointer = this.delayInputPointer - this.delayBufferSamples.length; + } }; -Sampler.prototype.generate = function() { - var frameOffset = this.frameCount * this.bufferSize; - - var loopWidth = this.playEnd * this.samples.length - this.playStart * this.samples.length; - var playStartSamples = this.playStart * this.samples.length; // ie 0.5 -> 50% of the length - var playEndSamples = this.playEnd * this.samples.length; // ie 0.5 -> 50% of the length - var offset; +/** + * Change the master volume. + * + * @param {Number} masterVolume Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) + */ +MultiDelay.prototype.setMasterVolume = function(masterVolume) { + this.masterVolume = masterVolume; +}; - for ( var i = 0; i < this.bufferSize; i++ ) { - switch (this.loopMode) { - case DSP.OFF: - this.playhead = Math.round(this.samplesProcessed * this.step + playStartSamples); - if (this.playhead < (this.playEnd * this.samples.length) ) { - this.signal[i] = this.samples[this.playhead] * this.amplitude; - } else { - this.signal[i] = 0; - } - break; - - case DSP.FW: - this.playhead = Math.round((this.samplesProcessed * this.step) % loopWidth + playStartSamples); - if (this.playhead < (this.playEnd * this.samples.length) ) { - this.signal[i] = this.samples[this.playhead] * this.amplitude; - } - break; - - case DSP.BW: - this.playhead = playEndSamples - Math.round((this.samplesProcessed * this.step) % loopWidth); - if (this.playhead < (this.playEnd * this.samples.length) ) { - this.signal[i] = this.samples[this.playhead] * this.amplitude; - } - break; - - case DSP.FWBW: - if ( Math.floor(this.samplesProcessed * this.step / loopWidth) % 2 === 0 ) { - this.playhead = Math.round((this.samplesProcessed * this.step) % loopWidth + playStartSamples); - } else { - this.playhead = playEndSamples - Math.round((this.samplesProcessed * this.step) % loopWidth); - } - if (this.playhead < (this.playEnd * this.samples.length) ) { - this.signal[i] = this.samples[this.playhead] * this.amplitude; - } - break; +/** + * Change the delay feedback volume. + * + * @param {Number} delayVolume Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) + */ +MultiDelay.prototype.setDelayVolume = function(delayVolume) { + this.delayVolume = delayVolume; +}; + +/** + * Process a given interleaved or mono non-interleaved float value Array and adds the delayed audio. + * + * @param {Array} samples Array containing Float values or a Float64Array + * + * @returns A new Float64Array interleaved or mono non-interleaved as was fed to this function. + */ +MultiDelay.prototype.process = function(samples) { + // NB. Make a copy to put in the output samples to return. + var outputSamples = new Float64Array(samples.length); + + for (var i=0; i= this.delayBufferSamples.length-1) { + this.delayInputPointer = 0; } - this.samplesProcessed++; - } - this.frameCount++; + this.delayOutputPointer++; + if (this.delayOutputPointer >= this.delayBufferSamples.length-1) { + this.delayOutputPointer = 0; + } + } - return this.signal; + return outputSamples; }; -Sampler.prototype.setFreq = function(frequency) { - var totalProcessed = this.samplesProcessed * this.step; - this.frequency = frequency; - this.step = this.frequency / this.rootFrequency; - this.samplesProcessed = Math.round(totalProcessed/this.step); -}; +module.exports = MultiDelay; -Sampler.prototype.reset = function() { - this.samplesProcessed = 0; - this.playhead = 0; -}; +},{}],12:[function(require,module,exports){ +/* global Float64Array */ +var DSP = require("./dsp"); /** * Oscillator class for generating and modifying signals @@ -955,7 +1541,7 @@ Sampler.prototype.reset = function() { * @param {Number} bufferSize Size of the sample buffer to generate * @param {Number} sampleRate The sample rate of the signal * - * @contructor + * @constructor */ function Oscillator(type, frequency, amplitude, bufferSize, sampleRate) { this.frequency = frequency; @@ -964,7 +1550,7 @@ function Oscillator(type, frequency, amplitude, bufferSize, sampleRate) { this.sampleRate = sampleRate; //this.pulseWidth = pulseWidth; this.frameCount = 0; - + this.waveTableLength = 2048; this.cyclesPerSample = frequency / sampleRate; @@ -1001,14 +1587,14 @@ function Oscillator(type, frequency, amplitude, bufferSize, sampleRate) { } }; - if ( typeof Oscillator.waveTable === 'undefined' ) { + if ( typeof Oscillator.waveTable === "undefined" ) { Oscillator.waveTable = {}; } - if ( typeof Oscillator.waveTable[this.func] === 'undefined' ) { + if ( typeof Oscillator.waveTable[this.func] === "undefined" ) { this.generateWaveTable(); } - + this.waveTable = Oscillator.waveTable[this.func]; } @@ -1024,35 +1610,42 @@ Oscillator.prototype.setAmp = function(amplitude) { throw "Amplitude out of range (0..1)."; } }; - + /** * Set the frequency of the signal * * @param {Number} frequency The frequency of the signal - */ + */ Oscillator.prototype.setFreq = function(frequency) { this.frequency = frequency; this.cyclesPerSample = frequency / this.sampleRate; }; - -// Add an oscillator + +/** + * Add an oscillator + * @param {Oscillator} oscillator The oscillator to be added to + * @return {Array} the current oscillator signal + */ Oscillator.prototype.add = function(oscillator) { for ( var i = 0; i < this.bufferSize; i++ ) { //this.signal[i] += oscillator.valueAt(i); this.signal[i] += oscillator.signal[i]; } - + return this.signal; }; - -// Add a signal to the current generated osc signal + +/** + * Add a signal to the current generated osc signal + * @param {Array} signal + */ Oscillator.prototype.addSignal = function(signal) { for ( var i = 0; i < signal.length; i++ ) { if ( i >= this.bufferSize ) { break; } this.signal[i] += signal[i]; - + /* // Constrain amplitude if ( this.signal[i] > 1 ) { @@ -1064,20 +1657,34 @@ Oscillator.prototype.addSignal = function(signal) { } return this.signal; }; - -// Add an envelope to the oscillator + +/** + * Add an envelope to the oscillator + * @param {ADSR} envelope + */ Oscillator.prototype.addEnvelope = function(envelope) { this.envelope = envelope; }; +/** + * Apply the oscillator envelope to its signal + */ Oscillator.prototype.applyEnvelope = function() { this.envelope.process(this.signal); }; - + +/** + * Get value + * @param {Number} offset + */ Oscillator.prototype.valueAt = function(offset) { return this.waveTable[offset % this.waveTableLength]; }; - + +/** + * Generate the oscillator signal + * @return {Array} the signal + */ Oscillator.prototype.generate = function() { var frameOffset = this.frameCount * this.bufferSize; var step = this.waveTableLength * this.frequency / this.sampleRate; @@ -1087,967 +1694,671 @@ Oscillator.prototype.generate = function() { //var step = (frameOffset + i) * this.cyclesPerSample % 1; //this.signal[i] = this.func(step) * this.amplitude; //this.signal[i] = this.valueAt(Math.round((frameOffset + i) * step)) * this.amplitude; - offset = Math.round((frameOffset + i) * step); - this.signal[i] = this.waveTable[offset % this.waveTableLength] * this.amplitude; - } - - this.frameCount++; - - return this.signal; -}; - -Oscillator.Sine = function(step) { - return Math.sin(DSP.TWO_PI * step); -}; - -Oscillator.Square = function(step) { - return step < 0.5 ? 1 : -1; -}; - -Oscillator.Saw = function(step) { - return 2 * (step - Math.round(step)); -}; - -Oscillator.Triangle = function(step) { - return 1 - 4 * Math.abs(Math.round(step) - step); -}; - -Oscillator.Pulse = function(step) { - // stub -}; - -function ADSR(attackLength, decayLength, sustainLevel, sustainLength, releaseLength, sampleRate) { - this.sampleRate = sampleRate; - // Length in seconds - this.attackLength = attackLength; - this.decayLength = decayLength; - this.sustainLevel = sustainLevel; - this.sustainLength = sustainLength; - this.releaseLength = releaseLength; - this.sampleRate = sampleRate; - - // Length in samples - this.attackSamples = attackLength * sampleRate; - this.decaySamples = decayLength * sampleRate; - this.sustainSamples = sustainLength * sampleRate; - this.releaseSamples = releaseLength * sampleRate; - - // Updates the envelope sample positions - this.update = function() { - this.attack = this.attackSamples; - this.decay = this.attack + this.decaySamples; - this.sustain = this.decay + this.sustainSamples; - this.release = this.sustain + this.releaseSamples; - }; - - this.update(); - - this.samplesProcessed = 0; -} - -ADSR.prototype.noteOn = function() { - this.samplesProcessed = 0; - this.sustainSamples = this.sustainLength * this.sampleRate; - this.update(); -}; - -// Send a note off when using a sustain of infinity to let the envelope enter the release phase -ADSR.prototype.noteOff = function() { - this.sustainSamples = this.samplesProcessed - this.decaySamples; - this.update(); -}; - -ADSR.prototype.processSample = function(sample) { - var amplitude = 0; - - if ( this.samplesProcessed <= this.attack ) { - amplitude = 0 + (1 - 0) * ((this.samplesProcessed - 0) / (this.attack - 0)); - } else if ( this.samplesProcessed > this.attack && this.samplesProcessed <= this.decay ) { - amplitude = 1 + (this.sustainLevel - 1) * ((this.samplesProcessed - this.attack) / (this.decay - this.attack)); - } else if ( this.samplesProcessed > this.decay && this.samplesProcessed <= this.sustain ) { - amplitude = this.sustainLevel; - } else if ( this.samplesProcessed > this.sustain && this.samplesProcessed <= this.release ) { - amplitude = this.sustainLevel + (0 - this.sustainLevel) * ((this.samplesProcessed - this.sustain) / (this.release - this.sustain)); - } - - return sample * amplitude; -}; - -ADSR.prototype.value = function() { - var amplitude = 0; - - if ( this.samplesProcessed <= this.attack ) { - amplitude = 0 + (1 - 0) * ((this.samplesProcessed - 0) / (this.attack - 0)); - } else if ( this.samplesProcessed > this.attack && this.samplesProcessed <= this.decay ) { - amplitude = 1 + (this.sustainLevel - 1) * ((this.samplesProcessed - this.attack) / (this.decay - this.attack)); - } else if ( this.samplesProcessed > this.decay && this.samplesProcessed <= this.sustain ) { - amplitude = this.sustainLevel; - } else if ( this.samplesProcessed > this.sustain && this.samplesProcessed <= this.release ) { - amplitude = this.sustainLevel + (0 - this.sustainLevel) * ((this.samplesProcessed - this.sustain) / (this.release - this.sustain)); - } - - return amplitude; -}; - -ADSR.prototype.process = function(buffer) { - for ( var i = 0; i < buffer.length; i++ ) { - buffer[i] *= this.value(); - - this.samplesProcessed++; - } - - return buffer; -}; - - -ADSR.prototype.isActive = function() { - if ( this.samplesProcessed > this.release || this.samplesProcessed === -1 ) { - return false; - } else { - return true; - } -}; - -ADSR.prototype.disable = function() { - this.samplesProcessed = -1; -}; - -function IIRFilter(type, cutoff, resonance, sampleRate) { - this.sampleRate = sampleRate; - - switch(type) { - case DSP.LOWPASS: - case DSP.LP12: - this.func = new IIRFilter.LP12(cutoff, resonance, sampleRate); - break; + offset = Math.round((frameOffset + i) * step); + this.signal[i] = this.waveTable[offset % this.waveTableLength] * this.amplitude; } -} -IIRFilter.prototype.__defineGetter__('cutoff', - function() { - return this.func.cutoff; - } -); + this.frameCount++; -IIRFilter.prototype.__defineGetter__('resonance', - function() { - return this.func.resonance; - } -); + return this.signal; +}; -IIRFilter.prototype.set = function(cutoff, resonance) { - this.func.calcCoeff(cutoff, resonance); +Oscillator.Sine = function(step) { + return Math.sin(DSP.TWO_PI * step); }; -IIRFilter.prototype.process = function(buffer) { - this.func.process(buffer); +Oscillator.Square = function(step) { + return step < 0.5 ? 1 : -1; }; -// Add an envelope to the filter -IIRFilter.prototype.addEnvelope = function(envelope) { - if ( envelope instanceof ADSR ) { - this.func.addEnvelope(envelope); - } else { - throw "Not an envelope."; - } +Oscillator.Saw = function(step) { + return 2 * (step - Math.round(step)); }; -IIRFilter.LP12 = function(cutoff, resonance, sampleRate) { - this.sampleRate = sampleRate; - this.vibraPos = 0; - this.vibraSpeed = 0; - this.envelope = false; - - this.calcCoeff = function(cutoff, resonance) { - this.w = 2.0 * Math.PI * cutoff / this.sampleRate; - this.q = 1.0 - this.w / (2.0 * (resonance + 0.5 / (1.0 + this.w)) + this.w - 2.0); - this.r = this.q * this.q; - this.c = this.r + 1.0 - 2.0 * Math.cos(this.w) * this.q; - - this.cutoff = cutoff; - this.resonance = resonance; - }; +Oscillator.Triangle = function(step) { + return 1 - 4 * Math.abs(Math.round(step) - step); +}; - this.calcCoeff(cutoff, resonance); +Oscillator.Pulse = function() { + // stub +}; - this.process = function(buffer) { - for ( var i = 0; i < buffer.length; i++ ) { - this.vibraSpeed += (buffer[i] - this.vibraPos) * this.c; - this.vibraPos += this.vibraSpeed; - this.vibraSpeed *= this.r; - - /* - var temp = this.vibraPos; - - if ( temp > 1.0 ) { - temp = 1.0; - } else if ( temp < -1.0 ) { - temp = -1.0; - } else if ( temp != temp ) { - temp = 1; - } - - buffer[i] = temp; - */ +module.exports = Oscillator; - if (this.envelope) { - buffer[i] = (buffer[i] * (1 - this.envelope.value())) + (this.vibraPos * this.envelope.value()); - this.envelope.samplesProcessed++; - } else { - buffer[i] = this.vibraPos; - } - } - }; -}; +},{"./dsp":4}],13:[function(require,module,exports){ +/* global Float64Array */ +var DSP = require("./dsp"); +var IIRFilter2 = require("./iir-filter2"); +var SingleDelay = require("./single-delay"); +var MultiDelay = require("./multi-delay"); -IIRFilter.LP12.prototype.addEnvelope = function(envelope) { - this.envelope = envelope; -}; +/** + * Reverb effect by Almer Thie (http://code.almeros.com). + * Copyright 2010 Almer Thie. All rights reserved. + * Example: http://code.almeros.com/code-examples/reverb-firefox-audio-api/ + * + * This reverb consists of 6 SingleDelays, 6 MultiDelays and an IIRFilter2 + * for each of the two stereo channels. + * + * Compatible with interleaved stereo buffers only! + * + * @param {Number} maxDelayInSamplesSize Maximum possible delay in samples (size of circular buffers) + * @param {Number} delayInSamples Initial delay in samples for internal (Single/Multi)delays + * @param {Number} masterVolume Initial master volume. Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) + * @param {Number} mixVolume Initial reverb signal mix volume. Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) + * @param {Number} delayVolume Initial feedback delay volume for internal (Single/Multi)delays. Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) + * @param {Number} dampFrequency Initial low pass filter frequency. 0 to 44100 (depending on your maximum sampling frequency) + * + * @constructor + */ +function Reverb(maxDelayInSamplesSize, delayInSamples, masterVolume, mixVolume, delayVolume, dampFrequency) { + this.delayInSamples = delayInSamples; + this.masterVolume = masterVolume; + this.mixVolume = mixVolume; + this.delayVolume = delayVolume; + this.dampFrequency = dampFrequency; -function IIRFilter2(type, cutoff, resonance, sampleRate) { - this.type = type; - this.cutoff = cutoff; - this.resonance = resonance; - this.sampleRate = sampleRate; + this.NR_OF_MULTIDELAYS = 6; + this.NR_OF_SINGLEDELAYS = 6; - this.f = Float64Array(4); - this.f[0] = 0.0; // lp - this.f[1] = 0.0; // hp - this.f[2] = 0.0; // bp - this.f[3] = 0.0; // br - - this.calcCoeff = function(cutoff, resonance) { - this.freq = 2 * Math.sin(Math.PI * Math.min(0.25, cutoff/(this.sampleRate*2))); - this.damp = Math.min(2 * (1 - Math.pow(resonance, 0.25)), Math.min(2, 2/this.freq - this.freq * 0.5)); - }; + this.LOWPASSL = new IIRFilter2(DSP.LOWPASS, dampFrequency, 0, 44100); + this.LOWPASSR = new IIRFilter2(DSP.LOWPASS, dampFrequency, 0, 44100); - this.calcCoeff(cutoff, resonance); -} + this.singleDelays = []; -IIRFilter2.prototype.process = function(buffer) { - var input, output; - var f = this.f; + var i, delayMultiply; - for ( var i = 0; i < buffer.length; i++ ) { - input = buffer[i]; + for (i = 0; i < this.NR_OF_SINGLEDELAYS; i++) { + delayMultiply = 1.0 + (i/7.0); // 1.0, 1.1, 1.2... + this.singleDelays[i] = new SingleDelay(maxDelayInSamplesSize, Math.round(this.delayInSamples * delayMultiply), this.delayVolume); + } - // first pass - f[3] = input - this.damp * f[2]; - f[0] = f[0] + this.freq * f[2]; - f[1] = f[3] - f[0]; - f[2] = this.freq * f[1] + f[2]; - output = 0.5 * f[this.type]; + this.multiDelays = []; - // second pass - f[3] = input - this.damp * f[2]; - f[0] = f[0] + this.freq * f[2]; - f[1] = f[3] - f[0]; - f[2] = this.freq * f[1] + f[2]; - output += 0.5 * f[this.type]; + for (i = 0; i < this.NR_OF_MULTIDELAYS; i++) { + delayMultiply = 1.0 + (i/10.0); // 1.0, 1.1, 1.2... + this.multiDelays[i] = new MultiDelay(maxDelayInSamplesSize, Math.round(this.delayInSamples * delayMultiply), this.masterVolume, this.delayVolume); + } +} - if (this.envelope) { - buffer[i] = (buffer[i] * (1 - this.envelope.value())) + (output * this.envelope.value()); - this.envelope.samplesProcessed++; - } else { - buffer[i] = output; - } +/** + * Change the delay time in samples as a base for all delays. + * + * @param {Number} delayInSamples Delay in samples + */ +Reverb.prototype.setDelayInSamples = function (delayInSamples){ + this.delayInSamples = delayInSamples; + + var i, delayMultiply; + + for (i = 0; i < this.NR_OF_SINGLEDELAYS; i++) { + delayMultiply = 1.0 + (i/7.0); // 1.0, 1.1, 1.2... + this.singleDelays[i].setDelayInSamples( Math.round(this.delayInSamples * delayMultiply) ); } -}; -IIRFilter2.prototype.addEnvelope = function(envelope) { - if ( envelope instanceof ADSR ) { - this.envelope = envelope; - } else { - throw "This is not an envelope."; + for (i = 0; i < this.NR_OF_MULTIDELAYS; i++) { + delayMultiply = 1.0 + (i/10.0); // 1.0, 1.1, 1.2... + this.multiDelays[i].setDelayInSamples( Math.round(this.delayInSamples * delayMultiply) ); } }; -IIRFilter2.prototype.set = function(cutoff, resonance) { - this.calcCoeff(cutoff, resonance); +/** + * Change the master volume. + * + * @param {Number} masterVolume Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) + */ +Reverb.prototype.setMasterVolume = function (masterVolume){ + this.masterVolume = masterVolume; +}; + +/** + * Change the reverb signal mix level. + * + * @param {Number} mixVolume Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) + */ +Reverb.prototype.setMixVolume = function (mixVolume){ + this.mixVolume = mixVolume; }; +/** + * Change all delays feedback volume. + * + * @param {Number} delayVolume Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) + */ +Reverb.prototype.setDelayVolume = function (delayVolume){ + this.delayVolume = delayVolume; + var i; -function WindowFunction(type, alpha) { - this.alpha = alpha; - - switch(type) { - case DSP.BARTLETT: - this.func = WindowFunction.Bartlett; - break; - - case DSP.BARTLETTHANN: - this.func = WindowFunction.BartlettHann; - break; - - case DSP.BLACKMAN: - this.func = WindowFunction.Blackman; - this.alpha = this.alpha || 0.16; - break; - - case DSP.COSINE: - this.func = WindowFunction.Cosine; - break; - - case DSP.GAUSS: - this.func = WindowFunction.Gauss; - this.alpha = this.alpha || 0.25; - break; - - case DSP.HAMMING: - this.func = WindowFunction.Hamming; - break; - - case DSP.HANN: - this.func = WindowFunction.Hann; - break; - - case DSP.LANCZOS: - this.func = WindowFunction.Lanczoz; - break; - - case DSP.RECTANGULAR: - this.func = WindowFunction.Rectangular; - break; - - case DSP.TRIANGULAR: - this.func = WindowFunction.Triangular; - break; + for (i = 0; i on 2010-05-23. - * Copyright 2010 Ricard Marxer. All rights reserved. +},{"./dsp":4,"./iir-filter2":9,"./multi-delay":11,"./single-delay":16}],14:[function(require,module,exports){ +/* global Float64Array Uint32Array */ +var FourierTransform = require("./fourier"); + +/** + * RFFT is a class for calculating the Discrete Fourier Transform of a signal + * with the Fast Fourier Transform algorithm. + * + * This method currently only contains a forward transform but is highly optimized. * + * @param {Number} bufferSize The size of the sample buffer to be computed. Must be power of 2 + * @param {Number} sampleRate The sampleRate of the buffer (eg. 44100) + * + * @constructor */ -// Implementation based on: -// http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt -function Biquad(type, sampleRate) { - this.Fs = sampleRate; - this.type = type; // type of the filter - this.parameterType = DSP.Q; // type of the parameter - this.x_1_l = 0; - this.x_2_l = 0; - this.y_1_l = 0; - this.y_2_l = 0; +// lookup tables don't really gain us any speed, but they do increase +// cache footprint, so don't use them in here - this.x_1_r = 0; - this.x_2_r = 0; - this.y_1_r = 0; - this.y_2_r = 0; +// also we don't use sepearate arrays for real/imaginary parts - this.b0 = 1; - this.a0 = 1; +// this one a little more than twice as fast as the one in FFT +// however I only did the forward transform - this.b1 = 0; - this.a1 = 0; +// the rest of this was translated from C, see http://www.jjj.de/fxt/ +// this is the real split radix FFT - this.b2 = 0; - this.a2 = 0; +function RFFT(bufferSize, sampleRate) { + FourierTransform.call(this, bufferSize, sampleRate); - this.b0a0 = this.b0 / this.a0; - this.b1a0 = this.b1 / this.a0; - this.b2a0 = this.b2 / this.a0; - this.a1a0 = this.a1 / this.a0; - this.a2a0 = this.a2 / this.a0; + this.trans = new Float64Array(bufferSize); - this.f0 = 3000; // "wherever it's happenin', man." Center Frequency or - // Corner Frequency, or shelf midpoint frequency, depending - // on which filter type. The "significant frequency". + this.reverseTable = new Uint32Array(bufferSize); - this.dBgain = 12; // used only for peaking and shelving filters + // don't use a lookup table to do the permute, use this instead + this.reverseBinPermute = function (dest, source) { + var bufferSize = this.bufferSize, + halfSize = bufferSize >>> 1, + nm1 = bufferSize - 1, + i = 1, r = 0, h; - this.Q = 1; // the EE kind of definition, except for peakingEQ in which A*Q is - // the classic EE Q. That adjustment in definition was made so that - // a boost of N dB followed by a cut of N dB for identical Q and - // f0/Fs results in a precisely flat unity gain filter or "wire". + dest[0] = source[0]; - this.BW = -3; // the bandwidth in octaves (between -3 dB frequencies for BPF - // and notch or between midpoint (dBgain/2) gain frequencies for - // peaking EQ + do { + r += halfSize; + dest[i] = source[r]; + dest[r] = source[i]; - this.S = 1; // a "shelf slope" parameter (for shelving EQ only). When S = 1, - // the shelf slope is as steep as it can be and remain monotonically - // increasing or decreasing gain with frequency. The shelf slope, in - // dB/octave, remains proportional to S for all other values for a - // fixed f0/Fs and dBgain. + i++; - this.coefficients = function() { - var b = [this.b0, this.b1, this.b2]; - var a = [this.a0, this.a1, this.a2]; - return {b: b, a:a}; - }; + h = halfSize << 1; + while (h = h >> 1, !((r ^= h) & h)); - this.setFilterType = function(type) { - this.type = type; - this.recalculateCoefficients(); - }; + if (r >= i) { + dest[i] = source[r]; + dest[r] = source[i]; - this.setSampleRate = function(rate) { - this.Fs = rate; - this.recalculateCoefficients(); + dest[nm1-i] = source[nm1-r]; + dest[nm1-r] = source[nm1-i]; + } + i++; + } while (i < halfSize); + dest[nm1] = source[nm1]; }; - this.setQ = function(q) { - this.parameterType = DSP.Q; - this.Q = Math.max(Math.min(q, 115.0), 0.001); - this.recalculateCoefficients(); - }; + this.generateReverseTable = function () { + var bufferSize = this.bufferSize, + halfSize = bufferSize >>> 1, + nm1 = bufferSize - 1, + i = 1, r = 0, h; - this.setBW = function(bw) { - this.parameterType = DSP.BW; - this.BW = bw; - this.recalculateCoefficients(); - }; + this.reverseTable[0] = 0; - this.setS = function(s) { - this.parameterType = DSP.S; - this.S = Math.max(Math.min(s, 5.0), 0.0001); - this.recalculateCoefficients(); - }; + do { + r += halfSize; - this.setF0 = function(freq) { - this.f0 = freq; - this.recalculateCoefficients(); - }; - - this.setDbGain = function(g) { - this.dBgain = g; - this.recalculateCoefficients(); - }; + this.reverseTable[i] = r; + this.reverseTable[r] = i; - this.recalculateCoefficients = function() { - var A; - if (type === DSP.PEAKING_EQ || type === DSP.LOW_SHELF || type === DSP.HIGH_SHELF ) { - A = Math.pow(10, (this.dBgain/40)); // for peaking and shelving EQ filters only - } else { - A = Math.sqrt( Math.pow(10, (this.dBgain/20)) ); - } + i++; - var w0 = DSP.TWO_PI * this.f0 / this.Fs; + h = halfSize << 1; + while (h = h >> 1, !((r ^= h) & h)); - var cosw0 = Math.cos(w0); - var sinw0 = Math.sin(w0); + if (r >= i) { + this.reverseTable[i] = r; + this.reverseTable[r] = i; - var alpha = 0; - - switch (this.parameterType) { - case DSP.Q: - alpha = sinw0/(2*this.Q); - break; - - case DSP.BW: - alpha = sinw0 * sinh( Math.LN2/2 * this.BW * w0/sinw0 ); - break; + this.reverseTable[nm1-i] = nm1-r; + this.reverseTable[nm1-r] = nm1-i; + } + i++; + } while (i < halfSize); - case DSP.S: - alpha = sinw0/2 * Math.sqrt( (A + 1/A)*(1/this.S - 1) + 2 ); - break; - } + this.reverseTable[nm1] = nm1; + }; - /** - FYI: The relationship between bandwidth and Q is - 1/Q = 2*sinh(ln(2)/2*BW*w0/sin(w0)) (digital filter w BLT) - or 1/Q = 2*sinh(ln(2)/2*BW) (analog filter prototype) + this.generateReverseTable(); +} + + +/** + * Performs a forward transform on the sample buffer. + * Converts a time domain signal to frequency domain spectra. + * + * @param {Array} buffer The sample buffer. Buffer Length must be power of 2 + * + * @returns The frequency spectrum array + */ + +// Ordering of output: +// +// trans[0] = re[0] (==zero frequency, purely real) +// trans[1] = re[1] +// ... +// trans[n/2-1] = re[n/2-1] +// trans[n/2] = re[n/2] (==nyquist frequency, purely real) +// +// trans[n/2+1] = im[n/2-1] +// trans[n/2+2] = im[n/2-2] +// ... +// trans[n-1] = im[1] + +RFFT.prototype.forward = function(buffer) { + var n = this.bufferSize, + spectrum = this.spectrum, + x = this.trans, + TWO_PI = 2*Math.PI, + sqrt = Math.sqrt, + i = n >>> 1, + bSi = 2 / n, + n2, n4, n8, nn, + t1, t2, t3, t4, + i1, i2, i3, i4, i5, i6, i7, i8, + st1, cc1, ss1, cc3, ss3, + e, + a, + rval, ival, mag; - The relationship between shelf slope and Q is - 1/Q = sqrt((A + 1/A)*(1/S - 1) + 2) - */ + this.reverseBinPermute(x, buffer); - var coeff; + /* + var reverseTable = this.reverseTable; - switch (this.type) { - case DSP.LPF: // H(s) = 1 / (s^2 + s/Q + 1) - this.b0 = (1 - cosw0)/2; - this.b1 = 1 - cosw0; - this.b2 = (1 - cosw0)/2; - this.a0 = 1 + alpha; - this.a1 = -2 * cosw0; - this.a2 = 1 - alpha; - break; + for (var k = 0, len = reverseTable.length; k < len; k++) { + x[k] = buffer[reverseTable[k]]; + } + */ - case DSP.HPF: // H(s) = s^2 / (s^2 + s/Q + 1) - this.b0 = (1 + cosw0)/2; - this.b1 = -(1 + cosw0); - this.b2 = (1 + cosw0)/2; - this.a0 = 1 + alpha; - this.a1 = -2 * cosw0; - this.a2 = 1 - alpha; - break; + for (var ix = 0, id = 4; ix < n; id *= 4) { + for (var i0 = ix; i0 < n; i0 += id) { + //sumdiff(x[i0], x[i0+1]); // {a, b} <--| {a+b, a-b} + st1 = x[i0] - x[i0+1]; + x[i0] += x[i0+1]; + x[i0+1] = st1; + } + ix = 2*(id-1); + } - case DSP.BPF_CONSTANT_SKIRT: // H(s) = s / (s^2 + s/Q + 1) (constant skirt gain, peak gain = Q) - this.b0 = sinw0/2; - this.b1 = 0; - this.b2 = -sinw0/2; - this.a0 = 1 + alpha; - this.a1 = -2*cosw0; - this.a2 = 1 - alpha; - break; + n2 = 2; + nn = n >>> 1; - case DSP.BPF_CONSTANT_PEAK: // H(s) = (s/Q) / (s^2 + s/Q + 1) (constant 0 dB peak gain) - this.b0 = alpha; - this.b1 = 0; - this.b2 = -alpha; - this.a0 = 1 + alpha; - this.a1 = -2*cosw0; - this.a2 = 1 - alpha; - break; + while((nn = nn >>> 1)) { + ix = 0; + n2 = n2 << 1; + id = n2 << 1; + n4 = n2 >>> 2; + n8 = n2 >>> 3; + do { + if(n4 !== 1) { + for(i0 = ix; i0 < n; i0 += id) { + i1 = i0; + i2 = i1 + n4; + i3 = i2 + n4; + i4 = i3 + n4; - case DSP.NOTCH: // H(s) = (s^2 + 1) / (s^2 + s/Q + 1) - this.b0 = 1; - this.b1 = -2*cosw0; - this.b2 = 1; - this.a0 = 1 + alpha; - this.a1 = -2*cosw0; - this.a2 = 1 - alpha; - break; + //diffsum3_r(x[i3], x[i4], t1); // {a, b, s} <--| {a, b-a, a+b} + t1 = x[i3] + x[i4]; + x[i4] -= x[i3]; + //sumdiff3(x[i1], t1, x[i3]); // {a, b, d} <--| {a+b, b, a-b} + x[i3] = x[i1] - t1; + x[i1] += t1; - case DSP.APF: // H(s) = (s^2 - s/Q + 1) / (s^2 + s/Q + 1) - this.b0 = 1 - alpha; - this.b1 = -2*cosw0; - this.b2 = 1 + alpha; - this.a0 = 1 + alpha; - this.a1 = -2*cosw0; - this.a2 = 1 - alpha; - break; + i1 += n8; + i2 += n8; + i3 += n8; + i4 += n8; - case DSP.PEAKING_EQ: // H(s) = (s^2 + s*(A/Q) + 1) / (s^2 + s/(A*Q) + 1) - this.b0 = 1 + alpha*A; - this.b1 = -2*cosw0; - this.b2 = 1 - alpha*A; - this.a0 = 1 + alpha/A; - this.a1 = -2*cosw0; - this.a2 = 1 - alpha/A; - break; + //sumdiff(x[i3], x[i4], t1, t2); // {s, d} <--| {a+b, a-b} + t1 = x[i3] + x[i4]; + t2 = x[i3] - x[i4]; - case DSP.LOW_SHELF: // H(s) = A * (s^2 + (sqrt(A)/Q)*s + A)/(A*s^2 + (sqrt(A)/Q)*s + 1) - coeff = sinw0 * Math.sqrt( (A^2 + 1)*(1/this.S - 1) + 2*A ); - this.b0 = A*((A+1) - (A-1)*cosw0 + coeff); - this.b1 = 2*A*((A-1) - (A+1)*cosw0); - this.b2 = A*((A+1) - (A-1)*cosw0 - coeff); - this.a0 = (A+1) + (A-1)*cosw0 + coeff; - this.a1 = -2*((A-1) + (A+1)*cosw0); - this.a2 = (A+1) + (A-1)*cosw0 - coeff; - break; + t1 = -t1 * Math.SQRT1_2; + t2 *= Math.SQRT1_2; - case DSP.HIGH_SHELF: // H(s) = A * (A*s^2 + (sqrt(A)/Q)*s + 1)/(s^2 + (sqrt(A)/Q)*s + A) - coeff = sinw0 * Math.sqrt( (A^2 + 1)*(1/this.S - 1) + 2*A ); - this.b0 = A*((A+1) + (A-1)*cosw0 + coeff); - this.b1 = -2*A*((A-1) + (A+1)*cosw0); - this.b2 = A*((A+1) + (A-1)*cosw0 - coeff); - this.a0 = (A+1) - (A-1)*cosw0 + coeff; - this.a1 = 2*((A-1) - (A+1)*cosw0); - this.a2 = (A+1) - (A-1)*cosw0 - coeff; - break; - } - - this.b0a0 = this.b0/this.a0; - this.b1a0 = this.b1/this.a0; - this.b2a0 = this.b2/this.a0; - this.a1a0 = this.a1/this.a0; - this.a2a0 = this.a2/this.a0; - }; + // sumdiff(t1, x[i2], x[i4], x[i3]); // {s, d} <--| {a+b, a-b} + st1 = x[i2]; + x[i4] = t1 + st1; + x[i3] = t1 - st1; - this.process = function(buffer) { - //y[n] = (b0/a0)*x[n] + (b1/a0)*x[n-1] + (b2/a0)*x[n-2] - // - (a1/a0)*y[n-1] - (a2/a0)*y[n-2] + //sumdiff3(x[i1], t2, x[i2]); // {a, b, d} <--| {a+b, b, a-b} + x[i2] = x[i1] - t2; + x[i1] += t2; + } + } else { + for(i0 = ix; i0 < n; i0 += id) { + i1 = i0; + i2 = i1 + n4; + i3 = i2 + n4; + i4 = i3 + n4; - var len = buffer.length; - var output = new Float64Array(len); + //diffsum3_r(x[i3], x[i4], t1); // {a, b, s} <--| {a, b-a, a+b} + t1 = x[i3] + x[i4]; + x[i4] -= x[i3]; - for ( var i=0; i on 2010-05-23. - * Copyright 2010 Ricard Marxer. All rights reserved. - * - * @buffer array of magnitudes to convert to decibels - * - * @returns the array in decibels - * - */ -DSP.mag2db = function(buffer) { - var minDb = -120; - var minMag = Math.pow(10.0, minDb / 20.0); + i5 = i0 + n4 - j; + i6 = i5 + n4; + i7 = i6 + n4; + i8 = i7 + n4; - var log = Math.log; - var max = Math.max; - - var result = Float64Array(buffer.length); - for (var i=0; i on 2010-05-23. - * Copyright 2010 Ricard Marxer. All rights reserved. - * - * Calculates the frequency response at the given points. - * - * @b b coefficients of the filter - * @a a coefficients of the filter - * @w w points (normally between -PI and PI) where to calculate the frequency response - * - * @returns the frequency response in magnitude - * - */ -DSP.freqz = function(b, a, w) { - var i, j; + //sumdiff(t2, t4); // {a, b} <--| {a+b, a-b} + st1 = t2 - t4; + t2 += t4; + t4 = st1; + + //sumdiff(t2, x[i6], x[i8], x[i3]); // {s, d} <--| {a+b, a-b} + //st1 = x[i6]; x[i8] = t2 + st1; x[i3] = t2 - st1; + x[i8] = t2 + x[i6]; + x[i3] = t2 - x[i6]; + + //sumdiff_r(t1, t3); // {a, b} <--| {a+b, b-a} + st1 = t3 - t1; + t1 += t3; + t3 = st1; + + //sumdiff(t3, x[i2], x[i4], x[i7]); // {s, d} <--| {a+b, a-b} + //st1 = x[i2]; x[i4] = t3 + st1; x[i7] = t3 - st1; + x[i4] = t3 + x[i2]; + x[i7] = t3 - x[i2]; - if (!w) { - w = Float64Array(200); - for (i=0;i this.peak) { + this.peakBand = i; + this.peak = mag; } - - result[i] = sqrt(numerator.real*numerator.real + numerator.imag*numerator.imag) / sqrt(denominator.real*denominator.real + denominator.imag*denominator.imag); + + spectrum[i] = mag; } - return result; -}; + spectrum[0] = bSi * x[0]; -/* - * Graphical Equalizer - * - * Implementation of a graphic equalizer with a configurable bands-per-octave - * and minimum and maximum frequencies - * - * Created by Ricard Marxer on 2010-05-23. - * Copyright 2010 Ricard Marxer. All rights reserved. - * - */ -function GraphicalEq(sampleRate) { - this.FS = sampleRate; - this.minFreq = 40.0; - this.maxFreq = 16000.0; + return spectrum; +}; - this.bandsPerOctave = 1.0; +module.exports = RFFT; - this.filters = []; - this.freqzs = []; +},{"./fourier":6}],15:[function(require,module,exports){ +/* global Float64Array */ +var DSP = require("./dsp"); - this.calculateFreqzs = true; +/** + * Sampler + * + * @constructor + * @param {} file + * @param {Number} bufferSize + * @param {Number} sampleRate + * @param {Number} playStart + * @param {Number} playEnd + * @param {Number} loopStart + * @param {Number} loopEnd + * @param {Number} loopMode + */ +function Sampler(file, bufferSize, sampleRate, playStart, playEnd, loopStart, loopEnd, loopMode) { + this.file = file; + this.bufferSize = bufferSize; + this.sampleRate = sampleRate; + this.playStart = playStart || 0; // 0% + this.playEnd = playEnd || 1; // 100% + this.loopStart = loopStart || 0; + this.loopEnd = loopEnd || 1; + this.loopMode = loopMode || DSP.OFF; + this.loaded = false; + this.samples = []; + this.signal = new Float64Array(bufferSize); + this.frameCount = 0; + this.envelope = null; + this.amplitude = 1; + this.rootFrequency = 110; // A2 110 + this.frequency = 550; + this.step = this.frequency / this.rootFrequency; + this.duration = 0; + this.samplesProcessed = 0; + this.playhead = 0; - this.recalculateFilters = function() { - var bandCount = Math.round(Math.log(this.maxFreq/this.minFreq) * this.bandsPerOctave/ Math.LN2); + var audio = /* new Audio();*/ document.createElement("AUDIO"); + var self = this; - this.filters = []; - for (var i=0; i (this.filters.length-1)) { - throw "The band index of the graphical equalizer is out of bounds."; - } - - if (!gain) { - throw "A gain must be passed."; - } - - this.filters[bandIndex].setDbGain(gain); - this.recalculateFreqz(bandIndex); + this.loadMetaData = function() { + self.duration = audio.duration; }; - - this.recalculateFreqz = function(bandIndex) { - if (!this.calculateFreqzs) { - return; - } - if (bandIndex < 0 || bandIndex > (this.filters.length-1)) { - throw "The band index of the graphical equalizer is out of bounds. " + bandIndex + " is out of [" + 0 + ", " + this.filters.length-1 + "]"; - } - - if (!this.w) { - this.w = Float64Array(400); - for (var i=0; i 50% of the length + var playEndSamples = this.playEnd * this.samples.length; // ie 0.5 -> 50% of the length - for (var i = 0; i < this.filters.length; i++) { - output = this.filters[i].process(output); - } + for ( var i = 0; i < this.bufferSize; i++ ) { + switch (this.loopMode) { + case DSP.OFF: + this.playhead = Math.round(this.samplesProcessed * this.step + playStartSamples); + if (this.playhead < (this.playEnd * this.samples.length) ) { + this.signal[i] = this.samples[this.playhead] * this.amplitude; + } else { + this.signal[i] = 0; + } + break; - return output; - }; + case DSP.FW: + this.playhead = Math.round((this.samplesProcessed * this.step) % loopWidth + playStartSamples); + if (this.playhead < (this.playEnd * this.samples.length) ) { + this.signal[i] = this.samples[this.playhead] * this.amplitude; + } + break; - this.processStereo = function(buffer) { - var output = buffer; + case DSP.BW: + this.playhead = playEndSamples - Math.round((this.samplesProcessed * this.step) % loopWidth); + if (this.playhead < (this.playEnd * this.samples.length) ) { + this.signal[i] = this.samples[this.playhead] * this.amplitude; + } + break; - for (var i = 0; i < this.filters.length; i++) { - output = this.filters[i].processStereo(output); + case DSP.FWBW: + if ( Math.floor(this.samplesProcessed * this.step / loopWidth) % 2 === 0 ) { + this.playhead = Math.round((this.samplesProcessed * this.step) % loopWidth + playStartSamples); + } else { + this.playhead = playEndSamples - Math.round((this.samplesProcessed * this.step) % loopWidth); + } + if (this.playhead < (this.playEnd * this.samples.length) ) { + this.signal[i] = this.samples[this.playhead] * this.amplitude; + } + break; } - - return output; - }; -} - -/** - * MultiDelay effect by Almer Thie (http://code.almeros.com). - * Copyright 2010 Almer Thie. All rights reserved. - * Example: http://code.almeros.com/code-examples/delay-firefox-audio-api/ - * - * This is a delay that feeds it's own delayed signal back into its circular - * buffer. Also known as a CombFilter. - * - * Compatible with interleaved stereo (or more channel) buffers and - * non-interleaved mono buffers. - * - * @param {Number} maxDelayInSamplesSize Maximum possible delay in samples (size of circular buffer) - * @param {Number} delayInSamples Initial delay in samples - * @param {Number} masterVolume Initial master volume. Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) - * @param {Number} delayVolume Initial feedback delay volume. Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) - * - * @constructor - */ -function MultiDelay(maxDelayInSamplesSize, delayInSamples, masterVolume, delayVolume) { - this.delayBufferSamples = new Float64Array(maxDelayInSamplesSize); // The maximum size of delay - this.delayInputPointer = delayInSamples; - this.delayOutputPointer = 0; - - this.delayInSamples = delayInSamples; - this.masterVolume = masterVolume; - this.delayVolume = delayVolume; -} - -/** - * Change the delay time in samples. - * - * @param {Number} delayInSamples Delay in samples - */ -MultiDelay.prototype.setDelayInSamples = function (delayInSamples) { - this.delayInSamples = delayInSamples; - - this.delayInputPointer = this.delayOutputPointer + delayInSamples; - - if (this.delayInputPointer >= this.delayBufferSamples.length-1) { - this.delayInputPointer = this.delayInputPointer - this.delayBufferSamples.length; + this.samplesProcessed++; } -}; -/** - * Change the master volume. - * - * @param {Number} masterVolume Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) - */ -MultiDelay.prototype.setMasterVolume = function(masterVolume) { - this.masterVolume = masterVolume; + this.frameCount++; + + return this.signal; }; -/** - * Change the delay feedback volume. - * - * @param {Number} delayVolume Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) - */ -MultiDelay.prototype.setDelayVolume = function(delayVolume) { - this.delayVolume = delayVolume; +Sampler.prototype.setFreq = function(frequency) { + var totalProcessed = this.samplesProcessed * this.step; + this.frequency = frequency; + this.step = this.frequency / this.rootFrequency; + this.samplesProcessed = Math.round(totalProcessed/this.step); }; -/** - * Process a given interleaved or mono non-interleaved float value Array and adds the delayed audio. - * - * @param {Array} samples Array containing Float values or a Float64Array - * - * @returns A new Float64Array interleaved or mono non-interleaved as was fed to this function. - */ -MultiDelay.prototype.process = function(samples) { - // NB. Make a copy to put in the output samples to return. - var outputSamples = new Float64Array(samples.length); - - for (var i=0; i= this.delayBufferSamples.length-1) { - this.delayInputPointer = 0; - } - - this.delayOutputPointer++; - if (this.delayOutputPointer >= this.delayBufferSamples.length-1) { - this.delayOutputPointer = 0; - } - } - - return outputSamples; +Sampler.prototype.reset = function() { + this.samplesProcessed = 0; + this.playhead = 0; }; +module.exports = Sampler; + +},{"./dsp":4}],16:[function(require,module,exports){ +/* global Float64Array */ + /** * SingleDelay effect by Almer Thie (http://code.almeros.com). * Copyright 2010 Almer Thie. All rights reserved. * Example: See usage in Reverb class * - * This is a delay that does NOT feeds it's own delayed signal back into its + * This is a delay that does NOT feeds it's own delayed signal back into its * circular buffer, neither does it return the original signal. Also known as * an AllPassFilter(?). * @@ -2060,12 +2371,11 @@ MultiDelay.prototype.process = function(samples) { * * @constructor */ - function SingleDelay(maxDelayInSamplesSize, delayInSamples, delayVolume) { this.delayBufferSamples = new Float64Array(maxDelayInSamplesSize); // The maximum size of delay this.delayInputPointer = delayInSamples; this.delayOutputPointer = 0; - + this.delayInSamples = delayInSamples; this.delayVolume = delayVolume; } @@ -2080,7 +2390,7 @@ SingleDelay.prototype.setDelayInSamples = function(delayInSamples) { this.delayInputPointer = this.delayOutputPointer + delayInSamples; if (this.delayInputPointer >= this.delayBufferSamples.length-1) { - this.delayInputPointer = this.delayInputPointer - this.delayBufferSamples.length; + this.delayInputPointer = this.delayInputPointer - this.delayBufferSamples.length; } }; @@ -2109,7 +2419,7 @@ SingleDelay.prototype.process = function(samples) { // Add audio data with the delay in the delay buffer this.delayBufferSamples[this.delayInputPointer] = samples[i]; - + // delayBufferSamples could contain initial NULL's, return silence in that case var delaySample = this.delayBufferSamples[this.delayOutputPointer]; @@ -2122,201 +2432,152 @@ SingleDelay.prototype.process = function(samples) { if (this.delayInputPointer >= this.delayBufferSamples.length-1) { this.delayInputPointer = 0; } - + this.delayOutputPointer++; if (this.delayOutputPointer >= this.delayBufferSamples.length-1) { - this.delayOutputPointer = 0; - } + this.delayOutputPointer = 0; + } } - + return outputSamples; }; +module.exports = SingleDelay; + +},{}],17:[function(require,module,exports){ /** - * Reverb effect by Almer Thie (http://code.almeros.com). - * Copyright 2010 Almer Thie. All rights reserved. - * Example: http://code.almeros.com/code-examples/reverb-firefox-audio-api/ + * Returns the hyperbolic sine of the number * - * This reverb consists of 6 SingleDelays, 6 MultiDelays and an IIRFilter2 - * for each of the two stereo channels. - * - * Compatible with interleaved stereo buffers only! + * @meta version: 1004.2314 + * @meta discuss at: http://phpjs.org/functions/sinh + * @meta original by: Onno Marsman * - * @param {Number} maxDelayInSamplesSize Maximum possible delay in samples (size of circular buffers) - * @param {Number} delayInSamples Initial delay in samples for internal (Single/Multi)delays - * @param {Number} masterVolume Initial master volume. Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) - * @param {Number} mixVolume Initial reverb signal mix volume. Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) - * @param {Number} delayVolume Initial feedback delay volume for internal (Single/Multi)delays. Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) - * @param {Number} dampFrequency Initial low pass filter frequency. 0 to 44100 (depending on your maximum sampling frequency) + * @param {Number} num + * @example + * sinh(-0.9834330348825909); // => -1.1497971402636502 + */ +function sinh (arg) { + return (Math.exp(arg) - Math.exp(-arg))/2; +} + +module.exports = sinh; + +},{}],18:[function(require,module,exports){ +var DSP = require("./dsp"); + +/** + * WindowFunction * * @constructor + * @param {Number} type + * @param {Number} alpha */ -function Reverb(maxDelayInSamplesSize, delayInSamples, masterVolume, mixVolume, delayVolume, dampFrequency) { - this.delayInSamples = delayInSamples; - this.masterVolume = masterVolume; - this.mixVolume = mixVolume; - this.delayVolume = delayVolume; - this.dampFrequency = dampFrequency; - - this.NR_OF_MULTIDELAYS = 6; - this.NR_OF_SINGLEDELAYS = 6; - - this.LOWPASSL = new IIRFilter2(DSP.LOWPASS, dampFrequency, 0, 44100); - this.LOWPASSR = new IIRFilter2(DSP.LOWPASS, dampFrequency, 0, 44100); - - this.singleDelays = []; - - var i, delayMultiply; +function WindowFunction(type, alpha) { + this.alpha = alpha; - for (i = 0; i < this.NR_OF_SINGLEDELAYS; i++) { - delayMultiply = 1.0 + (i/7.0); // 1.0, 1.1, 1.2... - this.singleDelays[i] = new SingleDelay(maxDelayInSamplesSize, Math.round(this.delayInSamples * delayMultiply), this.delayVolume); - } - - this.multiDelays = []; + switch(type) { + case DSP.BARTLETT: + this.func = WindowFunction.Bartlett; + break; - for (i = 0; i < this.NR_OF_MULTIDELAYS; i++) { - delayMultiply = 1.0 + (i/10.0); // 1.0, 1.1, 1.2... - this.multiDelays[i] = new MultiDelay(maxDelayInSamplesSize, Math.round(this.delayInSamples * delayMultiply), this.masterVolume, this.delayVolume); + case DSP.BARTLETTHANN: + this.func = WindowFunction.BartlettHann; + break; + + case DSP.BLACKMAN: + this.func = WindowFunction.Blackman; + this.alpha = this.alpha || 0.16; + break; + + case DSP.COSINE: + this.func = WindowFunction.Cosine; + break; + + case DSP.GAUSS: + this.func = WindowFunction.Gauss; + this.alpha = this.alpha || 0.25; + break; + + case DSP.HAMMING: + this.func = WindowFunction.Hamming; + break; + + case DSP.HANN: + this.func = WindowFunction.Hann; + break; + + case DSP.LANCZOS: + this.func = WindowFunction.Lanczoz; + break; + + case DSP.RECTANGULAR: + this.func = WindowFunction.Rectangular; + break; + + case DSP.TRIANGULAR: + this.func = WindowFunction.Triangular; + break; } } /** - * Change the delay time in samples as a base for all delays. - * - * @param {Number} delayInSamples Delay in samples + * Process a buffer + * @param {Array} buffer */ -Reverb.prototype.setDelayInSamples = function (delayInSamples){ - this.delayInSamples = delayInSamples; - - var i, delayMultiply; - - for (i = 0; i < this.NR_OF_SINGLEDELAYS; i++) { - delayMultiply = 1.0 + (i/7.0); // 1.0, 1.1, 1.2... - this.singleDelays[i].setDelayInSamples( Math.round(this.delayInSamples * delayMultiply) ); - } - - for (i = 0; i < this.NR_OF_MULTIDELAYS; i++) { - delayMultiply = 1.0 + (i/10.0); // 1.0, 1.1, 1.2... - this.multiDelays[i].setDelayInSamples( Math.round(this.delayInSamples * delayMultiply) ); +WindowFunction.prototype.process = function(buffer) { + var length = buffer.length; + for ( var i = 0; i < length; i++ ) { + buffer[i] *= this.func(length, i, this.alpha); } + return buffer; }; -/** - * Change the master volume. - * - * @param {Number} masterVolume Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) - */ -Reverb.prototype.setMasterVolume = function (masterVolume){ - this.masterVolume = masterVolume; +WindowFunction.Bartlett = function(length, index) { + return 2 / (length - 1) * ((length - 1) / 2 - Math.abs(index - (length - 1) / 2)); }; -/** - * Change the reverb signal mix level. - * - * @param {Number} mixVolume Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) - */ -Reverb.prototype.setMixVolume = function (mixVolume){ - this.mixVolume = mixVolume; +WindowFunction.BartlettHann = function(length, index) { + return 0.62 - 0.48 * Math.abs(index / (length - 1) - 0.5) - 0.38 * Math.cos(DSP.TWO_PI * index / (length - 1)); }; -/** - * Change all delays feedback volume. - * - * @param {Number} delayVolume Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) - */ -Reverb.prototype.setDelayVolume = function (delayVolume){ - this.delayVolume = delayVolume; - - var i; +WindowFunction.Blackman = function(length, index, alpha) { + var a0 = (1 - alpha) / 2; + var a1 = 0.5; + var a2 = alpha / 2; - for (i = 0; ithis.attack&&this.samplesProcessed<=this.decay){amplitude=1+(this.sustainLevel-1)*((this.samplesProcessed-this.attack)/(this.decay-this.attack))}else if(this.samplesProcessed>this.decay&&this.samplesProcessed<=this.sustain){amplitude=this.sustainLevel}else if(this.samplesProcessed>this.sustain&&this.samplesProcessed<=this.release){amplitude=this.sustainLevel+(0-this.sustainLevel)*((this.samplesProcessed-this.sustain)/(this.release-this.sustain))}return sample*amplitude};ADSR.prototype.value=function(){var amplitude=0;if(this.samplesProcessed<=this.attack){amplitude=0+(1-0)*((this.samplesProcessed-0)/(this.attack-0))}else if(this.samplesProcessed>this.attack&&this.samplesProcessed<=this.decay){amplitude=1+(this.sustainLevel-1)*((this.samplesProcessed-this.attack)/(this.decay-this.attack))}else if(this.samplesProcessed>this.decay&&this.samplesProcessed<=this.sustain){amplitude=this.sustainLevel}else if(this.samplesProcessed>this.sustain&&this.samplesProcessed<=this.release){amplitude=this.sustainLevel+(0-this.sustainLevel)*((this.samplesProcessed-this.sustain)/(this.release-this.sustain))}return amplitude};ADSR.prototype.process=function(buffer){for(var i=0;ithis.release||this.samplesProcessed===-1){return false}else{return true}};ADSR.prototype.disable=function(){this.samplesProcessed=-1};module.exports=ADSR},{}],2:[function(require,module,exports){var DSP=require("./dsp");var sinh=require("./sinh");function Biquad(type,sampleRate){this.Fs=sampleRate;this.type=type;this.parameterType=DSP.Q;this.x_1_l=0;this.x_2_l=0;this.y_1_l=0;this.y_2_l=0;this.x_1_r=0;this.x_2_r=0;this.y_1_r=0;this.y_2_r=0;this.b0=1;this.a0=1;this.b1=0;this.a1=0;this.b2=0;this.a2=0;this.b0a0=this.b0/this.a0;this.b1a0=this.b1/this.a0;this.b2a0=this.b2/this.a0;this.a1a0=this.a1/this.a0;this.a2a0=this.a2/this.a0;this.f0=3e3;this.dBgain=12;this.Q=1;this.BW=-3;this.S=1;this.coefficients=function(){var b=[this.b0,this.b1,this.b2];var a=[this.a0,this.a1,this.a2];return{b:b,a:a}};this.setFilterType=function(type){this.type=type;this.recalculateCoefficients()};this.setSampleRate=function(rate){this.Fs=rate;this.recalculateCoefficients()};this.setQ=function(q){this.parameterType=DSP.Q;this.Q=Math.max(Math.min(q,115),.001);this.recalculateCoefficients()};this.setBW=function(bw){this.parameterType=DSP.BW;this.BW=bw;this.recalculateCoefficients()};this.setS=function(s){this.parameterType=DSP.S;this.S=Math.max(Math.min(s,5),1e-4);this.recalculateCoefficients()};this.setF0=function(freq){this.f0=freq;this.recalculateCoefficients()};this.setDbGain=function(g){this.dBgain=g;this.recalculateCoefficients()};this.recalculateCoefficients=function(){var A;if(type===DSP.PEAKING_EQ||type===DSP.LOW_SHELF||type===DSP.HIGH_SHELF){A=Math.pow(10,this.dBgain/40)}else{A=Math.sqrt(Math.pow(10,this.dBgain/20))}var w0=DSP.TWO_PI*this.f0/this.Fs;var cosw0=Math.cos(w0);var sinw0=Math.sin(w0);var alpha=0;switch(this.parameterType){case DSP.Q:alpha=sinw0/(2*this.Q);break;case DSP.BW:alpha=sinw0*sinh(Math.LN2/2*this.BW*w0/sinw0);break;case DSP.S:alpha=sinw0/2*Math.sqrt((A+1/A)*(1/this.S-1)+2);break}var coeff;switch(this.type){case DSP.LPF:this.b0=(1-cosw0)/2;this.b1=1-cosw0;this.b2=(1-cosw0)/2;this.a0=1+alpha;this.a1=-2*cosw0;this.a2=1-alpha;break;case DSP.HPF:this.b0=(1+cosw0)/2;this.b1=-(1+cosw0);this.b2=(1+cosw0)/2;this.a0=1+alpha;this.a1=-2*cosw0;this.a2=1-alpha;break;case DSP.BPF_CONSTANT_SKIRT:this.b0=sinw0/2;this.b1=0;this.b2=-sinw0/2;this.a0=1+alpha;this.a1=-2*cosw0;this.a2=1-alpha;break;case DSP.BPF_CONSTANT_PEAK:this.b0=alpha;this.b1=0;this.b2=-alpha;this.a0=1+alpha;this.a1=-2*cosw0;this.a2=1-alpha;break;case DSP.NOTCH:this.b0=1;this.b1=-2*cosw0;this.b2=1;this.a0=1+alpha;this.a1=-2*cosw0;this.a2=1-alpha;break;case DSP.APF:this.b0=1-alpha;this.b1=-2*cosw0;this.b2=1+alpha;this.a0=1+alpha;this.a1=-2*cosw0;this.a2=1-alpha;break;case DSP.PEAKING_EQ:this.b0=1+alpha*A;this.b1=-2*cosw0;this.b2=1-alpha*A;this.a0=1+alpha/A;this.a1=-2*cosw0;this.a2=1-alpha/A;break;case DSP.LOW_SHELF:coeff=sinw0*Math.sqrt((A^2+1)*(1/this.S-1)+2*A);this.b0=A*(A+1-(A-1)*cosw0+coeff);this.b1=2*A*(A-1-(A+1)*cosw0);this.b2=A*(A+1-(A-1)*cosw0-coeff);this.a0=A+1+(A-1)*cosw0+coeff;this.a1=-2*(A-1+(A+1)*cosw0);this.a2=A+1+(A-1)*cosw0-coeff;break;case DSP.HIGH_SHELF:coeff=sinw0*Math.sqrt((A^2+1)*(1/this.S-1)+2*A);this.b0=A*(A+1+(A-1)*cosw0+coeff);this.b1=-2*A*(A-1+(A+1)*cosw0);this.b2=A*(A+1+(A-1)*cosw0-coeff);this.a0=A+1-(A-1)*cosw0+coeff;this.a1=2*(A-1-(A+1)*cosw0);this.a2=A+1-(A-1)*cosw0-coeff;break}this.b0a0=this.b0/this.a0;this.b1a0=this.b1/this.a0;this.b2a0=this.b2/this.a0;this.a1a0=this.a1/this.a0;this.a2a0=this.a2/this.a0};this.process=function(buffer){var len=buffer.length;var output=new Float64Array(len);for(var i=0;ipeak?Math.abs(buffer[i]):peak}return peak};DSP.mag2db=function(buffer){var minDb=-120;var minMag=Math.pow(10,minDb/20);var log=Math.log;var max=Math.max;var result=new Float64Array(buffer.length);for(var i=0;i>1;var i;while(limit>1}this.sinTable=new Float64Array(bufferSize);this.cosTable=new Float64Array(bufferSize);for(i=0;ithis.peak){this.peakBand=i;this.peak=mag}spectrum[i]=mag}}}},{}],7:[function(require,module,exports){var DSP=require("./dsp");var Biquad=require("./biquad");function GraphicalEq(sampleRate){this.FS=sampleRate;this.minFreq=40;this.maxFreq=16e3;this.bandsPerOctave=1;this.filters=[];this.freqzs=[];this.calculateFreqzs=true;this.recalculateFilters=function(){var bandCount=Math.round(Math.log(this.maxFreq/this.minFreq)*this.bandsPerOctave/Math.LN2);this.filters=[];for(var i=0;ithis.filters.length-1){throw"The band index of the graphical equalizer is out of bounds."}if(!gain){throw"A gain must be passed."}this.filters[bandIndex].setDbGain(gain);this.recalculateFreqz(bandIndex)};this.recalculateFreqz=function(bandIndex){if(!this.calculateFreqzs){return}if(bandIndex<0||bandIndex>this.filters.length-1){throw"The band index of the graphical equalizer is out of bounds. "+bandIndex+" is out of ["+0+", "+this.filters.length-1+"]"}if(!this.w){this.w=new Float64Array(400);for(var i=0;i=this.delayBufferSamples.length-1){this.delayInputPointer=this.delayInputPointer-this.delayBufferSamples.length}};MultiDelay.prototype.setMasterVolume=function(masterVolume){this.masterVolume=masterVolume};MultiDelay.prototype.setDelayVolume=function(delayVolume){this.delayVolume=delayVolume};MultiDelay.prototype.process=function(samples){var outputSamples=new Float64Array(samples.length);for(var i=0;i=this.delayBufferSamples.length-1){this.delayInputPointer=0}this.delayOutputPointer++;if(this.delayOutputPointer>=this.delayBufferSamples.length-1){this.delayOutputPointer=0}}return outputSamples};module.exports=MultiDelay},{}],12:[function(require,module,exports){var DSP=require("./dsp");function Oscillator(type,frequency,amplitude,bufferSize,sampleRate){this.frequency=frequency;this.amplitude=amplitude;this.bufferSize=bufferSize;this.sampleRate=sampleRate;this.frameCount=0;this.waveTableLength=2048;this.cyclesPerSample=frequency/sampleRate;this.signal=new Float64Array(bufferSize);this.envelope=null;switch(parseInt(type,10)){case DSP.TRIANGLE:this.func=Oscillator.Triangle;break;case DSP.SAW:this.func=Oscillator.Saw;break;case DSP.SQUARE:this.func=Oscillator.Square;break;default:case DSP.SINE:this.func=Oscillator.Sine;break}this.generateWaveTable=function(){Oscillator.waveTable[this.func]=new Float64Array(2048);var waveTableTime=this.waveTableLength/this.sampleRate;var waveTableHz=1/waveTableTime;for(var i=0;i=0&&litude<=1){this.amplitude=amplitude}else{throw"Amplitude out of range (0..1)."}};Oscillator.prototype.setFreq=function(frequency){this.frequency=frequency;this.cyclesPerSample=frequency/this.sampleRate};Oscillator.prototype.add=function(oscillator){for(var i=0;i=this.bufferSize){break}this.signal[i]+=signal[i]}return this.signal};Oscillator.prototype.addEnvelope=function(envelope){this.envelope=envelope};Oscillator.prototype.applyEnvelope=function(){this.envelope.process(this.signal)};Oscillator.prototype.valueAt=function(offset){return this.waveTable[offset%this.waveTableLength]};Oscillator.prototype.generate=function(){var frameOffset=this.frameCount*this.bufferSize;var step=this.waveTableLength*this.frequency/this.sampleRate;var offset;for(var i=0;i>>1,nm1=bufferSize-1,i=1,r=0,h;dest[0]=source[0];do{r+=halfSize;dest[i]=source[r];dest[r]=source[i];i++;h=halfSize<<1;while(h=h>>1,!((r^=h)&h));if(r>=i){dest[i]=source[r];dest[r]=source[i];dest[nm1-i]=source[nm1-r];dest[nm1-r]=source[nm1-i]}i++}while(i>>1,nm1=bufferSize-1,i=1,r=0,h;this.reverseTable[0]=0;do{r+=halfSize;this.reverseTable[i]=r;this.reverseTable[r]=i;i++;h=halfSize<<1;while(h=h>>1,!((r^=h)&h));if(r>=i){this.reverseTable[i]=r;this.reverseTable[r]=i;this.reverseTable[nm1-i]=nm1-r;this.reverseTable[nm1-r]=nm1-i}i++}while(i>>1,bSi=2/n,n2,n4,n8,nn,t1,t2,t3,t4,i1,i2,i3,i4,i5,i6,i7,i8,st1,cc1,ss1,cc3,ss3,e,a,rval,ival,mag;this.reverseBinPermute(x,buffer);for(var ix=0,id=4;ix>>1;while(nn=nn>>>1){ix=0;n2=n2<<1;id=n2<<1;n4=n2>>>2;n8=n2>>>3;do{if(n4!==1){for(i0=ix;i0this.peak){this.peakBand=i;this.peak=mag}spectrum[i]=mag}spectrum[0]=bSi*x[0];return spectrum};module.exports=RFFT},{"./fourier":6}],15:[function(require,module,exports){var DSP=require("./dsp");function Sampler(file,bufferSize,sampleRate,playStart,playEnd,loopStart,loopEnd,loopMode){this.file=file;this.bufferSize=bufferSize;this.sampleRate=sampleRate;this.playStart=playStart||0;this.playEnd=playEnd||1; + +this.loopStart=loopStart||0;this.loopEnd=loopEnd||1;this.loopMode=loopMode||DSP.OFF;this.loaded=false;this.samples=[];this.signal=new Float64Array(bufferSize);this.frameCount=0;this.envelope=null;this.amplitude=1;this.rootFrequency=110;this.frequency=550;this.step=this.frequency/this.rootFrequency;this.duration=0;this.samplesProcessed=0;this.playhead=0;var audio=document.createElement("AUDIO");var self=this;this.loadSamples=function(event){var buffer=DSP.getChannel(DSP.MIX,event.frameBuffer);for(var i=0;i=this.delayBufferSamples.length-1){this.delayInputPointer=this.delayInputPointer-this.delayBufferSamples.length}};SingleDelay.prototype.setDelayVolume=function(delayVolume){this.delayVolume=delayVolume};SingleDelay.prototype.process=function(samples){var outputSamples=new Float64Array(samples.length);for(var i=0;i=this.delayBufferSamples.length-1){this.delayInputPointer=0}this.delayOutputPointer++;if(this.delayOutputPointer>=this.delayBufferSamples.length-1){this.delayOutputPointer=0}}return outputSamples};module.exports=SingleDelay},{}],17:[function(require,module,exports){function sinh(arg){return(Math.exp(arg)-Math.exp(-arg))/2}module.exports=sinh},{}],18:[function(require,module,exports){var DSP=require("./dsp");function WindowFunction(type,alpha){this.alpha=alpha;switch(type){case DSP.BARTLETT:this.func=WindowFunction.Bartlett;break;case DSP.BARTLETTHANN:this.func=WindowFunction.BartlettHann;break;case DSP.BLACKMAN:this.func=WindowFunction.Blackman;this.alpha=this.alpha||.16;break;case DSP.COSINE:this.func=WindowFunction.Cosine;break;case DSP.GAUSS:this.func=WindowFunction.Gauss;this.alpha=this.alpha||.25;break;case DSP.HAMMING:this.func=WindowFunction.Hamming;break;case DSP.HANN:this.func=WindowFunction.Hann;break;case DSP.LANCZOS:this.func=WindowFunction.Lanczoz;break;case DSP.RECTANGULAR:this.func=WindowFunction.Rectangular;break;case DSP.TRIANGULAR:this.func=WindowFunction.Triangular;break}}WindowFunction.prototype.process=function(buffer){var length=buffer.length;for(var i=0;i +
ADSR
+
+
Biquad
+
+
DFT
+
+
FFT
+
+
GraphicalEq
+
+
IIRFilter
+
+
IIRFilter2
+
+
MultiDelay
+
+
Oscillator
+
+
Reverb
+
+
RFFT
+
+
Sampler
+
+
SingleDelay
+
+
WindowFunction
+
+ + +## Members + +
+
DSP
+

DSP is an object which contains general purpose utility functions and constants

+
+
+ +## Functions + +
+
sinh(num)
+

Returns the hyperbolic sine of the number

+
+
+ + + +## ADSR +**Kind**: global class + +* [ADSR](#ADSR) + * [new ADSR(attack, decay, sustain, release, sampleRate)](#new_ADSR_new) + * [.noteOn()](#ADSR+noteOn) + * [.noteOff()](#ADSR+noteOff) + * [.processSample()](#ADSR+processSample) + * [.value()](#ADSR+value) ⇒ Number + * [.process(buffer)](#ADSR+process) + * [.isActive()](#ADSR+isActive) ⇒ Boolean + * [.disable()](#ADSR+disable) + + + +### new ADSR(attack, decay, sustain, release, sampleRate) +ADSR Envelope + + +| Param | Type | Description | +| --- | --- | --- | +| attack | Number | The attack length in seconds | +| decay | Number | The decay length in seconds | +| sustain | Number | The sustain level | +| release | Number | The release length in seconds | +| sampleRate | Number | The the sample rate | + + + +### adsR.noteOn() +Start the envelope + +**Kind**: instance method of [ADSR](#ADSR) + + +### adsR.noteOff() +Stop the envelope + +Send a note off when using a sustain of infinity to let the envelope enter +the release phase + +**Kind**: instance method of [ADSR](#ADSR) + + +### adsR.processSample() +Process sample + +**Kind**: instance method of [ADSR](#ADSR) + + +### adsR.value() ⇒ Number +Get current value + +**Kind**: instance method of [ADSR](#ADSR) +**Returns**: Number - amplitude + + +### adsR.process(buffer) +Process a buffer + +**Kind**: instance method of [ADSR](#ADSR) + +| Param | Type | +| --- | --- | +| buffer | Array | + + + +### adsR.isActive() ⇒ Boolean +Test if the envelope is active + +**Kind**: instance method of [ADSR](#ADSR) + + +### adsR.disable() +Disable the envelope + +**Kind**: instance method of [ADSR](#ADSR) + + +## Biquad +**Kind**: global class + + +### new Biquad(type, sampleRate) +Biquad filter + +Created by Ricard Marxer on 2010-05-23. + Copyright 2010 Ricard Marxer. All rights reserved. + +Implementation based on: +http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt + + +| Param | Type | +| --- | --- | +| type | Number | +| sampleRate | Number | + + + +## DFT +**Kind**: global class + +* [DFT](#DFT) + * [new DFT(bufferSize, sampleRate)](#new_DFT_new) + * [.forward(buffer)](#DFT+forward) ⇒ + + + +### new DFT(bufferSize, sampleRate) +DFT is a class for calculating the Discrete Fourier Transform of a signal. + + +| Param | Type | Description | +| --- | --- | --- | +| bufferSize | Number | The size of the sample buffer to be computed | +| sampleRate | Number | The sampleRate of the buffer (eg. 44100) | + + + +### dfT.forward(buffer) ⇒ +Performs a forward transform on the sample buffer. +Converts a time domain signal to frequency domain spectra. + +**Kind**: instance method of [DFT](#DFT) +**Returns**: The frequency spectrum array + +| Param | Type | Description | +| --- | --- | --- | +| buffer | Array | The sample buffer | + + + +## FFT +**Kind**: global class + +* [FFT](#FFT) + * [new FFT(bufferSize, sampleRate)](#new_FFT_new) + * [.forward(buffer)](#FFT+forward) ⇒ + * [.inverse(real, imag)](#FFT+inverse) ⇒ + + + +### new FFT(bufferSize, sampleRate) +FFT is a class for calculating the Discrete Fourier Transform of a signal +with the Fast Fourier Transform algorithm. + + +| Param | Type | Description | +| --- | --- | --- | +| bufferSize | Number | The size of the sample buffer to be computed. Must be power of 2 | +| sampleRate | Number | The sampleRate of the buffer (eg. 44100) | + + + +### ffT.forward(buffer) ⇒ +Performs a forward transform on the sample buffer. +Converts a time domain signal to frequency domain spectra. + +**Kind**: instance method of [FFT](#FFT) +**Returns**: The frequency spectrum array + +| Param | Type | Description | +| --- | --- | --- | +| buffer | Array | The sample buffer. Buffer Length must be power of 2 | + + + +### ffT.inverse(real, imag) ⇒ +Performs a inverse FFT transformation +Converts a frequency domain spectra to a time domain signal + +**Kind**: instance method of [FFT](#FFT) +**Returns**: The time domain signal + +| Param | Type | +| --- | --- | +| real | Array | +| imag | Array | + + + +## GraphicalEq +**Kind**: global class + + +### new GraphicalEq(sampleRate) +Create a Graphical Equalizer + + Implementation of a graphic equalizer with a configurable bands-per-octave + and minimum and maximum frequencies + + Created by Ricard Marxer on 2010-05-23. + Copyright 2010 Ricard Marxer. All rights reserved. + + +| Param | Type | +| --- | --- | +| sampleRate | SampleRate | + +**Example** +```js +var eq = new GraphicalEq(44100) +``` + + +## IIRFilter +**Kind**: global class + +* [IIRFilter](#IIRFilter) + * [new IIRFilter()](#new_IIRFilter_new) + * _instance_ + * [.set(cutoff, resonance)](#IIRFilter+set) + * [.process(buffer)](#IIRFilter+process) + * [.addEnvelope(envelope)](#IIRFilter+addEnvelope) + * _static_ + * [.LP12](#IIRFilter.LP12) + * [new IIRFilter.LP12()](#new_IIRFilter.LP12_new) + * [.addEnvelope(envelope)](#IIRFilter.LP12+addEnvelope) + + + +### new IIRFilter() +IIRFilter + + + +### iirFilter.set(cutoff, resonance) +Set filter parameters + +**Kind**: instance method of [IIRFilter](#IIRFilter) + +| Param | Type | +| --- | --- | +| cutoff | Number | +| resonance | Number | + + + +### iirFilter.process(buffer) +Process a buffer + +**Kind**: instance method of [IIRFilter](#IIRFilter) + +| Param | Type | +| --- | --- | +| buffer | Array | + + + +### iirFilter.addEnvelope(envelope) +Add an envelope to the filter + +**Kind**: instance method of [IIRFilter](#IIRFilter) + +| Param | Type | +| --- | --- | +| envelope | [ADSR](#ADSR) | + + + +### IIRFilter.LP12 +**Kind**: static class of [IIRFilter](#IIRFilter) + +* [.LP12](#IIRFilter.LP12) + * [new IIRFilter.LP12()](#new_IIRFilter.LP12_new) + * [.addEnvelope(envelope)](#IIRFilter.LP12+addEnvelope) + + + +#### new IIRFilter.LP12() +LP12 filter + + + +#### lP12.addEnvelope(envelope) +Add an envelope to the filter + +**Kind**: instance method of [LP12](#IIRFilter.LP12) + +| Param | Type | +| --- | --- | +| envelope | [ADSR](#ADSR) | + + + +## IIRFilter2 +**Kind**: global class + +* [IIRFilter2](#IIRFilter2) + * [new IIRFilter2(type, cutoff, resonance, sampleRate)](#new_IIRFilter2_new) + * [.process(buffer)](#IIRFilter2+process) + * [.addEnvelope(envelope)](#IIRFilter2+addEnvelope) + * [.set(cutoff, resonance)](#IIRFilter2+set) + + + +### new IIRFilter2(type, cutoff, resonance, sampleRate) +IIRFilter2 + + +| Param | Type | +| --- | --- | +| type | Number | +| cutoff | Number | +| resonance | Number | +| sampleRate | Number | + + + +### iirFilter2.process(buffer) +Process a buffer + +**Kind**: instance method of [IIRFilter2](#IIRFilter2) + +| Param | Type | +| --- | --- | +| buffer | Array | + + + +### iirFilter2.addEnvelope(envelope) +Add an envelope to the filter + +**Kind**: instance method of [IIRFilter2](#IIRFilter2) + +| Param | Type | +| --- | --- | +| envelope | [ADSR](#ADSR) | + + + +### iirFilter2.set(cutoff, resonance) +Set filter parameters + +**Kind**: instance method of [IIRFilter2](#IIRFilter2) + +| Param | Type | +| --- | --- | +| cutoff | Number | +| resonance | Number | + + + +## MultiDelay +**Kind**: global class + +* [MultiDelay](#MultiDelay) + * [new MultiDelay(maxDelayInSamplesSize, delayInSamples, masterVolume, delayVolume)](#new_MultiDelay_new) + * [.setDelayInSamples(delayInSamples)](#MultiDelay+setDelayInSamples) + * [.setMasterVolume(masterVolume)](#MultiDelay+setMasterVolume) + * [.setDelayVolume(delayVolume)](#MultiDelay+setDelayVolume) + * [.process(samples)](#MultiDelay+process) ⇒ + + + +### new MultiDelay(maxDelayInSamplesSize, delayInSamples, masterVolume, delayVolume) +MultiDelay effect by Almer Thie (http://code.almeros.com). +Copyright 2010 Almer Thie. All rights reserved. +Example: http://code.almeros.com/code-examples/delay-firefox-audio-api/ + +This is a delay that feeds it's own delayed signal back into its circular +buffer. Also known as a CombFilter. + +Compatible with interleaved stereo (or more channel) buffers and +non-interleaved mono buffers. + + +| Param | Type | Description | +| --- | --- | --- | +| maxDelayInSamplesSize | Number | Maximum possible delay in samples (size of circular buffer) | +| delayInSamples | Number | Initial delay in samples | +| masterVolume | Number | Initial master volume. Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) | +| delayVolume | Number | Initial feedback delay volume. Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) | + + + +### multiDelay.setDelayInSamples(delayInSamples) +Change the delay time in samples. + +**Kind**: instance method of [MultiDelay](#MultiDelay) + +| Param | Type | Description | +| --- | --- | --- | +| delayInSamples | Number | Delay in samples | + + + +### multiDelay.setMasterVolume(masterVolume) +Change the master volume. + +**Kind**: instance method of [MultiDelay](#MultiDelay) + +| Param | Type | Description | +| --- | --- | --- | +| masterVolume | Number | Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) | + + + +### multiDelay.setDelayVolume(delayVolume) +Change the delay feedback volume. + +**Kind**: instance method of [MultiDelay](#MultiDelay) + +| Param | Type | Description | +| --- | --- | --- | +| delayVolume | Number | Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) | + + + +### multiDelay.process(samples) ⇒ +Process a given interleaved or mono non-interleaved float value Array and adds the delayed audio. + +**Kind**: instance method of [MultiDelay](#MultiDelay) +**Returns**: A new Float64Array interleaved or mono non-interleaved as was fed to this function. + +| Param | Type | Description | +| --- | --- | --- | +| samples | Array | Array containing Float values or a Float64Array | + + + +## Oscillator +**Kind**: global class + +* [Oscillator](#Oscillator) + * [new Oscillator(type, frequency, amplitude, bufferSize, sampleRate)](#new_Oscillator_new) + * [.setAmp(amplitude)](#Oscillator+setAmp) + * [.setFreq(frequency)](#Oscillator+setFreq) + * [.add(oscillator)](#Oscillator+add) ⇒ Array + * [.addSignal(signal)](#Oscillator+addSignal) + * [.addEnvelope(envelope)](#Oscillator+addEnvelope) + * [.applyEnvelope()](#Oscillator+applyEnvelope) + * [.valueAt(offset)](#Oscillator+valueAt) + * [.generate()](#Oscillator+generate) ⇒ Array + + + +### new Oscillator(type, frequency, amplitude, bufferSize, sampleRate) +Oscillator class for generating and modifying signals + + +| Param | Type | Description | +| --- | --- | --- | +| type | Number | A waveform constant (eg. DSP.SINE) | +| frequency | Number | Initial frequency of the signal | +| amplitude | Number | Initial amplitude of the signal | +| bufferSize | Number | Size of the sample buffer to generate | +| sampleRate | Number | The sample rate of the signal | + + + +### oscillator.setAmp(amplitude) +Set the amplitude of the signal + +**Kind**: instance method of [Oscillator](#Oscillator) + +| Param | Type | Description | +| --- | --- | --- | +| amplitude | Number | The amplitude of the signal (between 0 and 1) | + + + +### oscillator.setFreq(frequency) +Set the frequency of the signal + +**Kind**: instance method of [Oscillator](#Oscillator) + +| Param | Type | Description | +| --- | --- | --- | +| frequency | Number | The frequency of the signal | + + + +### oscillator.add(oscillator) ⇒ Array +Add an oscillator + +**Kind**: instance method of [Oscillator](#Oscillator) +**Returns**: Array - the current oscillator signal + +| Param | Type | Description | +| --- | --- | --- | +| oscillator | [Oscillator](#Oscillator) | The oscillator to be added to | + + + +### oscillator.addSignal(signal) +Add a signal to the current generated osc signal + +**Kind**: instance method of [Oscillator](#Oscillator) + +| Param | Type | +| --- | --- | +| signal | Array | + + + +### oscillator.addEnvelope(envelope) +Add an envelope to the oscillator + +**Kind**: instance method of [Oscillator](#Oscillator) + +| Param | Type | +| --- | --- | +| envelope | [ADSR](#ADSR) | + + + +### oscillator.applyEnvelope() +Apply the oscillator envelope to its signal + +**Kind**: instance method of [Oscillator](#Oscillator) + + +### oscillator.valueAt(offset) +Get value + +**Kind**: instance method of [Oscillator](#Oscillator) + +| Param | Type | +| --- | --- | +| offset | Number | + + + +### oscillator.generate() ⇒ Array +Generate the oscillator signal + +**Kind**: instance method of [Oscillator](#Oscillator) +**Returns**: Array - the signal + + +## Reverb +**Kind**: global class + +* [Reverb](#Reverb) + * [new Reverb(maxDelayInSamplesSize, delayInSamples, masterVolume, mixVolume, delayVolume, dampFrequency)](#new_Reverb_new) + * [.setDelayInSamples(delayInSamples)](#Reverb+setDelayInSamples) + * [.setMasterVolume(masterVolume)](#Reverb+setMasterVolume) + * [.setMixVolume(mixVolume)](#Reverb+setMixVolume) + * [.setDelayVolume(delayVolume)](#Reverb+setDelayVolume) + * [.setDampFrequency(dampFrequency)](#Reverb+setDampFrequency) + * [.process(samples)](#Reverb+process) ⇒ + + + +### new Reverb(maxDelayInSamplesSize, delayInSamples, masterVolume, mixVolume, delayVolume, dampFrequency) +Reverb effect by Almer Thie (http://code.almeros.com). +Copyright 2010 Almer Thie. All rights reserved. +Example: http://code.almeros.com/code-examples/reverb-firefox-audio-api/ + +This reverb consists of 6 SingleDelays, 6 MultiDelays and an IIRFilter2 +for each of the two stereo channels. + +Compatible with interleaved stereo buffers only! + + +| Param | Type | Description | +| --- | --- | --- | +| maxDelayInSamplesSize | Number | Maximum possible delay in samples (size of circular buffers) | +| delayInSamples | Number | Initial delay in samples for internal (Single/Multi)delays | +| masterVolume | Number | Initial master volume. Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) | +| mixVolume | Number | Initial reverb signal mix volume. Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) | +| delayVolume | Number | Initial feedback delay volume for internal (Single/Multi)delays. Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) | +| dampFrequency | Number | Initial low pass filter frequency. 0 to 44100 (depending on your maximum sampling frequency) | + + + +### reverb.setDelayInSamples(delayInSamples) +Change the delay time in samples as a base for all delays. + +**Kind**: instance method of [Reverb](#Reverb) + +| Param | Type | Description | +| --- | --- | --- | +| delayInSamples | Number | Delay in samples | + + + +### reverb.setMasterVolume(masterVolume) +Change the master volume. + +**Kind**: instance method of [Reverb](#Reverb) + +| Param | Type | Description | +| --- | --- | --- | +| masterVolume | Number | Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) | + + + +### reverb.setMixVolume(mixVolume) +Change the reverb signal mix level. + +**Kind**: instance method of [Reverb](#Reverb) + +| Param | Type | Description | +| --- | --- | --- | +| mixVolume | Number | Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) | + + + +### reverb.setDelayVolume(delayVolume) +Change all delays feedback volume. + +**Kind**: instance method of [Reverb](#Reverb) + +| Param | Type | Description | +| --- | --- | --- | +| delayVolume | Number | Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) | + + + +### reverb.setDampFrequency(dampFrequency) +Change the Low Pass filter frequency. + +**Kind**: instance method of [Reverb](#Reverb) + +| Param | Type | Description | +| --- | --- | --- | +| dampFrequency | Number | low pass filter frequency. 0 to 44100 (depending on your maximum sampling frequency) | + + + +### reverb.process(samples) ⇒ +Process a given interleaved float value Array and copies and adds the reverb signal. + +**Kind**: instance method of [Reverb](#Reverb) +**Returns**: A new Float64Array interleaved buffer. + +| Param | Type | Description | +| --- | --- | --- | +| samples | Array | Array containing Float values or a Float64Array | + + + +## RFFT +**Kind**: global class + +* [RFFT](#RFFT) + * [new RFFT(bufferSize, sampleRate)](#new_RFFT_new) + * [.forward(buffer)](#RFFT+forward) ⇒ + + + +### new RFFT(bufferSize, sampleRate) +RFFT is a class for calculating the Discrete Fourier Transform of a signal +with the Fast Fourier Transform algorithm. + +This method currently only contains a forward transform but is highly optimized. + + +| Param | Type | Description | +| --- | --- | --- | +| bufferSize | Number | The size of the sample buffer to be computed. Must be power of 2 | +| sampleRate | Number | The sampleRate of the buffer (eg. 44100) | + + + +### rffT.forward(buffer) ⇒ +Performs a forward transform on the sample buffer. +Converts a time domain signal to frequency domain spectra. + +**Kind**: instance method of [RFFT](#RFFT) +**Returns**: The frequency spectrum array + +| Param | Type | Description | +| --- | --- | --- | +| buffer | Array | The sample buffer. Buffer Length must be power of 2 | + + + +## Sampler +**Kind**: global class + + +### new Sampler(file, bufferSize, sampleRate, playStart, playEnd, loopStart, loopEnd, loopMode) +Sampler + + +| Param | Type | +| --- | --- | +| file | | +| bufferSize | Number | +| sampleRate | Number | +| playStart | Number | +| playEnd | Number | +| loopStart | Number | +| loopEnd | Number | +| loopMode | Number | + + + +## SingleDelay +**Kind**: global class + +* [SingleDelay](#SingleDelay) + * [new SingleDelay(maxDelayInSamplesSize, delayInSamples, delayVolume)](#new_SingleDelay_new) + * [.setDelayInSamples(delayInSamples)](#SingleDelay+setDelayInSamples) + * [.setDelayVolume(delayVolume)](#SingleDelay+setDelayVolume) + * [.process(samples)](#SingleDelay+process) ⇒ + + + +### new SingleDelay(maxDelayInSamplesSize, delayInSamples, delayVolume) +SingleDelay effect by Almer Thie (http://code.almeros.com). +Copyright 2010 Almer Thie. All rights reserved. +Example: See usage in Reverb class + +This is a delay that does NOT feeds it's own delayed signal back into its +circular buffer, neither does it return the original signal. Also known as +an AllPassFilter(?). + +Compatible with interleaved stereo (or more channel) buffers and +non-interleaved mono buffers. + + +| Param | Type | Description | +| --- | --- | --- | +| maxDelayInSamplesSize | Number | Maximum possible delay in samples (size of circular buffer) | +| delayInSamples | Number | Initial delay in samples | +| delayVolume | Number | Initial feedback delay volume. Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) | + + + +### singleDelay.setDelayInSamples(delayInSamples) +Change the delay time in samples. + +**Kind**: instance method of [SingleDelay](#SingleDelay) + +| Param | Type | Description | +| --- | --- | --- | +| delayInSamples | Number | Delay in samples | + + + +### singleDelay.setDelayVolume(delayVolume) +Change the return signal volume. + +**Kind**: instance method of [SingleDelay](#SingleDelay) + +| Param | Type | Description | +| --- | --- | --- | +| delayVolume | Number | Float value: 0.0 (silence), 1.0 (normal), >1.0 (amplify) | + + + +### singleDelay.process(samples) ⇒ +Process a given interleaved or mono non-interleaved float value Array and +returns the delayed audio. + +**Kind**: instance method of [SingleDelay](#SingleDelay) +**Returns**: A new Float64Array interleaved or mono non-interleaved as was fed to this function. + +| Param | Type | Description | +| --- | --- | --- | +| samples | Array | Array containing Float values or a Float64Array | + + + +## WindowFunction +**Kind**: global class + +* [WindowFunction](#WindowFunction) + * [new WindowFunction(type, alpha)](#new_WindowFunction_new) + * [.process(buffer)](#WindowFunction+process) + + + +### new WindowFunction(type, alpha) +WindowFunction + + +| Param | Type | +| --- | --- | +| type | Number | +| alpha | Number | + + + +### windowFunction.process(buffer) +Process a buffer + +**Kind**: instance method of [WindowFunction](#WindowFunction) + +| Param | Type | +| --- | --- | +| buffer | Array | + + + +## DSP +DSP is an object which contains general purpose utility functions and constants + +**Kind**: global variable + +* [DSP](#DSP) + * [.deinterleave](#DSP.deinterleave) ⇒ + * [.getChannel](#DSP.getChannel) ⇒ + * [.invert(buffer)](#DSP.invert) ⇒ + * [.interleave(left, right)](#DSP.interleave) ⇒ + * [.mixSampleBuffers(sampleBuffer1, sampleBuffer2, negate, volumeCorrection)](#DSP.mixSampleBuffers) ⇒ + * [.RMS(buffer)](#DSP.RMS) + * [.Peak(buffer)](#DSP.Peak) + * [.mag2db(@buffer)](#DSP.mag2db) ⇒ + * [.freqz(b, a, w)](#DSP.freqz) ⇒ + + + +### DSP.deinterleave ⇒ +Converts a stereo-interleaved sample buffer into split-stereo (dual mono) sample buffers + +**Kind**: static property of [DSP](#DSP) +**Returns**: an Array containing left and right channels + +| Param | Type | Description | +| --- | --- | --- | +| buffer | Array | A stereo-interleaved sample buffer | + + + +### DSP.getChannel ⇒ +Separates a channel from a stereo-interleaved sample buffer + +**Kind**: static property of [DSP](#DSP) +**Returns**: an Array containing a signal mono sample buffer + +| Param | Type | Description | +| --- | --- | --- | +| buffer | Array | A stereo-interleaved sample buffer | +| channel | Number | A channel constant (LEFT, RIGHT, MIX) | + + + +### DSP.invert(buffer) ⇒ +Inverts the phase of a signal + +**Kind**: static method of [DSP](#DSP) +**Returns**: The inverted sample buffer + +| Param | Type | Description | +| --- | --- | --- | +| buffer | Array | A sample buffer | + + + +### DSP.interleave(left, right) ⇒ +Converts split-stereo (dual mono) sample buffers into a stereo interleaved sample buffer + +**Kind**: static method of [DSP](#DSP) +**Returns**: The stereo interleaved buffer + +| Param | Type | Description | +| --- | --- | --- | +| left | Array | A sample buffer | +| right | Array | A sample buffer | + + + +### DSP.mixSampleBuffers(sampleBuffer1, sampleBuffer2, negate, volumeCorrection) ⇒ +Helper method (for Reverb) to mix two (interleaved) samplebuffers. It's possible +to negate the second buffer while mixing and to perform a volume correction +on the final signal. + +**Kind**: static method of [DSP](#DSP) +**Returns**: A new Float64Array interleaved buffer. + +| Param | Type | Description | +| --- | --- | --- | +| sampleBuffer1 | Array | Array containing Float values or a Float64Array | +| sampleBuffer2 | Array | Array containing Float values or a Float64Array | +| negate | Boolean | When true inverts/flips the audio signal | +| volumeCorrection | Number | When you add multiple sample buffers, use this to tame your signal ;) | + + + +### DSP.RMS(buffer) +Find RMS of signal + +**Kind**: static method of [DSP](#DSP) + +| Param | Type | +| --- | --- | +| buffer | Array | + + + +### DSP.Peak(buffer) +Find Peak of signal + +**Kind**: static method of [DSP](#DSP) + +| Param | Type | +| --- | --- | +| buffer | Array | + + + +### DSP.mag2db(@buffer) ⇒ +Magnitude to decibels + +Created by Ricard Marxer on 2010-05-23. +Copyright 2010 Ricard Marxer. All rights reserved. + +**Kind**: static method of [DSP](#DSP) +**Returns**: the array in decibels + +| Param | Type | Description | +| --- | --- | --- | +| @buffer | Array | The array of magnitudes to convert to decibels | + + + +### DSP.freqz(b, a, w) ⇒ +Frequency response + + Created by Ricard Marxer on 2010-05-23. + Copyright 2010 Ricard Marxer. All rights reserved. + + Calculates the frequency response at the given points. + +**Kind**: static method of [DSP](#DSP) +**Returns**: the frequency response in magnitude + +| Param | Type | Description | +| --- | --- | --- | +| b | Number | The coefficients of the filter | +| a | Number | The coefficients of the filter | +| w | Number | The points (normally between -PI and PI) where to calculate the frequency response | + + + +## sinh(num) +Returns the hyperbolic sine of the number + +**Kind**: global function +**Meta**: version: 1004.2314 +**Meta**: discuss at: http://phpjs.org/functions/sinh +**Meta**: original by: Onno Marsman + +| Param | Type | +| --- | --- | +| num | Number | + +**Example** +```js +sinh(-0.9834330348825909); // => -1.1497971402636502 +``` diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..e3a60ce --- /dev/null +++ b/docs/README.md @@ -0,0 +1,51 @@ +## DSP.js [![npm](https://img.shields.io/npm/v/dsp.js.svg?style=flat-square)](https://www.npmjs.com/package/dsp.js) + +DSP.js is a comprehensive digital signal processing library for javascript. +It includes many functions for signal analysis and generation, including +Oscillators(sine, saw, square, triangle), Window functions (Hann, Hamming, etc), +Envelopes(ADSR), IIR Filters(lowpass, highpass, bandpass, notch), FFT and DFT +transforms, Delays, Reverb. + +## Install + +__Node__ + +Via npm: `npm install --save dsp.js` and require the modules: + +```js +const { DSP, WindowFunction, Oscillator } = require('dsp.js') +``` + +You can require individual modules: + +```js +const DSP = require('dsp.js/lib/dsp') +const WindowFunction = require('dsp.js/lib/window-function') +``` + +__Browser__ + +Or grab the [dsp.js](https://github.com/corbanbrook/dsp.js/blob/master/dist/dsp.min.js) and include it in your html: + +```html + +``` + +## Usage + +[Read the API documentation](https://github.com/corbanbrook/dsp.js/blob/master/docs/API.md) + +## Tests and scripts + +To setup you local machine to work with the code, first you have to clone this repository and install dependencies (`node` and `npm` are assumed to be installed): `npm install` + +- Run the tests: `npm test` +- Generate API reference and distribution files: `npm run dist` +- Run benchmarks: `npm run bench` + + +## License + +MIT License + +Copyright (c) 2010 Corban Brook @corban diff --git a/examples/biquad.html b/examples/biquad.html index 754f017..f827564 100644 --- a/examples/biquad.html +++ b/examples/biquad.html @@ -11,7 +11,7 @@ - + - + - + - + - +
- +
diff --git a/examples/filter.html b/examples/filter.html index 9b175c1..334d142 100644 --- a/examples/filter.html +++ b/examples/filter.html @@ -3,13 +3,13 @@ - + - +