/** @module signals */
import { OPTS, isA } from './utils'
import { context } from './context'
import { add, conn, lifecycle, plug } from './routing'
import { dBToGain, levelToGain } from './units'
/**
* Create a GainNode
*
* @param {Object} config - may include:
*
* - value (or gain): the gain (can be a number or a signal)
* - dB (or db): the gain in dB (only if gain is not specified)
* - level: the gain in a logaritmic scale from 0 to 100
* - context: the audio context to use to create the signal
*
* This funcion accepts a number with the gain value instead of a config object.
*
* @return {AudioNode} a GainNode
* @example
* gain({ dB: -3, context: <AudioContext> })
* // with modulation (kind of tremolo)
* conn(sine(400), gain({ value: sine(10) }))
* // passing a number instead of an object
* conn(sine('C4'), gain(0.3))
*/
export function gain (opts) {
opts = opts || OPTS
var node = context(opts.context).createGain()
return lifecycle(node, [
plug('gain', getGain(opts), node)
])
}
// given an config object, return the gain
function getGain (opts) {
return isA('number', opts) ? opts
: opts.value ? opts.value
: opts.gain ? opts.gain
: isA('number', opts.dB) ? dBToGain(opts.dB)
: isA('number', opts.db) ? dBToGain(opts.db)
: isA('number', opts.level) ? levelToGain(opts.level)
: null
}
/**
* Create a constant signal. Normally you will use it in combination with
* envelopes or modulators.
*
* @param {Integer} value - the value of the constant
* @param {Object} options - (Optional) options may include:
*
* - context: the audio context to use to create the signal
*
* @return {AudioNode} the constant audio node
* @example
* sine(constant(440)).start()
*/
export function constant (value, o) {
// TODO: cache buffer
var ctx = context(o ? o.context : null)
var source = ctx.createBufferSource()
source.loop = true
source.buffer = ctx.createBuffer(1, 2, ctx.sampleRate)
var data = source.buffer.getChannelData(0)
data[0] = data[1] = value
return source
}
/**
* Create a signal source. You will use signals to change parameters of a
* audio node after starting. See example.
* @param {Integer} value - the value of the constant
* @param {Object} options - (Optional) options may include:
*
* - context: the audio context to use to create the signal
*
* @return {AudioParam} the constant audio node
* @example
* var freq = signal(440)
* sine(freq).start()
* freq.value.linearRampToValueAtTime(880, after(5))
*/
export function signal (value, opts) {
var signal = gain(value, opts)
conn(constant(1, opts), signal).start()
signal.signal = signal.gain
return signal
}
/**
* Create a node that bypasses the signal
* @param {Object} config - may include:
*
* - context: the audio context to use
*
* @return {AudioNode} the bypass audio node
* @example
* conn(sine('C4'), add(bypass(), dly(0.2)))
*/
export function bypass (o) {
return context(o ? o.context : null).createGain()
}
/**
* Multiply a signal.
*
* @param {Integer} value - the value
* @param {Integer|AudioNode} signal - the signal to multiply by
* @example
* // a vibrato effect
* sine(440, { detune: mult(500, sine(2)) })
*/
export function mult (value, signal) {
if (isA('number', signal)) return value * signal
return conn(signal, gain({ value: value, context: signal.context }))
}
/**
* Scale a signal. Given a signal (between -1 and 1) scale it to fit in a range.
* @param {Integer} min - the minimum of the range
* @param {Integer} max - the minimum of the range
* @param {AudioNode} source - the signal to scale
* @return {AudioNode} the scaled signal node
* @example
* // create a frequency envelope between 440 and 880 Hz
* sine(scale(440, 880, adsr(0.1, 0.01, 1, 1)))
*/
export function scale (min, max, source) {
var ctx = source
if (source.numberOfInputs) source = conn(constant(1, ctx), source)
var delta = max - min
return add(constant(min, ctx), mult(delta, source))
}