UNPKG

dot-audio

Version:

A simple web audio library for making synthesizers

173 lines (156 loc) 6.35 kB
/** * Base class for Dot audio nodes. * * @param {AudioContext} AC - Audio context * @returns {DotAudioNode} Audio Node */ class DotAudioNode { constructor(AC) { this.AC = AC this.name = 'DotAudioNode' return this } // --- Public Methods --- /** * Get the name of the node. * @returns {String} Node name */ getName = () => this.name /** * Get a map of the params available from the node. * Gives access to the audio params of the node that can be connected to. * @returns {Object.<string, AudioParam>} Node params */ getParams = () => this.params /** * Get a specific param of the node. * Gives access to a specific audio param of the node that can be connected to. * @param {String} param - Name of the param * @returns {AudioParam} Node audio param */ getParam = (param) => this.params[param] /** * Get an array of the inputs of this node. * @returns {Array.<DotAudioNode|AudioNode|AudioParam>} Array of inputs */ getInputs = () => this.inputs /** * Get an array of the outputs of this node. * @returns {Array.<DotAudioNode|AudioNode>} Array of outputs */ getOutputs = () => this.outputs // - Util Methods - // Connect and disconnect shells to allow child nodes to add validation /** * Connect this node to a DotAudioNode, AudioNode, or AudioParam. * An array can be passed to connect to multiple destinations. * Optionally pass output and input channels. * @example * const synth = new Dot.Synth(AC) * const reverb = new Dot.Reverb(AC) * * synth.connect(reverb) // Connect * * @param {DotAudioNode | AudioNode | AudioParam | Array.<DotAudioNode|AudioNode|AudioParam>} * destination - The input/destination to connect to * @param {Number} [outputNum] - Output channel to connect with (optional) * @param {Number} [inputNum] - Input channel to connect to (optional) * @returns {DotAudioNode} this */ connect = (destination, outputNum, inputNum) => this._connect(destination, outputNum, inputNum) /** * Disconnect this node from one if its connections. * If no destination is passed, all connections will be removed. * Optionally pass output and input channels. * @example * const synth = new Dot.Synth(AC) * const reverb = new Dot.Reverb(AC) * * synth.connect(reverb) // Connect * synth.disconnect(reverb) // Disconnect * * @param {AudioParam | AudioNode | DotAudioNode | Array.<DotAudioNode|AudioParam|AudioNode>} * destination - The input/destination to disconnect from * @param {Number} [outputNum] - Output channel to disconnect from (optional) * @param {Number} [inputNum] - Input channel to disconnect from (optional) * @returns {DotAudioNode} this */ disconnect = (destination, outputNum, inputNum) => this._disconnect(destination, outputNum, inputNum) // --- Private Methods --- _connect = (destination, outputNum = 0, inputNum = 0) => { if (Array.isArray(destination)) { destination.forEach(dest => this.connect(dest)) } else if (destination instanceof DotAudioNode) { if (!destination.getInputs()) { console.error('Cannot connect to a node with no inputs') return } const inputs = destination._getInputNodes() inputs.forEach((input) => { this.getOutputs().forEach(output => output.connect(input, outputNum, inputNum)) }) } else if (destination instanceof AudioNode) { this.getOutputs().forEach(output => output.connect(destination, outputNum, inputNum)) } else if (destination instanceof AudioParam) { this.getOutputs().forEach(output => output.connect(destination)) } else { console.error('Invalid destination type') } return this } _disconnect = (destination, outputNum = 0, inputNum = 0) => { if (Array.isArray(destination)) { destination.forEach(dest => this.disconnect(dest)) } else if (destination instanceof DotAudioNode) { if (!destination.getInputs()) { console.error('Cannot disconnect from destination provided') return } const inputs = destination._getInputNodes() inputs.forEach((input) => { this.getOutputs().forEach(output => output.disconnect(input, outputNum, inputNum)) }) } else if (destination instanceof AudioNode) { this.getOutputs().forEach(output => output.disconnect(destination, outputNum, inputNum)) } else { this.getOutputs().forEach(output => output.disconnect(destination)) } return this } // Recursively go down to get the default audio nodes/params of all inputs _getInputNodes = () => ( this.getInputs().reduce((nodes, input) => { if (input instanceof DotAudioNode) { nodes.push(...input._getInputNodes()) } else { nodes.push(input) } return nodes }, []) ) // - Update Methods - _update = (param, val) => param.setValueAtTime(val, this.AC.currentTime) _timeUpdate = (param, val, time = 0) => { time ? param.setTargetAtTime(val, this.AC.currentTime, time) : param.setValueAtTime(val, this.AC.currentTime) } _linearFadeUpdate = (aParam, bParam, val, time = 0) => { this._timeUpdate(aParam, 1 - val, time) this._timeUpdate(bParam, val, time) } _equalPowerFadeUpdate = (aParam, bParam, val, time = 0) => { this._timeUpdate(aParam, Math.cos(val * 0.5 * Math.PI), time) this._timeUpdate(bParam, Math.cos((1.0 - val) * 0.5 * Math.PI), time) } _dryWetUpdate = (dryParam, wetParam, val, time = 0) => { if (val < 0.5) { this._timeUpdate(dryParam, 1, time) this._timeUpdate(wetParam, val * 2, time) } else { this._timeUpdate(dryParam, 1 - ((val - 0.5) * 2), time) this._timeUpdate(wetParam, 1, time) } } } export default DotAudioNode