Tone.js/Tone/signal/Signal.js

378 lines
11 KiB
JavaScript
Raw Normal View History

2015-07-18 18:59:18 +00:00
define(["Tone/core/Tone", "Tone/signal/WaveShaper", "Tone/core/Types"], function(Tone){
"use strict";
2014-06-14 23:11:37 +00:00
/**
2015-06-20 19:50:57 +00:00
* @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
2015-06-19 04:52:04 +00:00
* has all of the methods available to native Web Audio
2015-07-04 19:25:37 +00:00
* [AudioParam](http://webaudio.github.io/web-audio-api/#the-audioparam-interface)
2015-06-20 19:50:57 +00:00
* as well as additional conveniences. Read more about working with signals
2015-07-04 19:25:37 +00:00
* [here](https://github.com/TONEnoTONE/Tone.js/wiki/Signals).
2014-06-17 16:15:10 +00:00
*
2014-06-14 23:11:37 +00:00
* @constructor
* @extends {Tone.SignalBase}
2015-06-19 04:52:04 +00:00
* @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.
2015-02-27 18:40:35 +00:00
* @example
2015-06-14 05:17:09 +00:00
* var signal = new Tone.Signal(10);
2014-06-14 23:11:37 +00:00
*/
Tone.Signal = function(){
var options = this.optionsObject(arguments, ["value", "units"], Tone.Signal.defaults);
/**
2015-06-19 04:52:04 +00:00
* The units of the signal.
* @type {string}
*/
this.units = options.units;
/**
* When true, converts the set value
* based on the units given. When false,
* applies no conversion and the units
* are merely used as a label.
* @type {boolean}
*/
this.convert = options.convert;
/**
* True if the signal value is being overridden by
* a connected signal.
* @readOnly
* @type {boolean}
2015-06-19 04:52:04 +00:00
* @private
*/
this.overridden = false;
2015-02-13 15:52:38 +00:00
/**
2015-02-21 19:05:58 +00:00
* The node where the constant signal value is scaled.
* @type {GainNode}
2015-02-13 15:52:38 +00:00
* @private
*/
2015-02-21 19:05:58 +00:00
this.output = this._scaler = this.context.createGain();
/**
2015-02-21 19:05:58 +00:00
* The node where the value is set.
* @type {AudioParam}
* @private
*/
2015-02-21 19:05:58 +00:00
this.input = this._value = this._scaler.gain;
if (options.value instanceof AudioParam){
this._scaler.connect(options.value);
2015-02-21 19:05:58 +00:00
//zero out the value
options.value.value = 0;
} else {
2015-06-07 17:16:50 +00:00
if (!this.isUndef(options.param)){
this._scaler.connect(options.param);
options.param.value = 0;
}
this.value = options.value;
}
//connect the constant 1 output to the node output
2015-02-21 19:05:58 +00:00
Tone.Signal._constant.chain(this._scaler);
2014-06-14 23:11:37 +00:00
};
Tone.extend(Tone.Signal, Tone.SignalBase);
2015-02-23 05:31:05 +00:00
/**
* The default values
* @type {Object}
* @static
* @const
*/
Tone.Signal.defaults = {
"value" : 0,
2015-06-07 17:16:50 +00:00
"param" : undefined,
2015-05-24 13:45:15 +00:00
"units" : Tone.Type.Default,
"convert" : true,
2015-02-23 05:31:05 +00:00
};
/**
2015-06-20 19:50:57 +00:00
* The current value of the signal.
* @memberOf Tone.Signal#
2015-06-19 04:52:04 +00:00
* @type {Number}
* @name value
*/
Object.defineProperty(Tone.Signal.prototype, "value", {
get : function(){
2015-02-13 15:52:38 +00:00
return this._toUnits(this._value.value);
},
set : function(value){
var convertedVal = this._fromUnits(value);
2015-02-21 19:05:58 +00:00
//is this what you want?
this.cancelScheduledValues(0);
2015-02-13 15:52:38 +00:00
this._value.value = convertedVal;
}
});
/**
* @private
2015-06-14 00:20:36 +00:00
* @param {*} val the value to convert
* @return {number} the number which the value should be set to
*/
Tone.Signal.prototype._fromUnits = function(val){
if (this.convert || this.isUndef(this.convert)){
switch(this.units){
case Tone.Type.Time:
return this.toSeconds(val);
case Tone.Type.Frequency:
return this.toFrequency(val);
case Tone.Type.Decibels:
return this.dbToGain(val);
2015-05-24 13:45:15 +00:00
case Tone.Type.NormalRange:
return Math.min(Math.max(val, 0), 1);
case Tone.Type.AudioRange:
return Math.min(Math.max(val, -1), 1);
case Tone.Type.Positive:
return Math.max(val, 0);
default:
return val;
}
} else {
return val;
}
};
/**
* convert to the desired units
* @private
* @param {number} val the value to convert
* @return {number}
*/
Tone.Signal.prototype._toUnits = function(val){
if (this.convert || this.isUndef(this.convert)){
switch(this.units){
case Tone.Type.Decibels:
return this.gainToDb(val);
default:
return val;
}
} else {
return val;
}
};
2014-06-14 23:11:37 +00:00
/**
* Schedules a parameter value change at the given time.
2015-06-19 04:52:04 +00:00
* @param {*} value The value to set the signal.
* @param {Time} time The time when the change should occur.
* @returns {Tone.Signal} this
2015-06-19 04:52:04 +00:00
* @example
* //set the frequency to "G4" in exactly 1 second from now.
* freq.setValueAtTime("G4", "+1");
2014-06-14 23:11:37 +00:00
*/
Tone.Signal.prototype.setValueAtTime = function(value, time){
value = this._fromUnits(value);
2015-02-13 15:52:38 +00:00
this._value.setValueAtTime(value, this.toSeconds(time));
2015-02-02 03:56:33 +00:00
return this;
2014-06-14 23:11:37 +00:00
};
/**
2015-02-27 18:40:35 +00:00
* Creates a schedule point with the current value at the current time.
2015-06-19 04:52:04 +00:00
* This is useful for creating an automation anchor point in order to
* schedule changes from the current value.
*
2015-06-19 04:52:04 +00:00
* @param {number=} now (Optionally) pass the now value in.
* @returns {Tone.Signal} this
*/
Tone.Signal.prototype.setCurrentValueNow = function(now){
now = this.defaultArg(now, this.now());
2015-02-13 15:52:38 +00:00
var currentVal = this._value.value;
this.cancelScheduledValues(now);
2015-02-13 15:52:38 +00:00
this._value.setValueAtTime(currentVal, now);
2015-02-02 03:56:33 +00:00
return this;
};
2014-06-14 23:11:37 +00:00
/**
* Schedules a linear continuous change in parameter value from the
* previous scheduled parameter value to the given value.
*
* @param {number} value
2015-06-14 00:20:36 +00:00
* @param {Time} endTime
* @returns {Tone.Signal} this
2014-06-14 23:11:37 +00:00
*/
Tone.Signal.prototype.linearRampToValueAtTime = function(value, endTime){
value = this._fromUnits(value);
2015-02-13 15:52:38 +00:00
this._value.linearRampToValueAtTime(value, this.toSeconds(endTime));
2015-02-02 03:56:33 +00:00
return this;
2014-06-14 23:11:37 +00:00
};
/**
* Schedules an exponential continuous change in parameter value from
* the previous scheduled parameter value to the given value.
*
* @param {number} value
2015-06-14 00:20:36 +00:00
* @param {Time} endTime
* @returns {Tone.Signal} this
2014-06-14 23:11:37 +00:00
*/
Tone.Signal.prototype.exponentialRampToValueAtTime = function(value, endTime){
value = this._fromUnits(value);
value = Math.max(0.00001, value);
2015-02-13 15:52:38 +00:00
this._value.exponentialRampToValueAtTime(value, this.toSeconds(endTime));
2015-02-02 03:56:33 +00:00
return this;
2014-06-14 23:11:37 +00:00
};
/**
* Schedules an exponential continuous change in parameter value from
* the current time and current value to the given value.
*
* @param {number} value
2015-06-14 00:20:36 +00:00
* @param {Time} rampTime the time that it takes the
* value to ramp from it's current value
* @returns {Tone.Signal} this
2015-02-27 18:40:35 +00:00
* @example
2015-06-14 01:54:20 +00:00
* //exponentially ramp to the value 2 over 4 seconds.
* signal.exponentialRampToValueNow(2, 4);
*/
Tone.Signal.prototype.exponentialRampToValueNow = function(value, rampTime){
var now = this.now();
// exponentialRampToValueAt cannot ever ramp from 0, apparently.
// More info: https://bugzilla.mozilla.org/show_bug.cgi?id=1125600#c2
var currentVal = this.value;
this.setValueAtTime(Math.max(currentVal, 0.0001), now);
this.exponentialRampToValueAtTime(value, now + this.toSeconds(rampTime));
2015-02-02 03:56:33 +00:00
return this;
};
/**
* Schedules an linear continuous change in parameter value from
* the current time and current value to the given value at the given time.
*
* @param {number} value
2015-06-14 00:20:36 +00:00
* @param {Time} rampTime the time that it takes the
* value to ramp from it's current value
* @returns {Tone.Signal} this
2015-02-27 18:40:35 +00:00
* @example
2015-06-14 01:54:20 +00:00
* //linearly ramp to the value 4 over 3 seconds.
* signal.linearRampToValueNow(4, 3);
*/
Tone.Signal.prototype.linearRampToValueNow = function(value, rampTime){
var now = this.now();
this.setCurrentValueNow(now);
2015-02-13 15:52:38 +00:00
this.linearRampToValueAtTime(value, now + this.toSeconds(rampTime));
2015-02-02 03:56:33 +00:00
return this;
};
2014-06-14 23:11:37 +00:00
/**
* Start exponentially approaching the target value at the given time with
* a rate having the given time constant.
* @param {number} value
2015-06-14 00:20:36 +00:00
* @param {Time} startTime
2014-06-14 23:11:37 +00:00
* @param {number} timeConstant
* @returns {Tone.Signal} this
2014-06-14 23:11:37 +00:00
*/
Tone.Signal.prototype.setTargetAtTime = function(value, startTime, timeConstant){
value = this._fromUnits(value);
2015-03-21 22:10:03 +00:00
// The value will never be able to approach without timeConstant > 0.
// http://www.w3.org/TR/webaudio/#dfn-setTargetAtTime, where the equation
// is described. 0 results in a division by 0.
timeConstant = Math.max(0.00001, timeConstant);
2015-02-13 15:52:38 +00:00
this._value.setTargetAtTime(value, this.toSeconds(startTime), timeConstant);
2015-02-02 03:56:33 +00:00
return this;
2014-06-14 23:11:37 +00:00
};
/**
* Sets an array of arbitrary parameter values starting at the given time
* for the given duration.
*
2015-06-14 00:56:41 +00:00
* @param {Array} values
2015-06-14 00:20:36 +00:00
* @param {Time} startTime
* @param {Time} duration
* @returns {Tone.Signal} this
2014-06-14 23:11:37 +00:00
*/
Tone.Signal.prototype.setValueCurveAtTime = function(values, startTime, duration){
2014-06-14 23:11:37 +00:00
for (var i = 0; i < values.length; i++){
values[i] = this._fromUnits(values[i]);
2014-06-14 23:11:37 +00:00
}
2015-02-13 15:52:38 +00:00
this._value.setValueCurveAtTime(values, this.toSeconds(startTime), this.toSeconds(duration));
2015-02-02 03:56:33 +00:00
return this;
2014-06-14 23:11:37 +00:00
};
/**
* Cancels all scheduled parameter changes with times greater than or
* equal to startTime.
*
2015-06-14 00:20:36 +00:00
* @param {Time} startTime
* @returns {Tone.Signal} this
2014-06-14 23:11:37 +00:00
*/
Tone.Signal.prototype.cancelScheduledValues = function(startTime){
2015-02-13 15:52:38 +00:00
this._value.cancelScheduledValues(this.toSeconds(startTime));
2015-02-02 03:56:33 +00:00
return this;
2014-06-14 23:11:37 +00:00
};
/**
* Ramps to the given value over the duration of the rampTime.
* Automatically selects the best ramp type (exponential or linear)
* depending on the `units` of the signal
*
* @param {number} value
2015-06-14 00:20:36 +00:00
* @param {Time} rampTime the time that it takes the
* value to ramp from it's current value
* @returns {Tone.Signal} this
2015-02-27 18:40:35 +00:00
* @example
2015-06-14 01:54:20 +00:00
* //ramp to the value either linearly or exponentially
* //depending on the "units" value of the signal
* signal.rampTo(0, 10);
*/
Tone.Signal.prototype.rampTo = function(value, rampTime){
2015-02-10 21:33:55 +00:00
rampTime = this.defaultArg(rampTime, 0);
if (this.units === Tone.Type.Frequency || this.units === Tone.Type.BPM){
this.exponentialRampToValueNow(value, rampTime);
} else {
this.linearRampToValueNow(value, rampTime);
}
return this;
};
/**
2015-02-02 01:02:30 +00:00
* dispose and disconnect
* @returns {Tone.Signal} this
*/
2015-02-02 03:05:24 +00:00
Tone.Signal.prototype.dispose = function(){
Tone.prototype.dispose.call(this);
2015-02-13 15:52:38 +00:00
this._value = null;
2015-02-21 19:05:58 +00:00
this._scaler = null;
2015-02-02 03:56:33 +00:00
return this;
};
2014-07-30 17:56:32 +00:00
///////////////////////////////////////////////////////////////////////////
// STATIC
///////////////////////////////////////////////////////////////////////////
/**
* the constant signal generator
2014-07-30 17:56:32 +00:00
* @static
* @private
* @const
* @type {OscillatorNode}
2014-07-30 17:56:32 +00:00
*/
Tone.Signal._generator = null;
2014-07-30 17:56:32 +00:00
/**
* the signal generator waveshaper. makes the incoming signal
* only output 1 for all inputs.
2014-07-30 17:56:32 +00:00
* @static
* @private
* @const
* @type {Tone.WaveShaper}
2014-07-30 17:56:32 +00:00
*/
Tone.Signal._constant = null;
2014-07-30 17:56:32 +00:00
/**
* initializer function
*/
Tone._initAudioContext(function(audioContext){
Tone.Signal._generator = audioContext.createOscillator();
Tone.Signal._constant = new Tone.WaveShaper([1,1]);
Tone.Signal._generator.connect(Tone.Signal._constant);
Tone.Signal._generator.start(0);
Tone.Signal._generator.noGC();
2014-07-30 17:56:32 +00:00
});
return Tone.Signal;
2014-06-14 23:11:37 +00:00
});