Source: syngen/mixer.js

/**
 * Provides a mastering process and utilities for routing audio into it like a virtual mixing board.
 * Implementations are encouraged to leverage this instead of the main audio destination directly.
 * @namespace
 */
syngen.mixer = (() => {
  const context = syngen.context()

  const mainCompensator = context.createGain(),
    mainCompressor = context.createDynamicsCompressor(),
    mainInput = context.createGain(),
    mainOutput = context.createGain()

  let mainHighpass,
    mainLowpass

  mainCompressor.connect(mainCompensator)
  mainCompensator.connect(mainOutput)
  mainOutput.connect(context.destination)

  mainCompensator.gain.value = 1
  mainCompressor.attack.value = syngen.const.zeroTime
  mainCompressor.knee.value = 0
  mainCompressor.ratio.value = 20
  mainCompressor.release.value = syngen.const.zeroTime
  mainCompressor.threshold.value = 0

  createFilters()

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

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

    mainInput.connect(mainHighpass)
    mainHighpass.connect(mainLowpass)
    mainLowpass.connect(mainCompressor)
  }

  function destroyFilters() {
    mainInput.disconnect()
    mainLowpass.disconnect()
    mainLowpass = null
    mainHighpass.disconnect()
    mainHighpass = null
  }

  return {
    /**
     * Creates a `GainNode` that's connected to the main input.
     * Implementations can leverage buses to create submixes.
     * @memberof syngen.mixer
     * @returns {GainNode}
     */
    createBus: () => {
      const input = context.createGain()
      input.connect(mainInput)
      return input
    },
    /**
     * Returns the main input `GainNode`.
     * @memberof syngen.mixer
     * @returns {GainNode}
     */
    input: () => mainInput,
    /**
     * Returns the main output `GainNode`.
     * @memberof syngen.mixer
     * @returns {GainNode}
     */
    output: () => mainOutput,
    /**
     * Exposes the parameters associated with the mastering process.
     * Here's an overview of its routing:
     * - `GainNode` input
     * - `BiquadFilterNode` highpass
     * - `BiquadFilterNode` lowpass
     * - `DynamicsCompressorNode` limiter
     * - `GainNode` limiter makeup gain
     * - `GainNode` output
     * - `AudioDestinationNode` `{@link syngen.context}().destination`
     * @memberof syngen.mixer
     * @property {AudioParam} gain
     * @property {Object} highpass
     * @property {AudioParam} highpass.frequency
     * @property {Object} limiter
     * @property {AudioParam} limiter.attack
     * @property {AudioParam} limiter.gain
     * @property {AudioParam} limiter.knee
     * @property {AudioParam} limiter.ratio
     * @property {AudioParam} limiter.release
     * @property {AudioParam} limiter.threshold
     * @property {Object} lowpass
     * @property {AudioParam} lowpass.frequency
     * @property {AudioParam} preGain
     */
    param: {
      gain: mainOutput.gain,
      highpass: {
        frequency: mainHighpass.frequency,
      },
      limiter: {
        attack: mainCompressor.attack,
        gain: mainCompensator.gain,
        knee: mainCompressor.knee,
        ratio: mainCompressor.ratio,
        release: mainCompressor.release,
        threshold: mainCompressor.threshold,
      },
      lowpass: {
        frequency: mainLowpass.frequency,
      },
      preGain: mainInput.gain,
    },
    /**
     * Occasionally the main filters can enter an unstable or bad state.
     * When this happens the entire mix can drop out to silence.
     * This provides a solution for replacing them with stable filters.
     * Implementations can proactively check for invalid states with an {@link https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode|AnalyserNode} or {@link https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletNode|AudioWorkletNode}.
     * Beware that the nodes that caused the issue may also need reset.
     * @memberof syngen.mixer
     */
    rebuildFilters: function () {
      const highpassFrequency = mainHighpass.frequency.value,
        lowpassFrequency = mainLowpass.frequency.value

      this.auxiliary.reverb.rebuildFilters()

      destroyFilters()
      createFilters(highpassFrequency, lowpassFrequency)

      this.param.highpass.frequency = mainHighpass.frequency
      this.param.lowpass.frequency = mainLowpass.frequency

      return this
    },
  }
})()