Source: syngen/audio/formant.js

/**
 * Provides factories that define and create circuits that model human vowel sounds.
 * @namespace
 */
syngen.audio.formant = {}

/**
 * Returns a blend of formants `a` and `b` with `mix`.
 * @param {syngen.audio.formant~Definition} a
 * @param {syngen.audio.formant~Definition} b
 * @param {Number} mix
 *   Expects a number within `[0, 1]`.
 * @static
 */
syngen.audio.formant.blend = (a, b, mix = 0) => {
  const getFrequency = (array, index) => (array[index] && array[index].frequency) || syngen.const.zero
  const getGain = (array, index) => (array[index] && array[index].gain) || syngen.const.zeroGain
  const getQ = (array, index) => (array[index] && array[index].Q) || 1

  return [...Array(Math.max(a.length, b.length))].map((_, i) => ({
    frequency: syngen.utility.lerp(getFrequency(a, i), getFrequency(b, i), mix),
    gain: syngen.utility.lerp(getGain(a, i), getGain(b, i), mix),
    Q: syngen.utility.lerp(getQ(a, i), getQ(b, i), mix),
  }))
}

/**
 * @returns {syngen.audio.synth~Plugin}
 * @static
 */
syngen.audio.formant.create = (frequencies = []) => {
  const context = syngen.audio.context()

  const input = context.createGain(),
    output = context.createGain()

  const filters = frequencies.map(({
    frequency = 0,
    gain = 1,
    Q = 1,
  } = {}) => {
    const filter = context.createBiquadFilter()
    filter.frequency.value = frequency
    filter.Q.value = Q
    filter.type = 'bandpass'

    const gainNode = context.createGain()
    gainNode.gain.value = gain

    input.connect(filter)
    filter.connect(gainNode)
    gainNode.connect(output)

    return {
      filter: {
        frequency: filter.frequency,
        Q: filter.Q,
      },
      gain: gainNode,
    }
  })

  return {
    input,
    output,
    param: {
      filter: filters,
    },
  }
}

/**
 * Creates a formant effect for the vowel A.
 * @returns {syngen.audio.synth~Plugin}
 * @static
 */
syngen.audio.formant.createA = function () {
  return this.create(
    this.a()
  )
}

/**
 * Creates a formant effect for the vowel E.
 * @returns {syngen.audio.synth~Plugin}
 * @static
 */
syngen.audio.formant.createE = function () {
  return this.create(
    this.e()
  )
}

/**
 * Creates a formant effect for the vowel I.
 * @returns {syngen.audio.synth~Plugin}
 * @static
 */
syngen.audio.formant.createI = function () {
  return this.create(
    this.i()
  )
}

/**
 * Creates a formant effect for the vowel O.
 * @returns {syngen.audio.synth~Plugin}
 * @static
 */
syngen.audio.formant.createO = function () {
  return this.create(
    this.o()
  )
}

/**
 * Creates a formant effect for the vowel U.
 * @returns {syngen.audio.synth~Plugin}
 * @static
 */
syngen.audio.formant.createU = function () {
  return this.create(
    this.u()
  )
}

/**
 * Transitions formant `plugin` to `definition` over `duration` seconds.
 * Returns a promise that resolves when the transition is complete.
 * The `plugin` can either be an instance or its parameter hash.
 * @param {syngen.audio.formant~Plugin|Object} plugin
 * @param {syngen.audio.formant~Definition} [definition=[]]
 * @param {Number} [duration={@link syngen.const.zeroTime}]
 * @returns {Promise}
 * @static
 */
syngen.audio.formant.transition = function(plugin, definition = [], duration = syngen.const.zeroTime) {
  // Look for formant parameters or expect it directly
  const bank = plugin.param ? plugin.param.filter : plugin

  if (!Array.isArray(bank) || !(bank[0] && bank[0].filter)) {
    throw new Error('Invalid plugin')
  }

  bank.forEach((filter, i) => {
    if (!definition[i]) {
      syngen.audio.ramp.linear(filter.gain.gain, syngen.const.zeroGain, duration)
      return
    }

    const {frequency, gain, Q} = definition[i]

    if (typeof frequency != 'undefined') {
      syngen.audio.ramp.exponential(filter.filter.frequency, frequency, duration)
    }

    if (typeof gain != 'undefined') {
      syngen.audio.ramp.linear(filter.gain.gain, gain, duration)
    }

    if (typeof Q != 'undefined') {
      syngen.audio.ramp.exponential(filter.filter.Q, Q, duration)
    }
  })

  return syngen.utility.timing.promise(duration * 1000)
}

/**
 * Returns a formant definition for the vowel A.
 * @returns {syngen.audio.formant~Definition}
 * @static
 */
syngen.audio.formant.a = () => [
  {
    frequency: 599,
    gain: 1,
    Q: 5,
  },
  {
    frequency: 1001,
    gain: 1,
    Q: 20,
  },
  {
    frequency: 2045,
    gain: 1,
    Q: 50,
  },
  {
    frequency: 2933,
    gain: 1,
    Q: 80,
  },
]

/**
 * Returns a formant definition for the vowel E.
 * @returns {syngen.audio.formant~Definition}
 * @static
 */
syngen.audio.formant.e = () => [
  {
    frequency: 469,
    gain: 1,
    Q: 5,
  },
  {
    frequency: 2150,
    gain: 1,
    Q: 20,
  },
  {
    frequency: 2836,
    gain: 1,
    Q: 50,
  },
  {
    frequency: 3311,
    gain: 1,
    Q: 80,
  },
]

/**
 * Returns a formant definition for the vowel I.
 * @returns {syngen.audio.formant~Definition}
 * @static
 */
syngen.audio.formant.i = () => [
  {
    frequency: 274,
    gain: 1,
    Q: 5,
  },
  {
    frequency: 1704,
    gain: 1,
    Q: 20,
  },
  {
    frequency: 2719,
    gain: 1,
    Q: 50,
  },
  {
    frequency: 3404,
    gain: 1,
    Q: 80,
  },
]

/**
 * Returns a formant definition for the vowel O.
 * @returns {syngen.audio.formant~Definition}
 * @static
 */
syngen.audio.formant.o = () => [
  {
    frequency: 411,
    gain: 1,
    Q: 5,
  },
  {
    frequency: 784,
    gain: 1,
    Q: 20,
  },
  {
    frequency: 2176,
    gain: 1,
    Q: 50,
  },
  {
    frequency: 2987,
    gain: 1,
    Q: 80,
  },
]

/**
 * Returns a formant definition for the vowel U.
 * @returns {syngen.audio.formant~Definition}
 * @static
 */
syngen.audio.formant.u = () => [
  {
    frequency: 290,
    gain: 1,
    Q: 5,
  },
  {
    frequency: 685,
    gain: 1,
    Q: 20,
  },
  {
    frequency: 2190,
    gain: 1,
    Q: 50,
  },
  {
    frequency: 3154,
    gain: 1,
    Q: 80,
  },
]

/**
 * Formant definition that consists of an array of bandpass filter parameters.
 * @property {Number} frequency
 * @property {Number} gain
 * @property {Number} Q
 * @type {Object[]}
 * @typedef syngen.audio.formant~Definition
 */

/**
 * Formant effect that consists of a bank of finely tuned bandpass filters.
 * @type {syngen.audio.synth~Plugin}
 * @typedef syngen.audio.formant~Plugin
 */