Source: syngen/audio/mixer/auxiliary/reverb.js

/**
 * Provides a auxiliary send for global reverb processing.
 * Because `ConvolverNode`s are quite intensive, implementations are encouraged to leverage this to provide a single global reverb.
 * @augments syngen.utility.pubsub
 * @namespace
 * @see syngen.audio.mixer.send.reverb
 */
syngen.audio.mixer.auxiliary.reverb = (() => {
  const context = syngen.audio.context(),
    delay = context.createDelay(),
    input = context.createGain(),
    output = syngen.audio.mixer.createBus(),
    pubsub = syngen.utility.pubsub.create()

  let active = true,
    convolver = context.createConvolver(),
    highpass,
    lowpass

  convolver.buffer = syngen.audio.buffer.impulse.small()
  delay.delayTime.value = 1/64

  input.connect(delay)
  createFilters()
  convolver.connect(output)

  function createFilters(highpassFrequency = syngen.const.minFrequency, lowpassFrequency = syngen.const.maxFrequency) {
    highpass = context.createBiquadFilter()
    highpass.type = 'highpass'
    highpass.frequency.value = highpassFrequency

    lowpass = context.createBiquadFilter()
    lowpass.type = 'lowpass'
    lowpass.frequency.value = lowpassFrequency

    delay.connect(highpass)
    highpass.connect(lowpass)
    lowpass.connect(convolver)
  }

  function destroyFilters() {
    delay.disconnect()
    lowpass.disconnect()
    lowpass = null
    highpass.disconnect()
    highpass = null
  }

  return syngen.utility.pubsub.decorate({
    /**
     * Creates a `GainNode` that's connected to the reverb input.
     * @memberof syngen.audio.mixer.auxiliary.reverb
     * @returns {GainNode}
     */
    createSend: () => {
      const gain = context.createGain()
      gain.connect(input)
      return gain
    },
    /**
     * Returns whether the processing is active.
     * @memberof syngen.audio.mixer.auxiliary.reverb
     * @returns {Boolean}
     */
    isActive: () => active,
    /**
     * Returns the output node for the send.
     * @deprecated
     * @memberof syngen.audio.mixer.auxiliary.reverb
     * @returns {GainNode}
     */
    output: () => output,
    /**
     * Exposes the parameters associated with reverb processing.
     * @memberof syngen.audio.mixer.auxiliary.reverb
     * @property {AudioParam} delay
     * @property {AudioParam} gain
     * @property {Object} highpass
     * @property {AudioParam} highpass.frequency
     * @property {Object} lowpass
     * @property {AudioParam} lowpass.frequency
     */
    param: {
      delay: delay.delayTime,
      gain: output.gain,
      highpass: {
        frequency: highpass.frequency,
      },
      lowpass: {
        frequency: lowpass.frequency,
      },
    },
    /**
     * Occasionally the filters can enter an unstable or bad state.
     * This provides a solution for replacing them with stable filters.
     * @memberof syngen.audio.mixer.auxiliary.reverb
     * @see syngen.audio.mixer.rebuildFilters
     */
    rebuildFilters: function () {
      const highpassFrequency = highpass.frequency.value,
        lowpassFrequency = lowpass.frequency.value

      destroyFilters()
      createFilters(highpassFrequency, lowpassFrequency)

      this.param.highpass.frequency = highpass.frequency
      this.param.lowpass.frequency = lowpass.frequency

      return this
    },
    /**
     * Sets the active state.
     * Implementations can disable processing for a performance boost.
     * @fires syngen.audio.mixer.auxiliary.reverb#event:activate
     * @fires syngen.audio.mixer.auxiliary.reverb#event:deactivate
     * @memberof syngen.audio.mixer.auxiliary.reverb
     * @param {Boolean} state
     */
    setActive: function (state) {
      if (active == state) {
        return this
      }

      active = Boolean(state)

      if (active) {
        /**
         * Fired whenever the send is activated.
         * @event syngen.audio.mixer.auxiliary.reverb#event:activate
         */
        pubsub.emit('activate')
        input.connect(delay)
      } else {
        /**
         * Fired whenever the send is deactivated.
         * @event syngen.audio.mixer.auxiliary.reverb#event:deactivate
         */
        pubsub.emit('deactivate')
        input.disconnect(delay)
      }

      return this
    },
    /**
     * Sets the impulse buffer for the inner `ConvolverNode`.
     * To prevent pops and clicks, the tail of the previous buffer persists until it fades out.
     * @memberof syngen.audio.mixer.auxiliary.reverb
     * @param {BufferSource} buffer
     */
    setImpulse: function (buffer) {
      input.disconnect()

      convolver = context.createConvolver()
      convolver.buffer = buffer
      convolver.connect(output)

      if (active) {
        input.connect(delay)
      }

      return this
    },
  }, pubsub)
})()