Source: syngen/audio/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.audio.mixer = (() => {
  const context = syngen.audio.context()

  const masterCompensator = context.createGain(),
    masterCompressor = context.createDynamicsCompressor(),
    masterInput = context.createGain(),
    masterOutput = context.createGain()

  let masterHighpass,
    masterLowpass

  masterCompressor.connect(masterCompensator)
  masterCompensator.connect(masterOutput)
  masterOutput.connect(context.destination)

  masterCompensator.gain.value = 1
  masterCompressor.attack.value = syngen.const.zeroTime
  masterCompressor.knee.value = 0
  masterCompressor.ratio.value = 20
  masterCompressor.release.value = syngen.const.zeroTime
  masterCompressor.threshold.value = 0

  createFilters()

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

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

    masterInput.connect(masterHighpass)
    masterHighpass.connect(masterLowpass)
    masterLowpass.connect(masterCompressor)
  }

  function destroyFilters() {
    masterInput.disconnect()
    masterLowpass.disconnect()
    masterLowpass = null
    masterHighpass.disconnect()
    masterHighpass = null
  }

  return {
    /**
     * A collection of auxiliary sends that provide optional parallel effects processing.
     * @memberof syngen.audio.mixer
     * @namespace
     */
    auxiliary: {},
    /**
     * Creates a `GainNode` that's connected to the master input.
     * Implementations can leverage buses to create submixes.
     * @memberof syngen.audio.mixer
     * @returns {GainNode}
     */
    createBus: () => {
      const input = context.createGain()
      input.connect(masterInput)
      return input
    },
    /**
     * Exposes the nodes and 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.audio.context}().destination`
     * @memberof syngen.audio.mixer
     * @property {Function} input
     *   Returns the master input `GainNode`.
     * @property {Function} output
     *   Returns the master output `GainNode`.
     * @property {Object} param
     *   Useful parameters for tuning the mastering process.
     * @property {AudioParam} param.gain
     * @property {Object} param.highpass
     * @property {AudioParam} param.highpass.frequency
     * @property {Object} param.limiter
     * @property {AudioParam} param.limiter.attack
     * @property {AudioParam} param.limiter.gain
     * @property {AudioParam} param.limiter.knee
     * @property {AudioParam} param.limiter.ratio
     * @property {AudioParam} param.limiter.release
     * @property {AudioParam} param.limiter.threshold
     * @property {Object} param.lowpass
     * @property {AudioParam} param.lowpass.frequency
     */
    master: {
      input: () => masterInput,
      output: () => masterOutput,
      param: {
        gain: masterOutput.gain,
        highpass: {
          frequency: masterHighpass.frequency,
        },
        limiter: {
          attack: masterCompressor.attack,
          gain: masterCompensator.gain,
          knee: masterCompressor.knee,
          ratio: masterCompressor.ratio,
          release: masterCompressor.release,
          threshold: masterCompressor.threshold,
        },
        lowpass: {
          frequency: masterLowpass.frequency,
        },
      },
    },
    /**
     * Occasionally the master 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.audio.mixer
     */
    rebuildFilters: function () {
      const highpassFrequency = masterHighpass.frequency.value,
        lowpassFrequency = masterLowpass.frequency.value

      this.auxiliary.reverb.rebuildFilters()

      destroyFilters()
      createFilters(highpassFrequency, lowpassFrequency)

      this.master.param.highpass.frequency = masterHighpass.frequency
      this.master.param.lowpass.frequency = masterLowpass.frequency

      return this
    },
    /**
     * A collection of circuits that route signals to auxiliary sends.
     * @namespace syngen.audio.mixer.send
     */
    send: {},
  }
})()