mirror of
https://github.com/Tonejs/Tone.js
synced 2025-01-06 17:08:48 +00:00
57070d987b
fixes #338
239 lines
6.7 KiB
JavaScript
239 lines
6.7 KiB
JavaScript
define(["Tone/core/Tone", "Tone/signal/WaveShaper", "Tone/type/Type", "Tone/core/Param",
|
|
"Tone/shim/ConstantSourceNode", "Tone/core/Gain"], function(Tone){
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* @class A signal is an audio-rate value. Tone.Signal is a core component of the library.
|
|
* Unlike a number, Signals can be scheduled with sample-level accuracy. Tone.Signal
|
|
* has all of the methods available to native Web Audio
|
|
* [AudioParam](http://webaudio.github.io/web-audio-api/#the-audioparam-interface)
|
|
* as well as additional conveniences. Read more about working with signals
|
|
* [here](https://github.com/Tonejs/Tone.js/wiki/Signals).
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Param}
|
|
* @param {Number|AudioParam} [value] Initial value of the signal. If an AudioParam
|
|
* is passed in, that parameter will be wrapped
|
|
* and controlled by the Signal.
|
|
* @param {string} [units=Number] unit The units the signal is in.
|
|
* @example
|
|
* var signal = new Tone.Signal(10);
|
|
*/
|
|
Tone.Signal = function(){
|
|
|
|
var options = Tone.defaults(arguments, ["value", "units"], Tone.Signal);
|
|
Tone.Param.call(this, options);
|
|
|
|
/**
|
|
* When a signal is connected to another signal or audio param,
|
|
* this signal becomes a proxy for it
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this._proxies = [];
|
|
|
|
/**
|
|
* Indicates if the constant source was started or not
|
|
* @private
|
|
* @type {Boolean}
|
|
*/
|
|
this._sourceStarted = false;
|
|
|
|
/**
|
|
* The constant source node which generates the signal
|
|
* @type {ConstantSourceNode}
|
|
* @private
|
|
*/
|
|
this._constantSource = this.context.createConstantSource();
|
|
this._param = this._constantSource.offset;
|
|
this.value = options.value;
|
|
|
|
/**
|
|
* The node where the constant signal value is scaled.
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this.output = this._constantSource;
|
|
|
|
/**
|
|
* The node where the value is set.
|
|
* @type {Tone.Param}
|
|
* @private
|
|
*/
|
|
this.input = this._param = this.output.offset;
|
|
};
|
|
|
|
Tone.extend(Tone.Signal, Tone.Param);
|
|
|
|
/**
|
|
* The default values
|
|
* @type {Object}
|
|
* @static
|
|
* @const
|
|
*/
|
|
Tone.Signal.defaults = {
|
|
"value" : 0,
|
|
"units" : Tone.Type.Default,
|
|
"convert" : true,
|
|
};
|
|
|
|
/**
|
|
* When signals connect to other signals or AudioParams,
|
|
* they take over the output value of that signal or AudioParam.
|
|
* For all other nodes, the behavior is the same as a default <code>connect</code>.
|
|
*
|
|
* @override
|
|
* @param {AudioParam|AudioNode|Tone.Signal|Tone} node
|
|
* @param {number} [outputNumber=0] The output number to connect from.
|
|
* @param {number} [inputNumber=0] The input number to connect to.
|
|
* @returns {Tone.Signal} this
|
|
* @method
|
|
*/
|
|
Tone.Signal.prototype.connect = function(node){
|
|
//this is an optimization where this node will forward automations
|
|
//to connected nodes without any signal if possible.
|
|
if (this._isParam(node) && !this._sourceStarted){
|
|
this._proxies.push(node);
|
|
node.overridden = true;
|
|
this._applyAutomations(node);
|
|
} else {
|
|
Tone.SignalBase.prototype.connect.apply(this, arguments);
|
|
if (!this._sourceStarted){
|
|
this._sourceStarted = true;
|
|
this._constantSource.start(0);
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Takes a node as an argument and returns if it is a Param or AudioParam
|
|
* @param {*} node The node to test
|
|
* @return {Boolean}
|
|
* @private
|
|
*/
|
|
Tone.Signal.prototype._isParam = function(node){
|
|
return (Tone.Param && Tone.Param === node.constructor) ||
|
|
node instanceof AudioParam;
|
|
};
|
|
|
|
/**
|
|
* Discard the optimization and connect all of the proxies
|
|
* @private
|
|
*/
|
|
Tone.Signal.prototype._connectProxies = function(){
|
|
if (!this._sourceStarted){
|
|
this._sourceStarted = true;
|
|
this._constantSource.start(0);
|
|
}
|
|
this._proxies.forEach(function(proxy){
|
|
Tone.SignalBase.prototype.connect.call(this, proxy);
|
|
if (proxy._proxies){
|
|
proxy._connectProxies();
|
|
}
|
|
}.bind(this));
|
|
};
|
|
|
|
/**
|
|
* Invoked when a node is connected to this
|
|
* @param {AudioNode} from
|
|
* @private
|
|
*/
|
|
Tone.Signal.prototype._onConnect = function(from){
|
|
if (!this._isParam(from)){
|
|
//connect all the proxies
|
|
this._connectProxies();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Apply all the current automations to the given parameter
|
|
* @param {AudioParam} param
|
|
* @private
|
|
*/
|
|
Tone.Signal.prototype._applyAutomations = function(param){
|
|
var now = this.context.currentTime;
|
|
param.cancelScheduledValues(now);
|
|
var currentVal = this.getValueAtTime(now);
|
|
param.setValueAtTime(currentVal, now);
|
|
this._events.forEachFrom(now, function(event){
|
|
param[event.type](event.value, event.time, event.constant);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Disconnect from the given node or all nodes if no param is given.
|
|
* @param {AudioNode|AudioParam} node
|
|
* @return {Tone.Signal} this
|
|
*/
|
|
Tone.Signal.prototype.disconnect = function(node){
|
|
if (this._proxies.includes(node)){
|
|
var index = this._proxies.indexOf(node);
|
|
this._proxies.splice(index, 1);
|
|
} else if (!node){
|
|
//no argument, disconnect everything
|
|
this._proxies = [];
|
|
}
|
|
if (this._sourceStarted){
|
|
Tone.SignalBase.prototype.disconnect.apply(this, arguments);
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Return the current signal value at the given time.
|
|
* @param {Time} time When to get the signal value
|
|
* @return {Number}
|
|
*/
|
|
Tone.Signal.prototype.getValueAtTime = function(time){
|
|
if (this._param.getValueAtTime){
|
|
return this._param.getValueAtTime(time);
|
|
} else {
|
|
return Tone.Param.prototype.getValueAtTime.call(this, time);
|
|
}
|
|
};
|
|
|
|
//wrap all of the automation methods
|
|
["setValueAtTime", "linearRampToValueAtTime", "exponentialRampToValueAtTime", "setTargetAtTime"]
|
|
.forEach(function(method){
|
|
var previousMethod = Tone.Signal.prototype[method];
|
|
Tone.Signal.prototype[method] = function(){
|
|
var args = arguments;
|
|
previousMethod.apply(this, arguments);
|
|
args[0] = this._fromUnits(args[0]);
|
|
args[1] = this.toSeconds(args[1]);
|
|
//apply it to the proxies
|
|
this._proxies.forEach(function(signal){
|
|
signal[method].apply(signal, args);
|
|
});
|
|
};
|
|
});
|
|
["cancelScheduledValues", "cancelAndHoldAtTime"]
|
|
.forEach(function(method){
|
|
var previousMethod = Tone.Signal.prototype[method];
|
|
Tone.Signal.prototype[method] = function(){
|
|
var args = arguments;
|
|
previousMethod.apply(this, arguments);
|
|
args[0] = this.toSeconds(args[0]);
|
|
//apply it to the proxies
|
|
this._proxies.forEach(function(signal){
|
|
signal[method].apply(signal, args);
|
|
});
|
|
};
|
|
});
|
|
|
|
/**
|
|
* dispose and disconnect
|
|
* @returns {Tone.Signal} this
|
|
*/
|
|
Tone.Signal.prototype.dispose = function(){
|
|
Tone.Param.prototype.dispose.call(this);
|
|
this._constantSource.disconnect();
|
|
this._constantSource = null;
|
|
this._proxies = null;
|
|
return this;
|
|
};
|
|
|
|
return Tone.Signal;
|
|
});
|