effects.js

/** @module effects */
import { context, samplingRate } from './context'
import { add, conn, plug, lifecycle } from './routing'
import { bypass, gain } from './signals'
import { isA, OPTS } from './utils'
import { osc } from './oscillators'
import { lowpass } from './filters'
import { gen, white } from './buffers'

/**
 * Mix an effect with the signal. Can be partially applied to create an
 * effect bus.
 * @param {Number} wet - the amount of effect (0-1)
 * @param {AudioNode} fx - the effect unit
 * @param {Object} options - (Optional) may include:
 *
 * - compensate: it it's false, the dry signal gain will pass without reduction.
 * If true (it's true by default) the dry signal is reduced the gain of the wet signal.
 * - context: the audio context
 *
 * @return {AudioNode}
 * @example
 * mix(dB(-3), delay(ms(800)))
 * @example
 * // create an effect bus
 * var rev = mix(reverb(2))
 * conn(sine(300), rev(0.2))
 */
export function mix (wet, fx, opts) {
  if (arguments.length === 1) return function (w, o) { return mix(w, wet, o) }
  if (!isA('number', wet)) wet = 0.5
  opts = opts || OPTS
  var dry = opts.compensate === false ? 1 : 1 - wet
  console.log('MIX', wet, dry, opts)
  return add(gain(dry), conn(fx, gain(wet)))
}

/**
 * Create a feedback loop.
 * @param {Integer} amount - the amount of signal
 * @param {AudioNode} node - the node to feedback
 * @param {Object} options - (Optional) options may include:
 *
 * - context: the audio context to use
 *
 * @return {AudioNode} the original node (with a feedback loop)
 */
export function feedback (amount, node, options) {
  var feed = gain(amount, options)
  node.conn(feed)
  feed.conn(node)
  return node
}

/**
 * Create a tremolo
 */
export function tremolo (rate, type, ac) {
  type = type || 'sine'
  return gain(osc(type, rate, ac), ac)
}

/**
 * Create a delay (a DelayNode object)
 */
export function dly (time, opts) {
  opts = opts || OPTS
  var dly = context(opts.context).createDelay(5)
  return lifecycle(dly, [
    plug('delayTime', time, dly)
  ])
}

/**
 * Create a convolver
 * @param {AudioBuffer} buffer
 * @param {Object} options - (Optional) options may include:
 *
 * - normalize: true to normalize the buffer
 * - context: the audio context to use
 *
 * @return {AudioNode} the convolver (ConvolverNode)
 * @example
 * reverb = mix(convolve(sample('emt-140.wav')))
 * connect(subtractive(880, { perc: [0.1, 1] }), reverb(0.2))
 */
export function convolve (buffer, o) {
  o = o || OPTS
  var c = context(o.context).createConvolver()
  c.buffer = isA('function', buffer) ? buffer() : buffer
  if (o.normalize === true) c.normalize = true
  return c
}

/**
 * Create a reverb impulse response using a logarithmic decay white noise
 * @param {Number} duration - the duration in samples
 * @param {Object} options - (Optional) options may include:
 *
 * - decay: the decay length in samples
 * - attack: the attack time in samples
 * - reverse: get the reversed impulse
 * - context: the context to use
 *
 * @return {AudioBuffer} the impulse response audio buffer
 */
export function decayIR (samples, opts) {
  samples = samples || 10000
  opts = opts || OPTS
  var attack = opts.attack || Math.floor(samples / 10)
  var decay = opts.decay || samples - attack
  // 60dB is a factor of 1 million in power, or 1000 in amplitude.
  var base = Math.pow(1 / 1000, 1 / decay)
  return gen(function (i) {
    var dec = base ? Math.pow(base, i) : 1
    var att = i < attack ? (i / attack) : 1
    return att * white.generator(i) * dec
  }, samples, opts)
}

/**
 * Create a simple reverb
 * @param {Number} duration - in seconds? WTF?
 */
export function reverb (duration, opts) {
  opts = opts || OPTS
  var rate = samplingRate(opts.context)
  return convolve(decayIR(duration * rate, opts), opts)
}

/**
 * A mono or stereo delay with filtered feedback
 */
export function delay (time, filter, feedAmount, ac) {
  if (!isA('number', feedAmount)) feedAmount = 0.3
  filter = isA('number', filter) ? lowpass(filter, null, null, ac) : filter || bypass(ac)
  return feedback(feedAmount, dly(time, ac), filter, ac)
}