mirror of
https://github.com/Tonejs/Tone.js
synced 2024-11-16 00:27:58 +00:00
ba1900dab6
-use while loop with a flag -fixes #461
851 lines
22 KiB
JavaScript
851 lines
22 KiB
JavaScript
/**
|
|
* Tone.js
|
|
* @author Yotam Mann
|
|
* @license http://opensource.org/licenses/MIT MIT License
|
|
* @copyright 2014-2019 Yotam Mann
|
|
*/
|
|
import version from "../version";
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// TONE
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* @class Tone is the base class of all other classes.
|
|
* @constructor
|
|
*/
|
|
var Tone = function(){
|
|
if (!(this instanceof Tone)){
|
|
throw new Error("constructor needs to be called with the 'new' keyword");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @memberOf Tone#
|
|
* @returns {String} returns the name of the class as a string
|
|
*/
|
|
Tone.prototype.toString = function(){
|
|
for (var className in Tone){
|
|
var isLetter = className[0].match(/^[A-Z]$/);
|
|
var sameConstructor = Tone[className] === this.constructor;
|
|
if (Tone.isFunction(Tone[className]) && isLetter && sameConstructor){
|
|
return className;
|
|
}
|
|
}
|
|
return "Tone";
|
|
};
|
|
|
|
/**
|
|
* @memberOf Tone#
|
|
* disconnect and dispose
|
|
* @returns {Tone} this
|
|
*/
|
|
Tone.prototype.dispose = function(){
|
|
return this;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GET/SET
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Set the parameters at once. Either pass in an
|
|
* object mapping parameters to values, or to set a
|
|
* single parameter, by passing in a string and value.
|
|
* The last argument is an optional ramp time which
|
|
* will ramp any signal values to their destination value
|
|
* over the duration of the rampTime.
|
|
* @param {Object|String} params
|
|
* @param {Number=} value
|
|
* @returns {Tone} this
|
|
* @memberOf Tone#
|
|
* @example
|
|
* //set values using an object
|
|
* filter.set({
|
|
* "frequency" : 300,
|
|
* "type" : highpass
|
|
* });
|
|
* @example
|
|
* filter.set("type", "highpass");
|
|
* @example
|
|
* //ramp to the value 220 over 3 seconds.
|
|
* oscillator.set({
|
|
* "frequency" : 220
|
|
* }, 3);
|
|
*/
|
|
Tone.prototype.set = function(params, value){
|
|
if (Tone.isString(params)){
|
|
var tmpObj = {};
|
|
tmpObj[params] = value;
|
|
params = tmpObj;
|
|
}
|
|
|
|
paramLoop:
|
|
for (var attr in params){
|
|
value = params[attr];
|
|
var parent = this;
|
|
if (attr.indexOf(".") !== -1){
|
|
var attrSplit = attr.split(".");
|
|
for (var i = 0; i < attrSplit.length - 1; i++){
|
|
parent = parent[attrSplit[i]];
|
|
if (parent instanceof Tone){
|
|
attrSplit.splice(0, i+1);
|
|
var innerParam = attrSplit.join(".");
|
|
parent.set(innerParam, value);
|
|
continue paramLoop;
|
|
}
|
|
}
|
|
attr = attrSplit[attrSplit.length - 1];
|
|
}
|
|
var param = parent[attr];
|
|
if (Tone.isUndef(param)){
|
|
continue;
|
|
}
|
|
if ((Tone.Signal && param instanceof Tone.Signal) ||
|
|
(Tone.Param && param instanceof Tone.Param)){
|
|
if (param.value !== value){
|
|
param.value = value;
|
|
}
|
|
} else if (param instanceof AudioParam){
|
|
if (param.value !== value){
|
|
param.value = value;
|
|
}
|
|
} else if (Tone.TimeBase && param instanceof Tone.TimeBase){
|
|
parent[attr] = value;
|
|
} else if (param instanceof Tone){
|
|
param.set(value);
|
|
} else if (param !== value){
|
|
parent[attr] = value;
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Get the object's attributes. Given no arguments get
|
|
* will return all available object properties and their corresponding
|
|
* values. Pass in a single attribute to retrieve or an array
|
|
* of attributes. The attribute strings can also include a "."
|
|
* to access deeper properties.
|
|
* @memberOf Tone#
|
|
* @example
|
|
* osc.get();
|
|
* //returns {"type" : "sine", "frequency" : 440, ...etc}
|
|
* @example
|
|
* osc.get("type");
|
|
* //returns { "type" : "sine"}
|
|
* @example
|
|
* //use dot notation to access deep properties
|
|
* synth.get(["envelope.attack", "envelope.release"]);
|
|
* //returns {"envelope" : {"attack" : 0.2, "release" : 0.4}}
|
|
* @param {Array=|string|undefined} params the parameters to get, otherwise will return
|
|
* all available.
|
|
* @returns {Object}
|
|
*/
|
|
Tone.prototype.get = function(params){
|
|
if (Tone.isUndef(params)){
|
|
params = this._collectDefaults(this.constructor);
|
|
} else if (Tone.isString(params)){
|
|
params = [params];
|
|
}
|
|
var ret = {};
|
|
for (var i = 0; i < params.length; i++){
|
|
var attr = params[i];
|
|
var parent = this;
|
|
var subRet = ret;
|
|
if (attr.indexOf(".") !== -1){
|
|
var attrSplit = attr.split(".");
|
|
for (var j = 0; j < attrSplit.length - 1; j++){
|
|
var subAttr = attrSplit[j];
|
|
subRet[subAttr] = subRet[subAttr] || {};
|
|
subRet = subRet[subAttr];
|
|
parent = parent[subAttr];
|
|
}
|
|
attr = attrSplit[attrSplit.length - 1];
|
|
}
|
|
var param = parent[attr];
|
|
if (Tone.isObject(params[attr])){
|
|
subRet[attr] = param.get();
|
|
} else if (Tone.Signal && param instanceof Tone.Signal){
|
|
subRet[attr] = param.value;
|
|
} else if (Tone.Param && param instanceof Tone.Param){
|
|
subRet[attr] = param.value;
|
|
} else if (param instanceof AudioParam){
|
|
subRet[attr] = param.value;
|
|
} else if (param instanceof Tone){
|
|
subRet[attr] = param.get();
|
|
} else if (!Tone.isFunction(param) && Tone.isDefined(param)){
|
|
subRet[attr] = param;
|
|
}
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* collect all of the default attributes in one
|
|
* @private
|
|
* @param {Function} constr the constructor to find the defaults from
|
|
* @return {Array} all of the attributes which belong to the class
|
|
*/
|
|
Tone.prototype._collectDefaults = function(constr){
|
|
var ret = [];
|
|
if (Tone.isDefined(constr.defaults)){
|
|
ret = Object.keys(constr.defaults);
|
|
}
|
|
if (Tone.isDefined(constr._super)){
|
|
var superDefs = this._collectDefaults(constr._super);
|
|
//filter out repeats
|
|
for (var i = 0; i < superDefs.length; i++){
|
|
if (ret.indexOf(superDefs[i]) === -1){
|
|
ret.push(superDefs[i]);
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// DEFAULTS
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* @memberOf Tone
|
|
* @param {Array} values The arguments array
|
|
* @param {Array} keys The names of the arguments
|
|
* @param {Function|Object} constr The class constructor
|
|
* @return {Object} An object composed of the defaults between the class' defaults
|
|
* and the passed in arguments.
|
|
*/
|
|
Tone.defaults = function(values, keys, constr){
|
|
var options = {};
|
|
if (values.length === 1 && Tone.isObject(values[0])){
|
|
options = values[0];
|
|
} else {
|
|
for (var i = 0; i < keys.length; i++){
|
|
options[keys[i]] = values[i];
|
|
}
|
|
}
|
|
if (Tone.isDefined(constr.defaults)){
|
|
return Tone.defaultArg(options, constr.defaults);
|
|
} else if (Tone.isObject(constr)){
|
|
return Tone.defaultArg(options, constr);
|
|
} else {
|
|
return options;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* If the `given` parameter is undefined, use the `fallback`.
|
|
* If both `given` and `fallback` are object literals, it will
|
|
* return a deep copy which includes all of the parameters from both
|
|
* objects. If a parameter is undefined in given, it will return
|
|
* the fallback property.
|
|
* <br><br>
|
|
* WARNING: if object is self referential, it will go into an an
|
|
* infinite recursive loop.
|
|
* @memberOf Tone
|
|
* @param {*} given
|
|
* @param {*} fallback
|
|
* @return {*}
|
|
*/
|
|
Tone.defaultArg = function(given, fallback){
|
|
if (Tone.isObject(given) && Tone.isObject(fallback)){
|
|
var ret = {};
|
|
//make a deep copy of the given object
|
|
for (var givenProp in given){
|
|
ret[givenProp] = Tone.defaultArg(fallback[givenProp], given[givenProp]);
|
|
}
|
|
for (var fallbackProp in fallback){
|
|
ret[fallbackProp] = Tone.defaultArg(given[fallbackProp], fallback[fallbackProp]);
|
|
}
|
|
return ret;
|
|
} else {
|
|
return Tone.isUndef(given) ? fallback : given;
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// DEBUGGING
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Print the outputs to the console log for debugging purposes.
|
|
* Prints the contents only if either the object has a property
|
|
* called `debug` set to true, or a variable called TONE_DEBUG_CLASS
|
|
* is set to the name of the class.
|
|
* @example
|
|
* //prints all logs originating from Tone.OscillatorNode
|
|
* Tone.global.TONE_DEBUG_CLASS = "OscillatorNode"
|
|
* @param {*} args Any arguments to print to the console.
|
|
* @private
|
|
*/
|
|
Tone.prototype.log = function(){
|
|
//if the object is either set to debug = true
|
|
//or if there is a string on the Tone.global.with the class name
|
|
if (this.debug || this.toString() === Tone.global.TONE_DEBUG_CLASS){
|
|
var args = Array.from(arguments);
|
|
args.unshift(this.toString()+":");
|
|
// eslint-disable-next-line no-console
|
|
console.log.apply(undefined, args);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Assert that the statement is true, otherwise invoke the error.
|
|
* @param {Boolean} statement
|
|
* @param {String} error The message which is passed into an Error
|
|
* @private
|
|
*/
|
|
Tone.prototype.assert = function(statement, error){
|
|
if (!statement){
|
|
throw new Error(error);
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// CONNECTIONS
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* connect together all of the arguments in series
|
|
* @param {...(AudioParam|Tone|AudioNode)} nodes
|
|
* @returns {Tone}
|
|
* @memberOf Tone
|
|
* @static
|
|
*/
|
|
Tone.connectSeries = function(){
|
|
var currentUnit = arguments[0];
|
|
for (var i = 1; i < arguments.length; i++){
|
|
var toUnit = arguments[i];
|
|
Tone.connect(currentUnit, toUnit);
|
|
currentUnit = toUnit;
|
|
}
|
|
return Tone;
|
|
};
|
|
|
|
/**
|
|
* Connect two nodes together so that signal flows from the
|
|
* first node to the second. The second node can be an AudioParam.
|
|
* Optionally specific the input and output channels.
|
|
* @param {(AudioNode|Tone.AudioNode)} srcNode The source node
|
|
* @param {(AudioNode|Tone.AudioNode|AudioParam|Tone.AudioParam)} dstNode The destination node
|
|
* @param {number} [outputNumber=0] The output channel of the srcNode
|
|
* @param {number} [inputNumber=0] The input channel of the dstNode
|
|
*/
|
|
Tone.connect = function(srcNode, dstNode, outputNumber, inputNumber){
|
|
|
|
//resolve the input of the dstNode
|
|
while (Tone.isDefined(dstNode.input)){
|
|
if (Tone.isArray(dstNode.input)){
|
|
inputNumber = Tone.defaultArg(inputNumber, 0);
|
|
dstNode = dstNode.input[inputNumber];
|
|
inputNumber = 0;
|
|
} else if (dstNode.input){
|
|
dstNode = dstNode.input;
|
|
}
|
|
}
|
|
|
|
//make the connection
|
|
if (dstNode instanceof AudioParam){
|
|
srcNode.connect(dstNode, outputNumber);
|
|
} else if (dstNode instanceof AudioNode){
|
|
srcNode.connect(dstNode, outputNumber, inputNumber);
|
|
}
|
|
|
|
return Tone;
|
|
};
|
|
|
|
/**
|
|
* Disconnect a node from all nodes or optionally include a destination node and input/output channels.
|
|
* @param {(AudioNode|Tone.AudioNode)} srcNode The source node
|
|
* @param {?(AudioNode|Tone.AudioNode|AudioParam|Tone.AudioParam)} dstNode The destination node
|
|
* @param {?number} [outputNumber=0] The output channel of the srcNode
|
|
* @param {?number} [inputNumber=0] The input channel of the dstNode
|
|
*/
|
|
Tone.disconnect = function(srcNode, dstNode, outputNumber, inputNumber){
|
|
if (dstNode){
|
|
//resolve the input of the dstNode
|
|
var bDone = false;
|
|
while (!bDone){
|
|
if (Tone.isArray(dstNode.input)){
|
|
if (Tone.isDefined(inputNumber)){
|
|
Tone.disconnect(srcNode, dstNode.input[inputNumber], outputNumber);
|
|
} else {
|
|
dstNode.input.forEach(function(dstNode){
|
|
//ignore errors from connections that aren't there
|
|
try {
|
|
Tone.disconnect(srcNode, dstNode, outputNumber);
|
|
// eslint-disable-next-line
|
|
} catch (e) {}
|
|
});
|
|
}
|
|
bDone = true;
|
|
} else if (dstNode.input){
|
|
dstNode = dstNode.input;
|
|
} else {
|
|
bDone = true;
|
|
}
|
|
}
|
|
|
|
//make the connection
|
|
if (dstNode instanceof AudioParam){
|
|
srcNode.disconnect(dstNode, outputNumber);
|
|
} else if (dstNode instanceof AudioNode){
|
|
srcNode.disconnect(dstNode, outputNumber, inputNumber);
|
|
}
|
|
} else {
|
|
srcNode.disconnect();
|
|
}
|
|
|
|
return Tone;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// TYPE CHECKING
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Test if the arg is undefined
|
|
* @param {*} arg the argument to test
|
|
* @returns {Boolean} true if the arg is undefined
|
|
* @static
|
|
* @memberOf Tone
|
|
*/
|
|
Tone.isUndef = function(val){
|
|
return typeof val === "undefined";
|
|
};
|
|
|
|
/**
|
|
* Test if the arg is not undefined
|
|
* @param {*} arg the argument to test
|
|
* @returns {Boolean} true if the arg is undefined
|
|
* @static
|
|
* @memberOf Tone
|
|
*/
|
|
Tone.isDefined = function(val){
|
|
return !Tone.isUndef(val);
|
|
};
|
|
|
|
/**
|
|
* Test if the arg is a function
|
|
* @param {*} arg the argument to test
|
|
* @returns {Boolean} true if the arg is a function
|
|
* @static
|
|
* @memberOf Tone
|
|
*/
|
|
Tone.isFunction = function(val){
|
|
return typeof val === "function";
|
|
};
|
|
|
|
/**
|
|
* Test if the argument is a number.
|
|
* @param {*} arg the argument to test
|
|
* @returns {Boolean} true if the arg is a number
|
|
* @static
|
|
* @memberOf Tone
|
|
*/
|
|
Tone.isNumber = function(arg){
|
|
return (typeof arg === "number");
|
|
};
|
|
|
|
/**
|
|
* Test if the given argument is an object literal (i.e. `{}`);
|
|
* @param {*} arg the argument to test
|
|
* @returns {Boolean} true if the arg is an object literal.
|
|
* @static
|
|
* @memberOf Tone
|
|
*/
|
|
Tone.isObject = function(arg){
|
|
return (Object.prototype.toString.call(arg) === "[object Object]" && arg.constructor === Object);
|
|
};
|
|
|
|
/**
|
|
* Test if the argument is a boolean.
|
|
* @param {*} arg the argument to test
|
|
* @returns {Boolean} true if the arg is a boolean
|
|
* @static
|
|
* @memberOf Tone
|
|
*/
|
|
Tone.isBoolean = function(arg){
|
|
return (typeof arg === "boolean");
|
|
};
|
|
|
|
/**
|
|
* Test if the argument is an Array
|
|
* @param {*} arg the argument to test
|
|
* @returns {Boolean} true if the arg is an array
|
|
* @static
|
|
* @memberOf Tone
|
|
*/
|
|
Tone.isArray = function(arg){
|
|
return (Array.isArray(arg));
|
|
};
|
|
|
|
/**
|
|
* Test if the argument is a string.
|
|
* @param {*} arg the argument to test
|
|
* @returns {Boolean} true if the arg is a string
|
|
* @static
|
|
* @memberOf Tone
|
|
*/
|
|
Tone.isString = function(arg){
|
|
return (typeof arg === "string");
|
|
};
|
|
|
|
/**
|
|
* Test if the argument is in the form of a note in scientific pitch notation.
|
|
* e.g. "C4"
|
|
* @param {*} arg the argument to test
|
|
* @returns {Boolean} true if the arg is a string
|
|
* @static
|
|
* @memberOf Tone
|
|
*/
|
|
Tone.isNote = function(arg){
|
|
return Tone.isString(arg) && /^([a-g]{1}(?:b|#|x|bb)?)(-?[0-9]+)/i.test(arg);
|
|
};
|
|
|
|
/**
|
|
* An empty function.
|
|
* @static
|
|
*/
|
|
Tone.noOp = function(){};
|
|
|
|
/**
|
|
* Make the property not writable. Internal use only.
|
|
* @private
|
|
* @param {String} property the property to make not writable
|
|
*/
|
|
Tone.prototype._readOnly = function(property){
|
|
if (Array.isArray(property)){
|
|
for (var i = 0; i < property.length; i++){
|
|
this._readOnly(property[i]);
|
|
}
|
|
} else {
|
|
Object.defineProperty(this, property, {
|
|
"writable" : false,
|
|
"enumerable" : true,
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Make an attribute writeable. Interal use only.
|
|
* @private
|
|
* @param {String} property the property to make writable
|
|
*/
|
|
Tone.prototype._writable = function(property){
|
|
if (Array.isArray(property)){
|
|
for (var i = 0; i < property.length; i++){
|
|
this._writable(property[i]);
|
|
}
|
|
} else {
|
|
Object.defineProperty(this, property, {
|
|
"writable" : true,
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Possible play states.
|
|
* @enum {String}
|
|
*/
|
|
Tone.State = {
|
|
"Started" : "started",
|
|
"Stopped" : "stopped",
|
|
"Paused" : "paused",
|
|
};
|
|
|
|
/**
|
|
* A reference to the global context, `global` or `Tone.global.
|
|
*/
|
|
Tone.global = Tone.isUndef(global) ? window : global;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// CONVERSIONS
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Equal power gain scale. Good for cross-fading.
|
|
* @param {NormalRange} percent (0-1)
|
|
* @return {Number} output gain (0-1)
|
|
* @static
|
|
* @memberOf Tone
|
|
*/
|
|
Tone.equalPowerScale = function(percent){
|
|
var piFactor = 0.5 * Math.PI;
|
|
return Math.sin(percent * piFactor);
|
|
};
|
|
|
|
/**
|
|
* Convert decibels into gain.
|
|
* @param {Decibels} db
|
|
* @return {Number}
|
|
* @static
|
|
* @memberOf Tone
|
|
*/
|
|
Tone.dbToGain = function(db){
|
|
return Math.pow(10, db / 20);
|
|
};
|
|
|
|
/**
|
|
* Convert gain to decibels.
|
|
* @param {Number} gain (0-1)
|
|
* @return {Decibels}
|
|
* @static
|
|
* @memberOf Tone
|
|
*/
|
|
Tone.gainToDb = function(gain){
|
|
return 20 * (Math.log(gain) / Math.LN10);
|
|
};
|
|
|
|
/**
|
|
* Convert an interval (in semitones) to a frequency ratio.
|
|
* @param {Interval} interval the number of semitones above the base note
|
|
* @return {Number} the frequency ratio
|
|
* @static
|
|
* @memberOf Tone
|
|
* @example
|
|
* tone.intervalToFrequencyRatio(0); // 1
|
|
* tone.intervalToFrequencyRatio(12); // 2
|
|
* tone.intervalToFrequencyRatio(-12); // 0.5
|
|
*/
|
|
Tone.intervalToFrequencyRatio = function(interval){
|
|
return Math.pow(2, (interval/12));
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// TIMING
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Return the current time of the AudioContext clock plus
|
|
* the lookAhead.
|
|
* @return {Number} the currentTime from the AudioContext
|
|
* @memberOf Tone#
|
|
*/
|
|
Tone.prototype.now = function(){
|
|
return Tone.context.now();
|
|
};
|
|
|
|
/**
|
|
* Return the current time of the AudioContext clock plus
|
|
* the lookAhead.
|
|
* @return {Number} the currentTime from the AudioContext
|
|
* @static
|
|
* @memberOf Tone
|
|
*/
|
|
Tone.now = function(){
|
|
return Tone.context.now();
|
|
};
|
|
|
|
/**
|
|
* Return the current time of the AudioContext clock without
|
|
* any lookAhead.
|
|
* @return {Number} the currentTime from the AudioContext
|
|
* @memberOf Tone#
|
|
*/
|
|
Tone.prototype.immediate = function(){
|
|
return Tone.context.currentTime;
|
|
};
|
|
|
|
/**
|
|
* Return the current time of the AudioContext clock without
|
|
* any lookAhead.
|
|
* @return {Number} the currentTime from the AudioContext
|
|
* @memberOf Tone
|
|
*/
|
|
Tone.immediate = function(){
|
|
return Tone.context.currentTime;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// INHERITANCE
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* have a child inherit all of Tone's (or a parent's) prototype
|
|
* to inherit the parent's properties, make sure to call
|
|
* Parent.call(this) in the child's constructor
|
|
*
|
|
* based on closure library's inherit function
|
|
*
|
|
* @memberOf Tone
|
|
* @static
|
|
* @param {Function} child
|
|
* @param {Function=} parent (optional) parent to inherit from
|
|
* if no parent is supplied, the child
|
|
* will inherit from Tone
|
|
*/
|
|
Tone.extend = function(child, parent){
|
|
if (Tone.isUndef(parent)){
|
|
parent = Tone;
|
|
}
|
|
function TempConstructor(){}
|
|
TempConstructor.prototype = parent.prototype;
|
|
child.prototype = new TempConstructor();
|
|
/** @override */
|
|
child.prototype.constructor = child;
|
|
child._super = parent;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// CONTEXT
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* The shared AudioContext
|
|
* @type {Tone.Context}
|
|
* @private
|
|
*/
|
|
Tone._audioContext = null;
|
|
|
|
/**
|
|
* Most browsers will not play _any_ audio until a user
|
|
* clicks something (like a play button). Invoke this method
|
|
* on a click or keypress event handler to start the audio context.
|
|
* More about the Autoplay policy [here](https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#webaudio)
|
|
* @memberOf Tone
|
|
* @static
|
|
* @return {Promise} This promise is resolved when the audio context is started.
|
|
* @example
|
|
* document.querySelector('#playbutton').addEventListener('click', () => Tone.start())
|
|
*/
|
|
Tone.start = function(){
|
|
return Tone.context.resume();
|
|
};
|
|
|
|
/**
|
|
* A static pointer to the audio context accessible as Tone.context.
|
|
* @type {Tone.Context}
|
|
* @name context
|
|
* @memberOf Tone
|
|
*/
|
|
Object.defineProperty(Tone, "context", {
|
|
"get" : function(){
|
|
return Tone._audioContext;
|
|
},
|
|
"set" : function(context){
|
|
if (context.isContext){
|
|
Tone._audioContext = context;
|
|
} else {
|
|
Tone._audioContext = new Tone.Context(context);
|
|
}
|
|
//initialize the new audio context
|
|
Tone.Context.emit("init", Tone._audioContext);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* The AudioContext
|
|
* @type {Tone.Context}
|
|
* @name context
|
|
* @memberOf Tone#
|
|
* @readOnly
|
|
*/
|
|
Object.defineProperty(Tone.prototype, "context", {
|
|
"get" : function(){
|
|
return Tone.context;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Tone automatically creates a context on init, but if you are working
|
|
* with other libraries which also create an AudioContext, it can be
|
|
* useful to set your own. If you are going to set your own context,
|
|
* be sure to do it at the start of your code, before creating any objects.
|
|
* @static
|
|
* @param {AudioContext} ctx The new audio context to set
|
|
*/
|
|
Tone.setContext = function(ctx){
|
|
Tone.context = ctx;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// ATTRIBUTES
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* The number of seconds of 1 processing block (128 samples)
|
|
* @type {Number}
|
|
* @name blockTime
|
|
* @memberOf Tone
|
|
* @static
|
|
* @readOnly
|
|
*/
|
|
Object.defineProperty(Tone.prototype, "blockTime", {
|
|
"get" : function(){
|
|
return 128 / this.context.sampleRate;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* The duration in seconds of one sample.
|
|
* @type {Number}
|
|
* @name sampleTime
|
|
* @memberOf Tone
|
|
* @static
|
|
* @readOnly
|
|
*/
|
|
Object.defineProperty(Tone.prototype, "sampleTime", {
|
|
"get" : function(){
|
|
return 1 / this.context.sampleRate;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Whether or not all the technologies that Tone.js relies on are supported by the current browser.
|
|
* @type {Boolean}
|
|
* @name supported
|
|
* @memberOf Tone
|
|
* @readOnly
|
|
* @static
|
|
*/
|
|
Object.defineProperty(Tone, "supported", {
|
|
"get" : function(){
|
|
var hasAudioContext = Tone.global.hasOwnProperty("AudioContext") || Tone.global.hasOwnProperty("webkitAudioContext");
|
|
var hasPromises = Tone.global.hasOwnProperty("Promise");
|
|
return hasAudioContext && hasPromises;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Boolean value if the audio context has been initialized.
|
|
* @type {Boolean}
|
|
* @memberOf Tone
|
|
* @static
|
|
* @name initialized
|
|
* @readOnly
|
|
*/
|
|
Object.defineProperty(Tone, "initialized", {
|
|
"get" : function(){
|
|
return Boolean(Tone.context);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Get the context when it becomes available
|
|
* @param {Function} resolve Callback when the context is initialized
|
|
* @return {Tone}
|
|
*/
|
|
Tone.getContext = function(resolve){
|
|
if (Tone.initialized){
|
|
resolve(Tone.context);
|
|
} else {
|
|
var resCallback = function(){
|
|
resolve(Tone.context);
|
|
Tone.Context.off("init", resCallback);
|
|
};
|
|
Tone.Context.on("init", resCallback);
|
|
}
|
|
return Tone;
|
|
};
|
|
|
|
/**
|
|
* The version number
|
|
* @type {String}
|
|
* @static
|
|
*/
|
|
Tone.version = version;
|
|
|
|
export default Tone;
|
|
|