import { when } from './context'
import { slice, isArray } from './utils'
/**
* This module provides two ways to route nodes:
*
* - In series: A -> B -> C -> D, using the `connect` function
* - In parallel: in -> [A, B, C] -> out, using the `add` function
* @module routing
*/
/**
* Connect nodes in series: A -> B -> C -> D.
* @param {Array<AudioNode>} nodes - the list of nodes to be connected
* @return {AudioNode} the resulting audio node
*/
export function conn (nodes) {
nodes = isArray(nodes) ? nodes : slice.call(arguments)
if (!nodes.length) return null
else if (nodes.length === 1) return nodes[0]
var node = nodes[0]
if (!node.duration) node.duration = 0
var last = nodes.reduce(function (src, dest) {
src.connect(dest)
node.duration = Math.max(node.duration, dest.duration || 0)
return dest
})
overrideConnect(node, last)
var startables = nodes.slice(1).filter(isStartable)
if (startables.length) {
var _start = node.start
node.start = function (time) {
if (_start) _start.call(node, time)
startables.forEach(function (node) { node.start(time) })
if (node.duration) node.stop(time + node.duration)
}
var _stop = node.stop
node.stop = function (time) {
var t = 0
startables.reverse()
startables.forEach(function (node) {
t = t || when(time, null, node.context)
node.stop(t)
t += node.release || 0
})
if (_stop) _stop.call(node, t)
}
}
return node
}
// TODO: A RESOLVER: add debe tener onended cuando acaben todos sus nodos
/**
* Connect nodes in parallel in order to add signals. This is one of the
* routing functions (the other is `connect`).
* @param {...AudioNode} nodes - the nodes to be connected
* @return {AudioNode} the resulting audio node
* @example
* add(sine(400), sine(401)).start()
*/
export function add (nodes) {
nodes = isArray(nodes) ? nodes : slice.call(arguments)
if (!nodes.length) return null
else if (nodes.length === 1) return nodes[0]
var context = nodes[0].context
var input = context.createGain()
input.id = 'ADDin'
var output = context.createGain()
output.id = 'ADDout'
// the connection loop: connect input to all nodes. all nodes to output.
nodes.forEach(function (node) {
if (node.numberOfInputs) input.connect(node)
node.connect(output)
})
// this must be after the connection loop
overrideConnect(input, output)
var node = lifecycle(input, nodes)
addOnEndedEvent(node)
return node
}
// make trigger an onended event when all startable node ended
function addOnEndedEvent (node) {
if (!node.dependents) return
var length = node.dependents.length
function triggerEnded () {
length--
if (!length && node.onended) node.onended()
}
node.dependents.forEach(function (node) {
node.onended = triggerEnded
})
}
// overrides the node's connect function to use output node
function overrideConnect (node, output) {
node.output = node
node.connect = function (dest) {
output.connect(dest)
return node
}
return node
}
/**
* Return if a node is startable or note
* @private
*/
function isStartable (node) { return node && typeof node.start === 'function' }
/**
* Plug something (a value, a node) into a node parameter
* @param {String} name - the parameter name
* @param {AudioNode|Object} value - the value (can be a signal)
* @param {AudioNode} target - the target audio node
* @return {AudioNode} the modulator signal if any or undefined
* @private
*/
export function plug (name, value, node) {
if (value === null || typeof value === 'undefined') {
// do nothing
} else if (typeof value.connect === 'function') {
node[name].value = 0
value.conn(node[name])
return value
} else if (node[name]) {
node[name].value = value
}
}
/**
* Override start and stop functions (if necessary) to handle node dependencies
* lifecycle.
* @private
*/
export function lifecycle (node, dependents) {
dependents = dependents.filter(isStartable)
if (dependents.length) {
var _start = node.start
var _stop = node.stop
node.start = function (time) {
var res = _start ? _start.call(node, time) : void 0
dependents.forEach(function (d) { if (d.start) d.start(time) })
if (node.start.then) node.start.then(time, node, dependents)
return res
}
node.stop = function (time) {
var res = _stop ? _stop.call(node, time) : void 0
dependents.forEach(function (d) { if (d.stop) d.stop(time) })
return res
}
node.dependents = dependents
}
return node
}