Source: effects/reverb.js

/*
Reverb.js: implements a parametric reverberator in Javascript using Web Audio API.
Use it by setting d, g, m, f, E to the parameters you want. Connect a source to it:
source.connect(reverb.input). Connect it to a node: reverb.connect(context.destination).

The reverberator is described in depth in this paper:
http://music.cs.northwestern.edu/publications/Rafii-Pardo%20-%20A%20Digital%20Reverberator%20Controlled%20through%20Measures%20of%20the%20Reverberation%20-%20NU%20EECS%202009.pdf

Depends: filter.js, primefactors.js.
*/

var fs = 48000;
var allpassgain = .1;
var mindelay = 0.01;


/**
 * A parametric Reverberator AudioNode <br>
 * Use it by setting d, g, m, f, E to the parameters you want <br>
 * The reverberator is described in depth <a
 * href="http://music.cs.northwestern.edu/publications/Rafii-Pardo%20-%20A%20Digital%20Reverberator%20Controlled%20through%20Measures%20of%20the%20Reverberation%20-%20NU%20EECS%202009.pdf">
 * here </a>
 *
 * @class
 * @name Reverb
 * @param {AudioContext} context - The Web Audio context
 * @param {Object} opts - Can contain initial vlaues reverb.param
 */
function Reverb (context, opts) {
	this.input = context.createGain();
	this.output = context.createGain();
	this.output.gain.value = .45;


	var p = this.meta.params;
	opts = opts || {};

	this.param = new Object();

	this.param.d = opts.d || p.d.defaultValue;
	this.param.g = opts.g || p.g.defaultValue;
	this.param.m = opts.m || p.m.defaultValue;
	this.param.f = opts.f || p.f.defaultValue;
	this.param.E = opts.E || p.E.defaultValue;
	this.param.wetdry = opts.wetdry;
	if (this.param.wetdry == undefined) {
		this.param.wetdry = 1;
	}
	
	this.wet = context.createGain();
	this.dry = context.createGain();
	this.wet.gain.value = Math.cos((1 - this.param.wetdry)*.5*Math.PI);
	this.dry.gain.value = Math.cos(this.param.wetdry*.5*Math.PI);

	// Setting up internal audio nodes to implement the reverberator.
	this.combNodes = []
	rt = this.param.d*Math.log(.001)/Math.log(this.param.g);

	for (var i = 0; i < 6; i++) {
		var delay = this.param.d*(15-i)/15;
		var gain = Math.pow(.001,delay/rt);
		delay = prevPrime(delay*fs)/fs;
		var obj = new Filter(context, {
			d: delay,
			g1: 0,
			g2: gain
		});
		this.combNodes.push(obj);
	}

	this.minDelay = context.createDelay();
	this.minDelay.delayTime.value = mindelay;
	da = mindelay + .006;
	delayVal = prevPrime((da + this.param.m/2)*fs)/fs;
	this.allpassleft = new Filter(context, {
		d: delayVal,
		g1: -allpassgain,
		g2: allpassgain
	});

	delayVal = prevPrime((da - this.param.m/2)*fs)/fs;
	this.allpassright = new Filter(context, {
		d: delayVal,
		g1: -allpassgain,
		g2: allpassgain
	});

	this.gainL1 = context.createGain();
	this.gainR1 = context.createGain();
	this.gainL1.gain.value = 1;
	this.gainR1.gain.value = 1;
	
	this.merger = context.createChannelMerger(2);
	
	x1 = Math.cos(2*Math.PI*this.param.f/fs);
	x2 = Math.sqrt(Math.pow((x1 - 2), 2) - 1);
	gainVal = 2 - x1 - x2;

	this.lowfilter = context.createBiquadFilter();
	this.lowfilter.type = 'lowpass';
	this.lowfilter.frequency.value = this.param.f;

	this.lowfiltL = context.createBiquadFilter();
	this.lowfiltL.type = 'lowpass';
	this.lowfiltL.frequency.value = this.param.f;

	this.lowfiltR = context.createBiquadFilter();
	this.lowfiltR.type = 'lowpass';
	this.lowfiltR.frequency.value = this.param.f;
	


	this.gain = context.createGain();
	this.gainclean = context.createGain();
	this.gainscale = context.createGain();

	var totalGain = this.param.E + 1;
	var g1 = 1/totalGain;

	this.gainclean.gain.value = Math.cos((1-g1)*.125*Math.PI);
	this.gain.gain.value = Math.cos(g1*.375*Math.PI);
	this.gainscale.gain.value = .5*.8/(this.gainclean.gain.value + this.gain.gain.value);

	this.combEntry = context.createGain();
	this.combExit = context.createGain();

	this.allEntry = context.createGain();
	this.allExit = context.createGain();

	this.lowEntry = context.createGain();
	this.lowExit = context.createGain();

	// Connect up the audio nodes!
	this.input.connect(this.dry);
	this.dry.connect(this.output);
	this.input.connect(this.wet);
	this.wet.connect(this.combEntry);
	this.wet.connect(this.minDelay);
	this.minDelay.connect(this.gainclean);

	for (var i = 0; i < 6; i++) {
		this.combEntry.connect(this.combNodes[i].input);
		this.combNodes[i].connect(this.combExit);
	}

	this.combExit.connect(this.allEntry);
	this.allEntry.connect(this.allpassleft.input);
	this.allEntry.connect(this.allpassright.input);	

	this.allpassright.connect(this.gainR1);
	this.allpassleft.connect(this.gainL1);
	
	this.gainL1.connect(this.lowfiltL);
	this.gainR1.connect(this.lowfiltR);
	this.lowfiltL.connect(this.merger, 0 ,0);
	this.lowfiltR.connect(this.merger, 0, 1);
	
	this.merger.connect(this.allExit);
	this.allExit.connect(this.lowExit);
	
	this.lowEntry.connect(this.lowfilter);
	this.lowfilter.connect(this.lowExit);

	this.lowExit.connect(this.gain);
	this.gain.connect(this.gainscale);
	this.gainclean.connect(this.gainscale);

	this.gainscale.connect(this.output);
}

Reverb.prototype = Object.create(null, {
  connect: {
    value: function(dest) {
      this.output.connect(dest.input ? dest.input : dest);
    }
  },

  disconnect: {value: function() { this.output.disconnect(); }},

  toggle: {
    value: function(comb, low, all, only) {
	  this.input.disconnect()
	  this.combExit.disconnect();
      this.allExit.disconnect();

      if (!only) {
        this.input.connect(this.minDelay);
        this.minDelay.connect(this.gainclean);
        console.log("reverb + clean");
      } else {
        console.log("only reverb");
      }
      if (comb) {
        this.input.connect(this.combEntry);
        console.log("combs connected");
      } else {
        this.input.connect(this.combExit);
        console.log("combs disconnected");
      }

      if (all) {
        this.combExit.connect(this.allEntry);
        console.log("allpass connected");
      } else {
        this.combExit.connect(this.allExit)
        console.log("allpass disconnected");
      }

      if (low) {
        this.allExit.connect(this.lowExit)
        console.log("lowpass connected");
      } else {
        this.allExit.connect(this.lowExit);
        console.log("lowpass disconnected");
      }
    }
  },

  meta: {
    value: {
      name: 'Reverberator',
      params: {
        d: {min: 0.01, max: .1, defaultValue: .05, type: 'float'},
        g: {min: 0, max: 1, defaultValue: .5, type: 'float'},
        m: {min: -.012, max: .012, defaultValue: .006, type: 'float'},
        f: {min: 0, max: 22050, defaultValue: 22050, type: 'int'},
        E: {min: 0, max: 1, defaultValue: 1, type: 'float'},
        wetdry: {min: 0, max: 1, defaultValue: .85, type: 'float'}
      }
    }
  },

  d: {
    get: function() { return this.param.d; },
    set: function(value) {
      this.param.d = value;
      rt = this.param.d * Math.log(.001) / Math.log(this.param.g);
      for (i = 0; i < 6; i++) {
        var delay = this.param.d * (15 - i) / 15;
        delay = prevPrime(delay * fs) / fs;
        var gain = Math.pow(.001, delay / rt);
        this.combNodes[i].g2 = gain;
        this.combNodes[i].d = delay;
      }
    }
  },

  g: {
    get: function() { return this.param.g; },
    set: function(value) {
      this.param.g = value;
      rt = this.param.d * Math.log(.001) / Math.log(this.param.g);
      for (i = 0; i < 6; i++) {
        var delay = this.param.d * (15 - i) / 15;
        var gain = Math.pow(.001, delay / rt);
        this.combNodes[i].g2 = gain;
      }
    }
  },

  m: {
    get: function() { return this.param.m; },
    set: function(value) {
      this.param.m = value;
      da = mindelay + .006;
      var delayval = prevPrime((da - this.param.m / 2) * fs) / fs;
      this.allpassright.d = delayval;
      delayval = prevPrime((da + this.param.m / 2) * fs) / fs;
      this.allpassleft.d = delayval;
    }
  },

  f: {
    get: function() { return this.param.f; },
    set: function(value) {
      this.param.f = value;
      this.lowfilter.frequency.setValueAtTime(this.param.f, 0);
      this.lowfiltL.frequency.setValueAtTime(this.param.f, 0);
      this.lowfiltR.frequency.setValueAtTime(this.param.f, 0);
    }
  },

  E: {
    get: function() { return this.param.E; },
    set: function(value) {
      this.param.E = value;
      var totalGain = this.param.E + 1;
      var g1 = 1 / totalGain;

      this.gainclean.gain.value = Math.cos((1 - g1) * .125 * Math.PI);
      this.gain.gain.value = Math.cos(g1 * .375 * Math.PI);
      this.gainscale.gain.value =
          .5 * .8 / (this.gainclean.gain.value + this.gain.gain.value);
    }
  },

  /**
   * The ratio of reverb to dry signal. 1 = reverb only.
   * 
   * @member
   * @name Reverb~wetdry
   * @param {number} wetdry
   */
  wetdry: {
    get: function() { return this.param.wetdry; },
    set: function(value) {
      this.param.wetdry = value;
      this.wet.gain.value = Math.cos((1 - value) * .5 * Math.PI);
      this.dry.gain.value = Math.cos(value * .5 * Math.PI);
    }
  },

  settings: {
    get: function() {
      return [this.d, this.g, this.m, this.f, this.E, this.wetdry];
    },
    set: function(settings) {
      this.d = settings[0];
      this.g = settings[1];
      this.m = settings[2];
      this.f = settings[3];
      this.E = settings[4];
      this.wetdry = settings[5];
    }
  }
});