(function(root, factory){
//UMD
if ( typeof define === "function" && define.amd ) {
define(function() {
return factory();
});
} else if (typeof module === "object") {
module.exports = factory();
} else {
root.Tone = factory();
}
}(this, function(){
"use strict";
var Tone;
//constructs the main Tone object
function Main(func){
Tone = func();
}
//invokes each of the modules with the main Tone object as the argument
function Module(func){
func(Tone);
} /**
* Tone.js
* @author Yotam Mann
* @license http://opensource.org/licenses/MIT MIT License
* @copyright 2014-2018 Yotam Mann
*/
Main(function () {
///////////////////////////////////////////////////////////////////////////
// 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
* @param {Time=} rampTime
* @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, rampTime) {
if (Tone.isObject(params)) {
rampTime = value;
} else 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) {
if (Tone.isUndef(rampTime)) {
param.value = value;
} else {
param.rampTo(value, rampTime);
}
}
} 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.
*
* 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;
}
};
///////////////////////////////////////////////////////////////////////////
// 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];
currentUnit.connect(toUnit);
currentUnit = toUnit;
}
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'
};
///////////////////////////////////////////////////////////////////////////
// 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.
* @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.
* @return {Number} the currentTime from the AudioContext
* @static
* @memberOf Tone
*/
Tone.now = function () {
return Tone.context.now();
};
///////////////////////////////////////////////////////////////////////////
// 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
///////////////////////////////////////////////////////////////////////////
/**
* Private reference to the global AudioContext
* @type {AudioContext}
* @private
*/
var audioContext = null;
/**
* 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 audioContext;
},
set: function (context) {
if (Tone.Context && context instanceof Tone.Context) {
audioContext = context;
} else {
audioContext = new Tone.Context(context);
}
//initialize the new audio context
Tone.Context.emit('init', 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 = window.hasOwnProperty('AudioContext') || window.hasOwnProperty('webkitAudioContext');
var hasPromises = window.hasOwnProperty('Promise');
var hasWorkers = window.hasOwnProperty('Worker');
return hasAudioContext && hasPromises && hasWorkers;
}
});
/**
* Boolean value if the audio context has been initialized.
* @type {Boolean}
* @memberOf Tone
* @static
* @name initialized
*/
Object.defineProperty(Tone, 'initialized', {
get: function () {
return audioContext !== null;
}
});
/**
* 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 = 'r12-dev';
return Tone;
});
Module(function (Tone) {
/**
* @class Tone.Emitter gives classes which extend it
* the ability to listen for and emit events.
* Inspiration and reference from Jerome Etienne's [MicroEvent](https://github.com/jeromeetienne/microevent.js).
* MIT (c) 2011 Jerome Etienne.
*
* @extends {Tone}
*/
Tone.Emitter = function () {
Tone.call(this);
/**
* Contains all of the events.
* @private
* @type {Object}
*/
this._events = {};
};
Tone.extend(Tone.Emitter);
/**
* Bind a callback to a specific event.
* @param {String} event The name of the event to listen for.
* @param {Function} callback The callback to invoke when the
* event is emitted
* @return {Tone.Emitter} this
*/
Tone.Emitter.prototype.on = function (event, callback) {
//split the event
var events = event.split(/\W+/);
for (var i = 0; i < events.length; i++) {
var eventName = events[i];
if (!this._events.hasOwnProperty(eventName)) {
this._events[eventName] = [];
}
this._events[eventName].push(callback);
}
return this;
};
/**
* Bind a callback which is only invoked once
* @param {String} event The name of the event to listen for.
* @param {Function} callback The callback to invoke when the
* event is emitted
* @return {Tone.Emitter} this
*/
Tone.Emitter.prototype.once = function (event, callback) {
var boundCallback = function () {
//invoke the callback
callback.apply(this, arguments);
this.off(event, boundCallback);
}.bind(this);
this.on(event, boundCallback);
return this;
};
/**
* Remove the event listener.
* @param {String} event The event to stop listening to.
* @param {Function=} callback The callback which was bound to
* the event with Tone.Emitter.on.
* If no callback is given, all callbacks
* events are removed.
* @return {Tone.Emitter} this
*/
Tone.Emitter.prototype.off = function (event, callback) {
var events = event.split(/\W+/);
for (var ev = 0; ev < events.length; ev++) {
event = events[ev];
if (this._events.hasOwnProperty(event)) {
if (Tone.isUndef(callback)) {
this._events[event] = [];
} else {
var eventList = this._events[event];
for (var i = 0; i < eventList.length; i++) {
if (eventList[i] === callback) {
eventList.splice(i, 1);
}
}
}
}
}
return this;
};
/**
* Invoke all of the callbacks bound to the event
* with any arguments passed in.
* @param {String} event The name of the event.
* @param {*} args... The arguments to pass to the functions listening.
* @return {Tone.Emitter} this
*/
Tone.Emitter.prototype.emit = function (event) {
if (this._events) {
var args = Array.apply(null, arguments).slice(1);
if (this._events.hasOwnProperty(event)) {
var eventList = this._events[event].slice(0);
for (var i = 0, len = eventList.length; i < len; i++) {
eventList[i].apply(this, args);
}
}
}
return this;
};
/**
* Add Emitter functions (on/off/emit) to the object
* @param {Object|Function} object The object or class to extend.
* @returns {Tone.Emitter}
*/
Tone.Emitter.mixin = function (object) {
var functions = [
'on',
'once',
'off',
'emit'
];
object._events = {};
for (var i = 0; i < functions.length; i++) {
var func = functions[i];
var emitterFunc = Tone.Emitter.prototype[func];
object[func] = emitterFunc;
}
return Tone.Emitter;
};
/**
* Clean up
* @return {Tone.Emitter} this
*/
Tone.Emitter.prototype.dispose = function () {
Tone.prototype.dispose.call(this);
this._events = null;
return this;
};
return Tone.Emitter;
});
Module(function (Tone) {
/**
* @class A Timeline class for scheduling and maintaining state
* along a timeline. All events must have a "time" property.
* Internally, events are stored in time order for fast
* retrieval.
* @extends {Tone}
* @param {Positive} [memory=Infinity] The number of previous events that are retained.
*/
Tone.Timeline = function () {
var options = Tone.defaults(arguments, ['memory'], Tone.Timeline);
Tone.call(this);
/**
* The array of scheduled timeline events
* @type {Array}
* @private
*/
this._timeline = [];
/**
* The memory of the timeline, i.e.
* how many events in the past it will retain
* @type {Positive}
*/
this.memory = options.memory;
};
Tone.extend(Tone.Timeline);
/**
* the default parameters
* @static
* @const
*/
Tone.Timeline.defaults = { 'memory': Infinity };
/**
* The number of items in the timeline.
* @type {Number}
* @memberOf Tone.Timeline#
* @name length
* @readOnly
*/
Object.defineProperty(Tone.Timeline.prototype, 'length', {
get: function () {
return this._timeline.length;
}
});
/**
* Insert an event object onto the timeline. Events must have a "time" attribute.
* @param {Object} event The event object to insert into the
* timeline.
* @returns {Tone.Timeline} this
*/
Tone.Timeline.prototype.add = function (event) {
//the event needs to have a time attribute
if (Tone.isUndef(event.time)) {
throw new Error('Tone.Timeline: events must have a time attribute');
}
event.time = event.time.valueOf();
var index = this._search(event.time);
this._timeline.splice(index + 1, 0, event);
//if the length is more than the memory, remove the previous ones
if (this.length > this.memory) {
var diff = this.length - this.memory;
this._timeline.splice(0, diff);
}
return this;
};
/**
* Remove an event from the timeline.
* @param {Object} event The event object to remove from the list.
* @returns {Tone.Timeline} this
*/
Tone.Timeline.prototype.remove = function (event) {
var index = this._timeline.indexOf(event);
if (index !== -1) {
this._timeline.splice(index, 1);
}
return this;
};
/**
* Get the nearest event whose time is less than or equal to the given time.
* @param {Number} time The time to query.
* @param {String} comparator Which value in the object to compare
* @returns {Object} The event object set after that time.
*/
Tone.Timeline.prototype.get = function (time, comparator) {
comparator = Tone.defaultArg(comparator, 'time');
var index = this._search(time, comparator);
if (index !== -1) {
return this._timeline[index];
} else {
return null;
}
};
/**
* Return the first event in the timeline without removing it
* @returns {Object} The first event object
*/
Tone.Timeline.prototype.peek = function () {
return this._timeline[0];
};
/**
* Return the first event in the timeline and remove it
* @returns {Object} The first event object
*/
Tone.Timeline.prototype.shift = function () {
return this._timeline.shift();
};
/**
* Get the event which is scheduled after the given time.
* @param {Number} time The time to query.
* @param {String} comparator Which value in the object to compare
* @returns {Object} The event object after the given time
*/
Tone.Timeline.prototype.getAfter = function (time, comparator) {
comparator = Tone.defaultArg(comparator, 'time');
var index = this._search(time, comparator);
if (index + 1 < this._timeline.length) {
return this._timeline[index + 1];
} else {
return null;
}
};
/**
* Get the event before the event at the given time.
* @param {Number} time The time to query.
* @param {String} comparator Which value in the object to compare
* @returns {Object} The event object before the given time
*/
Tone.Timeline.prototype.getBefore = function (time, comparator) {
comparator = Tone.defaultArg(comparator, 'time');
var len = this._timeline.length;
//if it's after the last item, return the last item
if (len > 0 && this._timeline[len - 1][comparator] < time) {
return this._timeline[len - 1];
}
var index = this._search(time, comparator);
if (index - 1 >= 0) {
return this._timeline[index - 1];
} else {
return null;
}
};
/**
* Cancel events after the given time
* @param {Number} time The time to query.
* @returns {Tone.Timeline} this
*/
Tone.Timeline.prototype.cancel = function (after) {
if (this._timeline.length > 1) {
var index = this._search(after);
if (index >= 0) {
if (this._timeline[index].time === after) {
//get the first item with that time
for (var i = index; i >= 0; i--) {
if (this._timeline[i].time === after) {
index = i;
} else {
break;
}
}
this._timeline = this._timeline.slice(0, index);
} else {
this._timeline = this._timeline.slice(0, index + 1);
}
} else {
this._timeline = [];
}
} else if (this._timeline.length === 1) {
//the first item's time
if (this._timeline[0].time >= after) {
this._timeline = [];
}
}
return this;
};
/**
* Cancel events before or equal to the given time.
* @param {Number} time The time to cancel before.
* @returns {Tone.Timeline} this
*/
Tone.Timeline.prototype.cancelBefore = function (time) {
var index = this._search(time);
if (index >= 0) {
this._timeline = this._timeline.slice(index + 1);
}
return this;
};
/**
* Returns the previous event if there is one. null otherwise
* @param {Object} event The event to find the previous one of
* @return {Object} The event right before the given event
*/
Tone.Timeline.prototype.previousEvent = function (event) {
var index = this._timeline.indexOf(event);
if (index > 0) {
return this._timeline[index - 1];
} else {
return null;
}
};
/**
* Does a binary search on the timeline array and returns the
* nearest event index whose time is after or equal to the given time.
* If a time is searched before the first index in the timeline, -1 is returned.
* If the time is after the end, the index of the last item is returned.
* @param {Number} time
* @param {String} comparator Which value in the object to compare
* @return {Number} the index in the timeline array
* @private
*/
Tone.Timeline.prototype._search = function (time, comparator) {
if (this._timeline.length === 0) {
return -1;
}
comparator = Tone.defaultArg(comparator, 'time');
var beginning = 0;
var len = this._timeline.length;
var end = len;
if (len > 0 && this._timeline[len - 1][comparator] <= time) {
return len - 1;
}
while (beginning < end) {
// calculate the midpoint for roughly equal partition
var midPoint = Math.floor(beginning + (end - beginning) / 2);
var event = this._timeline[midPoint];
var nextEvent = this._timeline[midPoint + 1];
if (event[comparator] === time) {
//choose the last one that has the same time
for (var i = midPoint; i < this._timeline.length; i++) {
var testEvent = this._timeline[i];
if (testEvent[comparator] === time) {
midPoint = i;
}
}
return midPoint;
} else if (event[comparator] < time && nextEvent[comparator] > time) {
return midPoint;
} else if (event[comparator] > time) {
//search lower
end = midPoint;
} else {
//search upper
beginning = midPoint + 1;
}
}
return -1;
};
/**
* Internal iterator. Applies extra safety checks for
* removing items from the array.
* @param {Function} callback
* @param {Number=} lowerBound
* @param {Number=} upperBound
* @private
*/
Tone.Timeline.prototype._iterate = function (callback, lowerBound, upperBound) {
lowerBound = Tone.defaultArg(lowerBound, 0);
upperBound = Tone.defaultArg(upperBound, this._timeline.length - 1);
this._timeline.slice(lowerBound, upperBound + 1).forEach(function (event) {
callback.call(this, event);
}.bind(this));
};
/**
* Iterate over everything in the array
* @param {Function} callback The callback to invoke with every item
* @returns {Tone.Timeline} this
*/
Tone.Timeline.prototype.forEach = function (callback) {
this._iterate(callback);
return this;
};
/**
* Iterate over everything in the array at or before the given time.
* @param {Number} time The time to check if items are before
* @param {Function} callback The callback to invoke with every item
* @returns {Tone.Timeline} this
*/
Tone.Timeline.prototype.forEachBefore = function (time, callback) {
//iterate over the items in reverse so that removing an item doesn't break things
var upperBound = this._search(time);
if (upperBound !== -1) {
this._iterate(callback, 0, upperBound);
}
return this;
};
/**
* Iterate over everything in the array after the given time.
* @param {Number} time The time to check if items are before
* @param {Function} callback The callback to invoke with every item
* @returns {Tone.Timeline} this
*/
Tone.Timeline.prototype.forEachAfter = function (time, callback) {
//iterate over the items in reverse so that removing an item doesn't break things
var lowerBound = this._search(time);
this._iterate(callback, lowerBound + 1);
return this;
};
/**
* Iterate over everything in the array between the startTime and endTime.
* The timerange is inclusive of the startTime, but exclusive of the endTime.
* range = [startTime, endTime).
* @param {Number} startTime The time to check if items are before
* @param {Number} endTime The end of the test interval.
* @param {Function} callback The callback to invoke with every item
* @returns {Tone.Timeline} this
*/
Tone.Timeline.prototype.forEachBetween = function (startTime, endTime, callback) {
var lowerBound = this._search(startTime);
var upperBound = this._search(endTime);
if (lowerBound !== -1 && upperBound !== -1) {
if (this._timeline[lowerBound].time !== startTime) {
lowerBound += 1;
}
//exclusive of the end time
if (this._timeline[upperBound].time === endTime) {
upperBound -= 1;
}
this._iterate(callback, lowerBound, upperBound);
} else if (lowerBound === -1) {
this._iterate(callback, 0, upperBound);
}
return this;
};
/**
* Iterate over everything in the array at or after the given time. Similar to
* forEachAfter, but includes the item(s) at the given time.
* @param {Number} time The time to check if items are before
* @param {Function} callback The callback to invoke with every item
* @returns {Tone.Timeline} this
*/
Tone.Timeline.prototype.forEachFrom = function (time, callback) {
//iterate over the items in reverse so that removing an item doesn't break things
var lowerBound = this._search(time);
//work backwards until the event time is less than time
while (lowerBound >= 0 && this._timeline[lowerBound].time >= time) {
lowerBound--;
}
this._iterate(callback, lowerBound + 1);
return this;
};
/**
* Iterate over everything in the array at the given time
* @param {Number} time The time to check if items are before
* @param {Function} callback The callback to invoke with every item
* @returns {Tone.Timeline} this
*/
Tone.Timeline.prototype.forEachAtTime = function (time, callback) {
//iterate over the items in reverse so that removing an item doesn't break things
var upperBound = this._search(time);
if (upperBound !== -1) {
this._iterate(function (event) {
if (event.time === time) {
callback.call(this, event);
}
}, 0, upperBound);
}
return this;
};
/**
* Clean up.
* @return {Tone.Timeline} this
*/
Tone.Timeline.prototype.dispose = function () {
Tone.prototype.dispose.call(this);
this._timeline = null;
return this;
};
return Tone.Timeline;
});
Module(function (Tone) {
if (Tone.supported) {
if (!window.hasOwnProperty('OfflineAudioContext') && window.hasOwnProperty('webkitOfflineAudioContext')) {
window.OfflineAudioContext = window.webkitOfflineAudioContext;
}
//returns promise?
var context = new OfflineAudioContext(1, 1, 44100);
var ret = context.startRendering();
if (!(ret instanceof Promise)) {
OfflineAudioContext.prototype._native_startRendering = OfflineAudioContext.prototype.startRendering;
OfflineAudioContext.prototype.startRendering = function () {
return new Promise(function (done) {
this.oncomplete = function (e) {
done(e.renderedBuffer);
};
this._native_startRendering();
}.bind(this));
};
}
}
});
Module(function (Tone) {
if (Tone.supported) {
if (!window.hasOwnProperty('AudioContext') && window.hasOwnProperty('webkitAudioContext')) {
window.AudioContext = window.webkitAudioContext;
}
//not functionally equivalent, but only an API placeholder
if (!AudioContext.prototype.close) {
AudioContext.prototype.close = function () {
if (Tone.isFunction(this.suspend)) {
this.suspend();
}
return Promise.resolve();
};
}
//not functionally equivalent
if (!AudioContext.prototype.resume) {
AudioContext.prototype.resume = function () {
return Promise.resolve();
};
}
//createGain
if (!AudioContext.prototype.createGain && AudioContext.prototype.createGainNode) {
AudioContext.prototype.createGain = AudioContext.prototype.createGainNode;
}
//createDelay
if (!AudioContext.prototype.createDelay && AudioContext.prototype.createDelayNode) {
AudioContext.prototype.createDelay = AudioContext.prototype.createDelayNode;
}
//test decodeAudioData returns a promise
// https://github.com/mohayonao/web-audio-api-shim/blob/master/src/AudioContext.js
// MIT License (c) 2015 @mohayonao
var decodeAudioDataPromise = false;
var offlineContext = new OfflineAudioContext(1, 1, 44100);
var audioData = new Uint32Array([
1179011410,
48,
1163280727,
544501094,
16,
131073,
44100,
176400,
1048580,
1635017060,
8,
0,
0,
0,
0
]).buffer;
try {
var ret = offlineContext.decodeAudioData(audioData);
if (ret instanceof Promise) {
decodeAudioDataPromise = true;
}
} catch (e) {
decodeAudioDataPromise = false;
}
if (!decodeAudioDataPromise) {
AudioContext.prototype._native_decodeAudioData = AudioContext.prototype.decodeAudioData;
AudioContext.prototype.decodeAudioData = function (audioData) {
return new Promise(function (success, error) {
this._native_decodeAudioData(audioData, success, error);
}.bind(this));
};
}
}
});
Module(function (Tone) {
/**
* @class Wrapper around the native AudioContext.
* @extends {Tone.Emitter}
* @param {AudioContext=} context optionally pass in a context
*/
Tone.Context = function () {
Tone.Emitter.call(this);
var options = Tone.defaults(arguments, ['context'], Tone.Context);
if (!options.context) {
options.context = new window.AudioContext();
if (!options.context) {
throw new Error('could not create AudioContext. Possibly too many AudioContexts running already.');
}
}
this._context = options.context;
// extend all of the methods
for (var prop in this._context) {
this._defineProperty(this._context, prop);
}
/**
* The default latency hint
* @type {String}
* @private
*/
this._latencyHint = options.latencyHint;
/**
* An object containing all of the constants AudioBufferSourceNodes
* @type {Object}
* @private
*/
this._constants = {};
///////////////////////////////////////////////////////////////////////
// WORKER
///////////////////////////////////////////////////////////////////////
/**
* The amount of time events are scheduled
* into the future
* @type {Number}
*/
this.lookAhead = options.lookAhead;
/**
* A reference to the actual computed update interval
* @type {Number}
* @private
*/
this._computedUpdateInterval = 0;
/**
* A reliable callback method
* @private
* @type {Ticker}
*/
this._ticker = new Ticker(this.emit.bind(this, 'tick'), options.clockSource, options.updateInterval);
///////////////////////////////////////////////////////////////////////
// TIMEOUTS
///////////////////////////////////////////////////////////////////////
/**
* All of the setTimeout events.
* @type {Tone.Timeline}
* @private
*/
this._timeouts = new Tone.Timeline();
/**
* The timeout id counter
* @private
* @type {Number}
*/
this._timeoutIds = 0;
this.on('tick', this._timeoutLoop.bind(this));
};
Tone.extend(Tone.Context, Tone.Emitter);
Tone.Emitter.mixin(Tone.Context);
/**
* defaults
* @static
* @type {Object}
*/
Tone.Context.defaults = {
'clockSource': 'worker',
'latencyHint': 'interactive',
'lookAhead': 0.1,
'updateInterval': 0.03
};
/**
* Define a property on this Tone.Context.
* This is used to extend the native AudioContext
* @param {AudioContext} context
* @param {String} prop
* @private
*/
Tone.Context.prototype._defineProperty = function (context, prop) {
if (Tone.isUndef(this[prop])) {
Object.defineProperty(this, prop, {
get: function () {
if (typeof context[prop] === 'function') {
return context[prop].bind(context);
} else {
return context[prop];
}
},
set: function (val) {
context[prop] = val;
}
});
}
};
/**
* The current audio context time
* @return {Number}
*/
Tone.Context.prototype.now = function () {
return this._context.currentTime + this.lookAhead;
};
/**
* Promise which is invoked when the context is running.
* Tries to resume the context if it's not started.
* @return {Promise}
*/
Tone.Context.prototype.ready = function () {
return new Promise(function (done) {
if (this._context.state === 'running') {
done();
} else {
this._context.resume().then(function () {
done();
});
}
}.bind(this));
};
/**
* Promise which is invoked when the context is running.
* Tries to resume the context if it's not started.
* @return {Promise}
*/
Tone.Context.prototype.close = function () {
return this._context.close().then(function () {
Tone.Context.emit('close', this);
}.bind(this));
};
/**
* Generate a looped buffer at some constant value.
* @param {Number} val
* @return {BufferSourceNode}
*/
Tone.Context.prototype.getConstant = function (val) {
if (this._constants[val]) {
return this._constants[val];
} else {
var buffer = this._context.createBuffer(1, 128, this._context.sampleRate);
var arr = buffer.getChannelData(0);
for (var i = 0; i < arr.length; i++) {
arr[i] = val;
}
var constant = this._context.createBufferSource();
constant.channelCount = 1;
constant.channelCountMode = 'explicit';
constant.buffer = buffer;
constant.loop = true;
constant.start(0);
this._constants[val] = constant;
return constant;
}
};
/**
* The private loop which keeps track of the context scheduled timeouts
* Is invoked from the clock source
* @private
*/
Tone.Context.prototype._timeoutLoop = function () {
var now = this.now();
while (this._timeouts && this._timeouts.length && this._timeouts.peek().time <= now) {
this._timeouts.shift().callback();
}
};
/**
* A setTimeout which is gaurenteed by the clock source.
* Also runs in the offline context.
* @param {Function} fn The callback to invoke
* @param {Seconds} timeout The timeout in seconds
* @returns {Number} ID to use when invoking Tone.Context.clearTimeout
*/
Tone.Context.prototype.setTimeout = function (fn, timeout) {
this._timeoutIds++;
var now = this.now();
this._timeouts.add({
callback: fn,
time: now + timeout,
id: this._timeoutIds
});
return this._timeoutIds;
};
/**
* Clears a previously scheduled timeout with Tone.context.setTimeout
* @param {Number} id The ID returned from setTimeout
* @return {Tone.Context} this
*/
Tone.Context.prototype.clearTimeout = function (id) {
this._timeouts.forEach(function (event) {
if (event.id === id) {
this.remove(event);
}
});
return this;
};
/**
* How often the Web Worker callback is invoked.
* This number corresponds to how responsive the scheduling
* can be. Context.updateInterval + Context.lookAhead gives you the
* total latency between scheduling an event and hearing it.
* @type {Number}
* @memberOf Tone.Context#
* @name updateInterval
*/
Object.defineProperty(Tone.Context.prototype, 'updateInterval', {
get: function () {
return this._ticker.updateInterval;
},
set: function (interval) {
this._ticker.updateInterval = interval;
}
});
/**
* What the source of the clock is, either "worker" (Web Worker [default]),
* "timeout" (setTimeout), or "offline" (none).
* @type {String}
* @memberOf Tone.Context#
* @name clockSource
*/
Object.defineProperty(Tone.Context.prototype, 'clockSource', {
get: function () {
return this._ticker.type;
},
set: function (type) {
this._ticker.type = type;
}
});
/**
* The type of playback, which affects tradeoffs between audio
* output latency and responsiveness.
*
* In addition to setting the value in seconds, the latencyHint also
* accepts the strings "interactive" (prioritizes low latency),
* "playback" (prioritizes sustained playback), "balanced" (balances
* latency and performance), and "fastest" (lowest latency, might glitch more often).
* @type {String|Seconds}
* @memberOf Tone.Context#
* @name latencyHint
* @example
* //set the lookAhead to 0.3 seconds
* Tone.context.latencyHint = 0.3;
*/
Object.defineProperty(Tone.Context.prototype, 'latencyHint', {
get: function () {
return this._latencyHint;
},
set: function (hint) {
var lookAhead = hint;
this._latencyHint = hint;
if (Tone.isString(hint)) {
switch (hint) {
case 'interactive':
lookAhead = 0.1;
this._context.latencyHint = hint;
break;
case 'playback':
lookAhead = 0.8;
this._context.latencyHint = hint;
break;
case 'balanced':
lookAhead = 0.25;
this._context.latencyHint = hint;
break;
case 'fastest':
this._context.latencyHint = 'interactive';
lookAhead = 0.01;
break;
}
}
this.lookAhead = lookAhead;
this.updateInterval = lookAhead / 3;
}
});
/**
* Unlike other dispose methods, this returns a Promise
* which executes when the context is closed and disposed
* @returns {Promise} this
*/
Tone.Context.prototype.dispose = function () {
return this.close().then(function () {
Tone.Emitter.prototype.dispose.call(this);
this._ticker.dispose();
this._ticker = null;
this._timeouts.dispose();
this._timeouts = null;
for (var con in this._constants) {
this._constants[con].disconnect();
}
this._constants = null;
}.bind(this));
};
/**
* @class A class which provides a reliable callback using either
* a Web Worker, or if that isn't supported, falls back to setTimeout.
* @private
*/
var Ticker = function (callback, type, updateInterval) {
/**
* Either "worker" or "timeout"
* @type {String}
* @private
*/
this._type = type;
/**
* The update interval of the worker
* @private
* @type {Number}
*/
this._updateInterval = updateInterval;
/**
* The callback to invoke at regular intervals
* @type {Function}
* @private
*/
this._callback = Tone.defaultArg(callback, Tone.noOp);
//create the clock source for the first time
this._createClock();
};
/**
* The possible ticker types
* @private
* @type {Object}
*/
Ticker.Type = {
Worker: 'worker',
Timeout: 'timeout',
Offline: 'offline'
};
/**
* Generate a web worker
* @return {WebWorker}
* @private
*/
Ticker.prototype._createWorker = function () {
//URL Shim
window.URL = window.URL || window.webkitURL;
var blob = new Blob([//the initial timeout time
'var timeoutTime = ' + (this._updateInterval * 1000).toFixed(1) + ';' + //onmessage callback
'self.onmessage = function(msg){' + '\ttimeoutTime = parseInt(msg.data);' + '};' + //the tick function which posts a message
//and schedules a new tick
'function tick(){' + '\tsetTimeout(tick, timeoutTime);' + '\tself.postMessage(\'tick\');' + '}' + //call tick initially
'tick();']);
var blobUrl = URL.createObjectURL(blob);
var worker = new Worker(blobUrl);
worker.onmessage = this._callback.bind(this);
this._worker = worker;
};
/**
* Create a timeout loop
* @private
*/
Ticker.prototype._createTimeout = function () {
this._timeout = setTimeout(function () {
this._createTimeout();
this._callback();
}.bind(this), this._updateInterval * 1000);
};
/**
* Create the clock source.
* @private
*/
Ticker.prototype._createClock = function () {
if (this._type === Ticker.Type.Worker) {
try {
this._createWorker();
} catch (e) {
// workers not supported, fallback to timeout
this._type = Ticker.Type.Timeout;
this._createClock();
}
} else if (this._type === Ticker.Type.Timeout) {
this._createTimeout();
}
};
/**
* @memberOf Ticker#
* @type {Number}
* @name updateInterval
* @private
*/
Object.defineProperty(Ticker.prototype, 'updateInterval', {
get: function () {
return this._updateInterval;
},
set: function (interval) {
this._updateInterval = Math.max(interval, 128 / 44100);
if (this._type === Ticker.Type.Worker) {
this._worker.postMessage(Math.max(interval * 1000, 1));
}
}
});
/**
* The type of the ticker, either a worker or a timeout
* @memberOf Ticker#
* @type {Number}
* @name type
* @private
*/
Object.defineProperty(Ticker.prototype, 'type', {
get: function () {
return this._type;
},
set: function (type) {
this._disposeClock();
this._type = type;
this._createClock();
}
});
/**
* Clean up the current clock source
* @private
*/
Ticker.prototype._disposeClock = function () {
if (this._timeout) {
clearTimeout(this._timeout);
this._timeout = null;
}
if (this._worker) {
this._worker.terminate();
this._worker.onmessage = null;
this._worker = null;
}
};
/**
* Clean up
* @private
*/
Ticker.prototype.dispose = function () {
this._disposeClock();
this._callback = null;
};
/**
* Shim all connect/disconnect and some deprecated methods which are still in
* some older implementations.
* @private
*/
Tone.getContext(function () {
var nativeConnect = AudioNode.prototype.connect;
var nativeDisconnect = AudioNode.prototype.disconnect;
//replace the old connect method
function toneConnect(B, outNum, inNum) {
if (B.input) {
inNum = Tone.defaultArg(inNum, 0);
if (Tone.isArray(B.input)) {
return this.connect(B.input[inNum]);
} else {
return this.connect(B.input, outNum, inNum);
}
} else {
try {
if (B instanceof AudioNode) {
nativeConnect.call(this, B, outNum, inNum);
return B;
} else {
nativeConnect.call(this, B, outNum);
return B;
}
} catch (e) {
throw new Error('error connecting to node: ' + B + '\n' + e);
}
}
}
//replace the old disconnect method
function toneDisconnect(B, outNum, inNum) {
if (B && B.input && Tone.isArray(B.input)) {
inNum = Tone.defaultArg(inNum, 0);
this.disconnect(B.input[inNum], outNum, 0);
} else if (B && B.input) {
this.disconnect(B.input, outNum, inNum);
} else {
try {
nativeDisconnect.apply(this, arguments);
} catch (e) {
throw new Error('error disconnecting node: ' + B + '\n' + e);
}
}
}
if (AudioNode.prototype.connect !== toneConnect) {
AudioNode.prototype.connect = toneConnect;
AudioNode.prototype.disconnect = toneDisconnect;
}
});
// set the audio context initially, and if one is not already created
if (Tone.supported && !Tone.initialized) {
Tone.context = new Tone.Context();
// log on first initialization
// allow optional silencing of this log
if (!window.TONE_SILENCE_VERSION_LOGGING) {
// eslint-disable-next-line no-console
console.log('%c * Tone.js ' + Tone.version + ' * ', 'background: #000; color: #fff');
}
} else if (!Tone.supported) {
// eslint-disable-next-line no-console
console.warn('This browser does not support Tone.js');
}
return Tone.Context;
});
Module(function (Tone) {
/**
* @class Tone.AudioNode is the base class for classes which process audio.
* AudioNodes have inputs and outputs.
* @param {AudioContext=} context The audio context to use with the class
* @extends {Tone}
*/
Tone.AudioNode = function () {
Tone.call(this);
//use the default context if one is not passed in
var options = Tone.defaults(arguments, ['context'], { 'context': Tone.context });
/**
* The AudioContext of this instance
* @private
* @type {AudioContext}
*/
this._context = options.context;
};
Tone.extend(Tone.AudioNode);
/**
* Get the audio context belonging to this instance.
* @type {Tone.Context}
* @memberOf Tone.AudioNode#
* @name context
* @readOnly
*/
Object.defineProperty(Tone.AudioNode.prototype, 'context', {
get: function () {
return this._context;
}
});
/**
* Create input and outputs for this object.
* @param {Number} [input=0] The number of inputs
* @param {Number} [outputs=0] The number of outputs
* @return {Tone.AudioNode} this
* @private
*/
Tone.AudioNode.prototype.createInsOuts = function (inputs, outputs) {
if (inputs === 1) {
this.input = this.context.createGain();
} else if (inputs > 1) {
this.input = new Array(inputs);
}
if (outputs === 1) {
this.output = this.context.createGain();
} else if (outputs > 1) {
this.output = new Array(outputs);
}
};
/**
* channelCount is the number of channels used when up-mixing and down-mixing
* connections to any inputs to the node. The default value is 2 except for
* specific nodes where its value is specially determined.
*
* @memberof Tone.AudioNode#
* @type {Number}
* @name channelCount
* @readOnly
*/
Object.defineProperty(Tone.AudioNode.prototype, 'channelCount', {
get: function () {
return this.output.channelCount;
},
set: function (c) {
return this.output.channelCount = c;
}
});
/**
* channelCountMode determines how channels will be counted when up-mixing and
* down-mixing connections to any inputs to the node.
* The default value is "max". This attribute has no effect for nodes with no inputs.
* @memberof Tone.AudioNode#
* @type {String}
* @name channelCountMode
* @readOnly
*/
Object.defineProperty(Tone.AudioNode.prototype, 'channelCountMode', {
get: function () {
return this.output.channelCountMode;
},
set: function (m) {
return this.output.channelCountMode = m;
}
});
/**
* channelInterpretation determines how individual channels will be treated
* when up-mixing and down-mixing connections to any inputs to the node.
* The default value is "speakers".
* @memberof Tone.AudioNode#
* @type {String}
* @name channelInterpretation
* @readOnly
*/
Object.defineProperty(Tone.AudioNode.prototype, 'channelInterpretation', {
get: function () {
return this.output.channelInterpretation;
},
set: function (i) {
return this.output.channelInterpretation = i;
}
});
/**
* The number of inputs feeding into the AudioNode.
* For source nodes, this will be 0.
* @type {Number}
* @name numberOfInputs
* @memberof Tone.AudioNode#
* @readOnly
*/
Object.defineProperty(Tone.AudioNode.prototype, 'numberOfInputs', {
get: function () {
if (this.input) {
if (Tone.isArray(this.input)) {
return this.input.length;
} else {
return 1;
}
} else {
return 0;
}
}
});
/**
* The number of outputs coming out of the AudioNode.
* @type {Number}
* @name numberOfOutputs
* @memberof Tone.AudioNode#
* @readOnly
*/
Object.defineProperty(Tone.AudioNode.prototype, 'numberOfOutputs', {
get: function () {
if (this.output) {
if (Tone.isArray(this.output)) {
return this.output.length;
} else {
return 1;
}
} else {
return 0;
}
}
});
/**
* Called when an audio param connects to this node
* @private
*/
Tone.AudioNode.prototype._onConnect = function () {
};
/**
* connect the output of a ToneNode to an AudioParam, AudioNode, or ToneNode
* @param {Tone | AudioParam | AudioNode} unit
* @param {number} [outputNum=0] optionally which output to connect from
* @param {number} [inputNum=0] optionally which input to connect to
* @returns {Tone.AudioNode} this
*/
Tone.AudioNode.prototype.connect = function (unit, outputNum, inputNum) {
if (unit._onConnect) {
unit._onConnect(this);
}
if (Tone.isArray(this.output)) {
outputNum = Tone.defaultArg(outputNum, 0);
this.output[outputNum].connect(unit, 0, inputNum);
} else {
this.output.connect(unit, outputNum, inputNum);
}
return this;
};
/**
* disconnect the output
* @param {Number|AudioNode} output Either the output index to disconnect
* if the output is an array, or the
* node to disconnect from.
* @returns {Tone.AudioNode} this
*/
Tone.AudioNode.prototype.disconnect = function (destination, outputNum, inputNum) {
if (Tone.isArray(this.output)) {
if (Tone.isNumber(destination)) {
this.output[destination].disconnect();
} else {
outputNum = Tone.defaultArg(outputNum, 0);
this.output[outputNum].disconnect(destination, 0, inputNum);
}
} else {
this.output.disconnect.apply(this.output, arguments);
}
};
/**
* Connect the output of this node to the rest of the nodes in series.
* @example
* //connect a node to an effect, panVol and then to the master output
* node.chain(effect, panVol, Tone.Master);
* @param {...AudioParam|Tone|AudioNode} nodes
* @returns {Tone.AudioNode} this
* @private
*/
Tone.AudioNode.prototype.chain = function () {
var currentUnit = this;
for (var i = 0; i < arguments.length; i++) {
var toUnit = arguments[i];
currentUnit.connect(toUnit);
currentUnit = toUnit;
}
return this;
};
/**
* connect the output of this node to the rest of the nodes in parallel.
* @param {...AudioParam|Tone|AudioNode} nodes
* @returns {Tone.AudioNode} this
* @private
*/
Tone.AudioNode.prototype.fan = function () {
for (var i = 0; i < arguments.length; i++) {
this.connect(arguments[i]);
}
return this;
};
if (window.AudioNode) {
//give native nodes chain and fan methods
AudioNode.prototype.chain = Tone.AudioNode.prototype.chain;
AudioNode.prototype.fan = Tone.AudioNode.prototype.fan;
}
/**
* Dispose and disconnect
* @return {Tone.AudioNode} this
*/
Tone.AudioNode.prototype.dispose = function () {
if (Tone.isDefined(this.input)) {
if (this.input instanceof AudioNode) {
this.input.disconnect();
}
this.input = null;
}
if (Tone.isDefined(this.output)) {
if (this.output instanceof AudioNode) {
this.output.disconnect();
}
this.output = null;
}
this._context = null;
return this;
};
return Tone.AudioNode;
});
Module(function (Tone) {
/**
* @class Base class for all Signals. Used Internally.
*
* @constructor
* @extends {Tone}
*/
Tone.SignalBase = function () {
Tone.AudioNode.call(this);
};
Tone.extend(Tone.SignalBase, Tone.AudioNode);
/**
* 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 connect.
*
* @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.SignalBase} this
*/
Tone.SignalBase.prototype.connect = function (node, outputNumber, inputNumber) {
//zero it out so that the signal can have full control
if (Tone.Signal && Tone.Signal === node.constructor || Tone.Param && Tone.Param === node.constructor) {
//cancel changes
node._param.cancelScheduledValues(0);
//reset the value
node._param.value = 0;
//mark the value as overridden
node.overridden = true;
} else if (node instanceof AudioParam) {
node.cancelScheduledValues(0);
node.value = 0;
}
Tone.AudioNode.prototype.connect.call(this, node, outputNumber, inputNumber);
return this;
};
return Tone.SignalBase;
});
Module(function (Tone) {
if (Tone.supported) {
//fixes safari only bug which is still present in 11
var ua = navigator.userAgent.toLowerCase();
var isSafari = ua.includes('safari') && !ua.includes('chrome');
if (isSafari) {
var WaveShaperNode = function (context) {
this._internalNode = this.input = this.output = context._native_createWaveShaper();
this._curve = null;
for (var prop in this._internalNode) {
this._defineProperty(this._internalNode, prop);
}
};
Object.defineProperty(WaveShaperNode.prototype, 'curve', {
get: function () {
return this._curve;
},
set: function (curve) {
this._curve = curve;
var array = new Float32Array(curve.length + 1);
array.set(curve, 1);
array[0] = curve[0];
this._internalNode.curve = array;
}
});
WaveShaperNode.prototype._defineProperty = function (context, prop) {
if (Tone.isUndef(this[prop])) {
Object.defineProperty(this, prop, {
get: function () {
if (typeof context[prop] === 'function') {
return context[prop].bind(context);
} else {
return context[prop];
}
},
set: function (val) {
context[prop] = val;
}
});
}
};
AudioContext.prototype._native_createWaveShaper = AudioContext.prototype.createWaveShaper;
AudioContext.prototype.createWaveShaper = function () {
return new WaveShaperNode(this);
};
}
}
});
Module(function (Tone) {
/**
* @class Wraps the native Web Audio API
* [WaveShaperNode](http://webaudio.github.io/web-audio-api/#the-waveshapernode-interface).
*
* @extends {Tone.SignalBase}
* @constructor
* @param {function|Array|Number} mapping The function used to define the values.
* The mapping function should take two arguments:
* the first is the value at the current position
* and the second is the array position.
* If the argument is an array, that array will be
* set as the wave shaping function. The input
* signal is an AudioRange [-1, 1] value and the output
* signal can take on any numerical values.
*
* @param {Number} [bufferLen=1024] The length of the WaveShaperNode buffer.
* @example
* var timesTwo = new Tone.WaveShaper(function(val){
* return val * 2;
* }, 2048);
* @example
* //a waveshaper can also be constructed with an array of values
* var invert = new Tone.WaveShaper([1, -1]);
*/
Tone.WaveShaper = function (mapping, bufferLen) {
Tone.SignalBase.call(this);
/**
* the waveshaper
* @type {WaveShaperNode}
* @private
*/
this._shaper = this.input = this.output = this.context.createWaveShaper();
/**
* the waveshapers curve
* @type {Float32Array}
* @private
*/
this._curve = null;
if (Array.isArray(mapping)) {
this.curve = mapping;
} else if (isFinite(mapping) || Tone.isUndef(mapping)) {
this._curve = new Float32Array(Tone.defaultArg(mapping, 1024));
} else if (Tone.isFunction(mapping)) {
this._curve = new Float32Array(Tone.defaultArg(bufferLen, 1024));
this.setMap(mapping);
}
};
Tone.extend(Tone.WaveShaper, Tone.SignalBase);
/**
* Uses a mapping function to set the value of the curve.
* @param {function} mapping The function used to define the values.
* The mapping function take two arguments:
* the first is the value at the current position
* which goes from -1 to 1 over the number of elements
* in the curve array. The second argument is the array position.
* @returns {Tone.WaveShaper} this
* @example
* //map the input signal from [-1, 1] to [0, 10]
* shaper.setMap(function(val, index){
* return (val + 1) * 5;
* })
*/
Tone.WaveShaper.prototype.setMap = function (mapping) {
var array = new Array(this._curve.length);
for (var i = 0, len = this._curve.length; i < len; i++) {
var normalized = i / (len - 1) * 2 - 1;
array[i] = mapping(normalized, i);
}
this.curve = array;
return this;
};
/**
* The array to set as the waveshaper curve. For linear curves
* array length does not make much difference, but for complex curves
* longer arrays will provide smoother interpolation.
* @memberOf Tone.WaveShaper#
* @type {Array}
* @name curve
*/
Object.defineProperty(Tone.WaveShaper.prototype, 'curve', {
get: function () {
return this._shaper.curve;
},
set: function (mapping) {
this._curve = new Float32Array(mapping);
this._shaper.curve = this._curve;
}
});
/**
* Specifies what type of oversampling (if any) should be used when
* applying the shaping curve. Can either be "none", "2x" or "4x".
* @memberOf Tone.WaveShaper#
* @type {string}
* @name oversample
*/
Object.defineProperty(Tone.WaveShaper.prototype, 'oversample', {
get: function () {
return this._shaper.oversample;
},
set: function (oversampling) {
if ([
'none',
'2x',
'4x'
].includes(oversampling)) {
this._shaper.oversample = oversampling;
} else {
throw new RangeError('Tone.WaveShaper: oversampling must be either \'none\', \'2x\', or \'4x\'');
}
}
});
/**
* Clean up.
* @returns {Tone.WaveShaper} this
*/
Tone.WaveShaper.prototype.dispose = function () {
Tone.SignalBase.prototype.dispose.call(this);
this._shaper.disconnect();
this._shaper = null;
this._curve = null;
return this;
};
return Tone.WaveShaper;
});
Module(function (Tone) {
/**
* @class Tone.TimeBase is a flexible encoding of time
* which can be evaluated to and from a string.
* @extends {Tone}
* @param {Time} val The time value as a number or string
* @param {String=} units Unit values
* @example
* Tone.TimeBase(4, "n")
* Tone.TimeBase(2, "t")
* Tone.TimeBase("2t")
* Tone.TimeBase("2t") + Tone.TimeBase("4n");
*/
Tone.TimeBase = function (val, units) {
//allows it to be constructed with or without 'new'
if (this instanceof Tone.TimeBase) {
/**
* The value
* @type {Number|String|Tone.TimeBase}
* @private
*/
this._val = val;
/**
* The units
* @type {String?}
* @private
*/
this._units = units;
//test if the value is a string representation of a number
if (Tone.isUndef(this._units) && Tone.isString(this._val) && // eslint-disable-next-line eqeqeq
parseFloat(this._val) == this._val && this._val.charAt(0) !== '+') {
this._val = parseFloat(this._val);
this._units = this._defaultUnits;
} else if (val && val.constructor === this.constructor) {
//if they're the same type, just copy values over
this._val = val._val;
this._units = val._units;
} else if (val instanceof Tone.TimeBase) {
switch (this._defaultUnits) {
case 's':
this._val = val.toSeconds();
break;
case 'i':
this._val = val.toTicks();
break;
case 'hz':
this._val = val.toFrequency();
break;
case 'midi':
this._val = val.toMidi();
break;
default:
throw new Error('Unrecognized default units ' + this._defaultUnits);
}
}
} else {
return new Tone.TimeBase(val, units);
}
};
Tone.extend(Tone.TimeBase);
///////////////////////////////////////////////////////////////////////////
// ABSTRACT SYNTAX TREE PARSER
///////////////////////////////////////////////////////////////////////////
/**
* All the primary expressions.
* @private
* @type {Object}
*/
Tone.TimeBase.prototype._expressions = {
'n': {
regexp: /^(\d+)n(\.?)$/i,
method: function (value, dot) {
value = parseInt(value);
var scalar = dot === '.' ? 1.5 : 1;
if (value === 1) {
return this._beatsToUnits(this._getTimeSignature()) * scalar;
} else {
return this._beatsToUnits(4 / value) * scalar;
}
}
},
't': {
regexp: /^(\d+)t$/i,
method: function (value) {
value = parseInt(value);
return this._beatsToUnits(8 / (parseInt(value) * 3));
}
},
'm': {
regexp: /^(\d+)m$/i,
method: function (value) {
return this._beatsToUnits(parseInt(value) * this._getTimeSignature());
}
},
'i': {
regexp: /^(\d+)i$/i,
method: function (value) {
return this._ticksToUnits(parseInt(value));
}
},
'hz': {
regexp: /^(\d+(?:\.\d+)?)hz$/i,
method: function (value) {
return this._frequencyToUnits(parseFloat(value));
}
},
'tr': {
regexp: /^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?):?(\d+(?:\.\d+)?)?$/,
method: function (m, q, s) {
var total = 0;
if (m && m !== '0') {
total += this._beatsToUnits(this._getTimeSignature() * parseFloat(m));
}
if (q && q !== '0') {
total += this._beatsToUnits(parseFloat(q));
}
if (s && s !== '0') {
total += this._beatsToUnits(parseFloat(s) / 4);
}
return total;
}
},
's': {
regexp: /^(\d+(?:\.\d+)?)s$/,
method: function (value) {
return this._secondsToUnits(parseFloat(value));
}
},
'samples': {
regexp: /^(\d+)samples$/,
method: function (value) {
return parseInt(value) / this.context.sampleRate;
}
},
'default': {
regexp: /^(\d+(?:\.\d+)?)$/,
method: function (value) {
return this._expressions[this._defaultUnits].method.call(this, value);
}
}
};
/**
* The default units if none are given.
* @type {String}
* @private
*/
Tone.TimeBase.prototype._defaultUnits = 's';
///////////////////////////////////////////////////////////////////////////
// TRANSPORT FALLBACKS
///////////////////////////////////////////////////////////////////////////
/**
* Return the bpm, or 120 if Transport is not available
* @type {Number}
* @private
*/
Tone.TimeBase.prototype._getBpm = function () {
if (Tone.Transport) {
return Tone.Transport.bpm.value;
} else {
return 120;
}
};
/**
* Return the timeSignature or 4 if Transport is not available
* @type {Number}
* @private
*/
Tone.TimeBase.prototype._getTimeSignature = function () {
if (Tone.Transport) {
return Tone.Transport.timeSignature;
} else {
return 4;
}
};
/**
* Return the PPQ or 192 if Transport is not available
* @type {Number}
* @private
*/
Tone.TimeBase.prototype._getPPQ = function () {
if (Tone.Transport) {
return Tone.Transport.PPQ;
} else {
return 192;
}
};
/**
* Return the current time in whichever context is relevant
* @type {Number}
* @private
*/
Tone.TimeBase.prototype._now = function () {
return this.now();
};
///////////////////////////////////////////////////////////////////////////
// UNIT CONVERSIONS
///////////////////////////////////////////////////////////////////////////
/**
* Returns the value of a frequency in the current units
* @param {Frequency} freq
* @return {Number}
* @private
*/
Tone.TimeBase.prototype._frequencyToUnits = function (freq) {
return 1 / freq;
};
/**
* Return the value of the beats in the current units
* @param {Number} beats
* @return {Number}
* @private
*/
Tone.TimeBase.prototype._beatsToUnits = function (beats) {
return 60 / this._getBpm() * beats;
};
/**
* Returns the value of a second in the current units
* @param {Seconds} seconds
* @return {Number}
* @private
*/
Tone.TimeBase.prototype._secondsToUnits = function (seconds) {
return seconds;
};
/**
* Returns the value of a tick in the current time units
* @param {Ticks} ticks
* @return {Number}
* @private
*/
Tone.TimeBase.prototype._ticksToUnits = function (ticks) {
return ticks * (this._beatsToUnits(1) / this._getPPQ());
};
/**
* With no arguments, return 'now'
* @return {Number}
* @private
*/
Tone.TimeBase.prototype._noArg = function () {
return this._now();
};
///////////////////////////////////////////////////////////////////////////
// EXPRESSIONS
///////////////////////////////////////////////////////////////////////////
/**
* Evaluate the time value. Returns the time
* in seconds.
* @return {Seconds}
*/
Tone.TimeBase.prototype.valueOf = function () {
if (Tone.isUndef(this._val)) {
return this._noArg();
} else if (Tone.isString(this._val) && Tone.isUndef(this._units)) {
for (var units in this._expressions) {
if (this._expressions[units].regexp.test(this._val.trim())) {
this._units = units;
break;
}
}
}
if (Tone.isDefined(this._units)) {
var expr = this._expressions[this._units];
var matching = this._val.toString().trim().match(expr.regexp);
if (matching) {
return expr.method.apply(this, matching.slice(1));
} else {
return expr.method.call(this, parseFloat(this._val));
}
} else {
return this._val;
}
};
/**
* Return the value in seconds
* @return {Seconds}
*/
Tone.TimeBase.prototype.toSeconds = function () {
return this.valueOf();
};
/**
* Return the value in hertz
* @return {Frequency}
*/
Tone.TimeBase.prototype.toFrequency = function () {
return 1 / this.toSeconds();
};
/**
* Return the time in samples
* @return {Samples}
*/
Tone.TimeBase.prototype.toSamples = function () {
return this.toSeconds() * this.context.sampleRate;
};
/**
* Return the time in milliseconds.
* @return {Milliseconds}
*/
Tone.TimeBase.prototype.toMilliseconds = function () {
return this.toSeconds() * 1000;
};
/**
* Clean up
* @return {Tone.TimeBase} this
*/
Tone.TimeBase.prototype.dispose = function () {
this._val = null;
this._units = null;
};
return Tone.TimeBase;
});
Module(function (Tone) {
/**
* @class Tone.Frequency is a primitive type for encoding Frequency values.
* Eventually all time values are evaluated to hertz
* using the `eval` method.
* @constructor
* @extends {Tone.TimeBase}
* @param {String|Number} val The time value.
* @param {String=} units The units of the value.
* @example
* Tone.Frequency("C3") // 261
* Tone.Frequency(38, "midi") //
* Tone.Frequency("C3").transpose(4);
*/
Tone.Frequency = function (val, units) {
if (this instanceof Tone.Frequency) {
Tone.TimeBase.call(this, val, units);
} else {
return new Tone.Frequency(val, units);
}
};
Tone.extend(Tone.Frequency, Tone.TimeBase);
///////////////////////////////////////////////////////////////////////////
// AUGMENT BASE EXPRESSIONS
///////////////////////////////////////////////////////////////////////////
Tone.Frequency.prototype._expressions = Object.assign({}, Tone.TimeBase.prototype._expressions, {
'midi': {
regexp: /^(\d+(?:\.\d+)?midi)/,
method: function (value) {
if (this._defaultUnits === 'midi') {
return value;
} else {
return Tone.Frequency.mtof(value);
}
}
},
'note': {
regexp: /^([a-g]{1}(?:b|#|x|bb)?)(-?[0-9]+)/i,
method: function (pitch, octave) {
var index = noteToScaleIndex[pitch.toLowerCase()];
var noteNumber = index + (parseInt(octave) + 1) * 12;
if (this._defaultUnits === 'midi') {
return noteNumber;
} else {
return Tone.Frequency.mtof(noteNumber);
}
}
},
'tr': {
regexp: /^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?):?(\d+(?:\.\d+)?)?/,
method: function (m, q, s) {
var total = 1;
if (m && m !== '0') {
total *= this._beatsToUnits(this._getTimeSignature() * parseFloat(m));
}
if (q && q !== '0') {
total *= this._beatsToUnits(parseFloat(q));
}
if (s && s !== '0') {
total *= this._beatsToUnits(parseFloat(s) / 4);
}
return total;
}
}
});
///////////////////////////////////////////////////////////////////////////
// EXPRESSIONS
///////////////////////////////////////////////////////////////////////////
/**
* Transposes the frequency by the given number of semitones.
* @param {Interval} interval
* @return {Tone.Frequency} A new transposed frequency
* @example
* Tone.Frequency("A4").transpose(3); //"C5"
*/
Tone.Frequency.prototype.transpose = function (interval) {
return new this.constructor(this.valueOf() * Tone.intervalToFrequencyRatio(interval));
};
/**
* Takes an array of semitone intervals and returns
* an array of frequencies transposed by those intervals.
* @param {Array} intervals
* @return {Array} Returns an array of Frequencies
* @example
* Tone.Frequency("A4").harmonize([0, 3, 7]); //["A4", "C5", "E5"]
*/
Tone.Frequency.prototype.harmonize = function (intervals) {
return intervals.map(function (interval) {
return this.transpose(interval);
}.bind(this));
};
///////////////////////////////////////////////////////////////////////////
// UNIT CONVERSIONS
///////////////////////////////////////////////////////////////////////////
/**
* Return the value of the frequency as a MIDI note
* @return {MIDI}
* @example
* Tone.Frequency("C4").toMidi(); //60
*/
Tone.Frequency.prototype.toMidi = function () {
return Tone.Frequency.ftom(this.valueOf());
};
/**
* Return the value of the frequency in Scientific Pitch Notation
* @return {Note}
* @example
* Tone.Frequency(69, "midi").toNote(); //"A4"
*/
Tone.Frequency.prototype.toNote = function () {
var freq = this.toFrequency();
var log = Math.log2(freq / Tone.Frequency.A4);
var noteNumber = Math.round(12 * log) + 57;
var octave = Math.floor(noteNumber / 12);
if (octave < 0) {
noteNumber += -12 * octave;
}
var noteName = scaleIndexToNote[noteNumber % 12];
return noteName + octave.toString();
};
/**
* Return the duration of one cycle in seconds.
* @return {Seconds}
*/
Tone.Frequency.prototype.toSeconds = function () {
return 1 / Tone.TimeBase.prototype.toSeconds.call(this);
};
/**
* Return the value in Hertz
* @return {Frequency}
*/
Tone.Frequency.prototype.toFrequency = function () {
return Tone.TimeBase.prototype.toFrequency.call(this);
};
/**
* Return the duration of one cycle in ticks
* @return {Ticks}
*/
Tone.Frequency.prototype.toTicks = function () {
var quarterTime = this._beatsToUnits(1);
var quarters = this.valueOf() / quarterTime;
return Math.floor(quarters * Tone.Transport.PPQ);
};
///////////////////////////////////////////////////////////////////////////
// UNIT CONVERSIONS HELPERS
///////////////////////////////////////////////////////////////////////////
/**
* With no arguments, return 0
* @return {Number}
* @private
*/
Tone.Frequency.prototype._noArg = function () {
return 0;
};
/**
* Returns the value of a frequency in the current units
* @param {Frequency} freq
* @return {Number}
* @private
*/
Tone.Frequency.prototype._frequencyToUnits = function (freq) {
return freq;
};
/**
* Returns the value of a tick in the current time units
* @param {Ticks} ticks
* @return {Number}
* @private
*/
Tone.Frequency.prototype._ticksToUnits = function (ticks) {
return 1 / (ticks * 60 / (Tone.Transport.bpm.value * Tone.Transport.PPQ));
};
/**
* Return the value of the beats in the current units
* @param {Number} beats
* @return {Number}
* @private
*/
Tone.Frequency.prototype._beatsToUnits = function (beats) {
return 1 / Tone.TimeBase.prototype._beatsToUnits.call(this, beats);
};
/**
* Returns the value of a second in the current units
* @param {Seconds} seconds
* @return {Number}
* @private
*/
Tone.Frequency.prototype._secondsToUnits = function (seconds) {
return 1 / seconds;
};
/**
* The default units if none are given.
* @private
*/
Tone.Frequency.prototype._defaultUnits = 'hz';
///////////////////////////////////////////////////////////////////////////
// FREQUENCY CONVERSIONS
///////////////////////////////////////////////////////////////////////////
/**
* Note to scale index
* @type {Object}
*/
var noteToScaleIndex = {
'cbb': -2,
'cb': -1,
'c': 0,
'c#': 1,
'cx': 2,
'dbb': 0,
'db': 1,
'd': 2,
'd#': 3,
'dx': 4,
'ebb': 2,
'eb': 3,
'e': 4,
'e#': 5,
'ex': 6,
'fbb': 3,
'fb': 4,
'f': 5,
'f#': 6,
'fx': 7,
'gbb': 5,
'gb': 6,
'g': 7,
'g#': 8,
'gx': 9,
'abb': 7,
'ab': 8,
'a': 9,
'a#': 10,
'ax': 11,
'bbb': 9,
'bb': 10,
'b': 11,
'b#': 12,
'bx': 13
};
/**
* scale index to note (sharps)
* @type {Array}
*/
var scaleIndexToNote = [
'C',
'C#',
'D',
'D#',
'E',
'F',
'F#',
'G',
'G#',
'A',
'A#',
'B'
];
/**
* The [concert pitch](https://en.wikipedia.org/wiki/Concert_pitch)
* A4's values in Hertz.
* @type {Frequency}
* @static
*/
Tone.Frequency.A4 = 440;
/**
* Convert a MIDI note to frequency value.
* @param {MIDI} midi The midi number to convert.
* @return {Frequency} the corresponding frequency value
* @static
* @example
* Tone.Frequency.mtof(69); // returns 440
*/
Tone.Frequency.mtof = function (midi) {
return Tone.Frequency.A4 * Math.pow(2, (midi - 69) / 12);
};
/**
* Convert a frequency value to a MIDI note.
* @param {Frequency} frequency The value to frequency value to convert.
* @returns {MIDI}
* @static
* @example
* Tone.Frequency.ftom(440); // returns 69
*/
Tone.Frequency.ftom = function (frequency) {
return 69 + Math.round(12 * Math.log2(frequency / Tone.Frequency.A4));
};
return Tone.Frequency;
});
Module(function (Tone) {
/**
* @class Tone.Time is a primitive type for encoding Time values.
* Tone.Time can be constructed with or without the `new` keyword. Tone.Time can be passed
* into the parameter of any method which takes time as an argument.
* @constructor
* @extends {Tone.TimeBase}
* @param {String|Number} val The time value.
* @param {String=} units The units of the value.
* @example
* var t = Tone.Time("4n");//a quarter note
*/
Tone.Time = function (val, units) {
if (this instanceof Tone.Time) {
Tone.TimeBase.call(this, val, units);
} else {
return new Tone.Time(val, units);
}
};
Tone.extend(Tone.Time, Tone.TimeBase);
/**
* Extend the base expressions
*/
Tone.Time.prototype._expressions = Object.assign({}, Tone.TimeBase.prototype._expressions, {
'quantize': {
regexp: /^@(.+)/,
method: function (capture) {
if (Tone.Transport) {
var quantTo = new this.constructor(capture);
return Tone.Transport.nextSubdivision(quantTo);
} else {
return 0;
}
}
},
'now': {
regexp: /^\+(.+)/,
method: function (capture) {
return this._now() + new this.constructor(capture);
}
}
});
/**
* Quantize the time by the given subdivision. Optionally add a
* percentage which will move the time value towards the ideal
* quantized value by that percentage.
* @param {Number|Time} val The subdivision to quantize to
* @param {NormalRange} [percent=1] Move the time value
* towards the quantized value by
* a percentage.
* @return {Number} this
* @example
* Tone.Time(21).quantize(2) //returns 22
* Tone.Time(0.6).quantize("4n", 0.5) //returns 0.55
*/
Tone.Time.prototype.quantize = function (subdiv, percent) {
percent = Tone.defaultArg(percent, 1);
var subdivision = new this.constructor(subdiv);
var value = this.valueOf();
var multiple = Math.round(value / subdivision);
var ideal = multiple * subdivision;
var diff = ideal - value;
return value + diff * percent;
};
///////////////////////////////////////////////////////////////////////////
// CONVERSIONS
///////////////////////////////////////////////////////////////////////////
/**
* Convert a Time to Notation. The notation values are will be the
* closest representation between 1m to 128th note.
* @return {Notation}
* @example
* //if the Transport is at 120bpm:
* Tone.Time(2).toNotation();//returns "1m"
*/
Tone.Time.prototype.toNotation = function () {
var time = this.toSeconds();
var testNotations = ['1m'];
for (var power = 1; power < 8; power++) {
var subdiv = Math.pow(2, power);
testNotations.push(subdiv + 'n.');
testNotations.push(subdiv + 'n');
testNotations.push(subdiv + 't');
}
testNotations.push('0');
//find the closets notation representation
var closest = testNotations[0];
var closestSeconds = Tone.Time(testNotations[0]).toSeconds();
testNotations.forEach(function (notation) {
var notationSeconds = Tone.Time(notation).toSeconds();
if (Math.abs(notationSeconds - time) < Math.abs(closestSeconds - time)) {
closest = notation;
closestSeconds = notationSeconds;
}
});
return closest;
};
/**
* Return the time encoded as Bars:Beats:Sixteenths.
* @return {BarsBeatsSixteenths}
*/
Tone.Time.prototype.toBarsBeatsSixteenths = function () {
var quarterTime = this._beatsToUnits(1);
var quarters = this.valueOf() / quarterTime;
var measures = Math.floor(quarters / this._getTimeSignature());
var sixteenths = quarters % 1 * 4;
quarters = Math.floor(quarters) % this._getTimeSignature();
sixteenths = sixteenths.toString();
if (sixteenths.length > 3) {
// the additional parseFloat removes insignificant trailing zeroes
sixteenths = parseFloat(parseFloat(sixteenths).toFixed(3));
}
var progress = [
measures,
quarters,
sixteenths
];
return progress.join(':');
};
/**
* Return the time in ticks.
* @return {Ticks}
*/
Tone.Time.prototype.toTicks = function () {
var quarterTime = this._beatsToUnits(1);
var quarters = this.valueOf() / quarterTime;
return Math.round(quarters * this._getPPQ());
};
/**
* Return the time in seconds.
* @return {Seconds}
*/
Tone.Time.prototype.toSeconds = function () {
return this.valueOf();
};
/**
* Return the value as a midi note.
* @return {Midi}
*/
Tone.Time.prototype.toMidi = function () {
return Tone.Frequency.ftom(this.toFrequency());
};
return Tone.Time;
});
Module(function (Tone) {
/**
* @class Tone.TransportTime is a the time along the Transport's
* timeline. It is similar to Tone.Time, but instead of evaluating
* against the AudioContext's clock, it is evaluated against
* the Transport's position. See [TransportTime wiki](https://github.com/Tonejs/Tone.js/wiki/TransportTime).
* @constructor
* @param {Time} val The time value as a number or string
* @param {String=} units Unit values
* @extends {Tone.Time}
*/
Tone.TransportTime = function (val, units) {
if (this instanceof Tone.TransportTime) {
Tone.Time.call(this, val, units);
} else {
return new Tone.TransportTime(val, units);
}
};
Tone.extend(Tone.TransportTime, Tone.Time);
/**
* Return the current time in whichever context is relevant
* @type {Number}
* @private
*/
Tone.TransportTime.prototype._now = function () {
return Tone.Transport.seconds;
};
return Tone.TransportTime;
});
Module(function (Tone) {
///////////////////////////////////////////////////////////////////////////
// TYPES
///////////////////////////////////////////////////////////////////////////
/**
* Units which a value can take on.
* @enum {String}
*/
Tone.Type = {
/**
* Default units
* @typedef {Default}
*/
Default: 'number',
/**
* Time can be described in a number of ways. Read more [Time](https://github.com/Tonejs/Tone.js/wiki/Time).
*
* * Numbers, which will be taken literally as the time (in seconds).
* * Notation, ("4n", "8t") describes time in BPM and time signature relative values.
* * TransportTime, ("4:3:2") will also provide tempo and time signature relative times
* in the form BARS:QUARTERS:SIXTEENTHS.
* * Frequency, ("8hz") is converted to the length of the cycle in seconds.
* * Now-Relative, ("+1") prefix any of the above with "+" and it will be interpreted as
* "the current time plus whatever expression follows".
* * Expressions, ("3:0 + 2 - (1m / 7)") any of the above can also be combined
* into a mathematical expression which will be evaluated to compute the desired time.
* * No Argument, for methods which accept time, no argument will be interpreted as
* "now" (i.e. the currentTime).
*
* @typedef {Time}
*/
Time: 'time',
/**
* Frequency can be described similar to time, except ultimately the
* values are converted to frequency instead of seconds. A number
* is taken literally as the value in hertz. Additionally any of the
* Time encodings can be used. Note names in the form
* of NOTE OCTAVE (i.e. C4) are also accepted and converted to their
* frequency value.
* @typedef {Frequency}
*/
Frequency: 'frequency',
/**
* TransportTime describes a position along the Transport's timeline. It is
* similar to Time in that it uses all the same encodings, but TransportTime specifically
* pertains to the Transport's timeline, which is startable, stoppable, loopable, and seekable.
* [Read more](https://github.com/Tonejs/Tone.js/wiki/TransportTime)
* @typedef {TransportTime}
*/
TransportTime: 'transportTime',
/**
* Ticks are the basic subunit of the Transport. They are
* the smallest unit of time that the Transport supports.
* @typedef {Ticks}
*/
Ticks: 'ticks',
/**
* Normal values are within the range [0, 1].
* @typedef {NormalRange}
*/
NormalRange: 'normalRange',
/**
* AudioRange values are between [-1, 1].
* @typedef {AudioRange}
*/
AudioRange: 'audioRange',
/**
* Decibels are a logarithmic unit of measurement which is useful for volume
* because of the logarithmic way that we perceive loudness. 0 decibels
* means no change in volume. -10db is approximately half as loud and 10db
* is twice is loud.
* @typedef {Decibels}
*/
Decibels: 'db',
/**
* Half-step note increments, i.e. 12 is an octave above the root. and 1 is a half-step up.
* @typedef {Interval}
*/
Interval: 'interval',
/**
* Beats per minute.
* @typedef {BPM}
*/
BPM: 'bpm',
/**
* The value must be greater than or equal to 0.
* @typedef {Positive}
*/
Positive: 'positive',
/**
* Gain is the ratio between input and output of a signal.
* A gain of 0 is the same as silencing the signal. A gain of
* 1, causes no change to the incoming signal.
* @typedef {Gain}
*/
Gain: 'gain',
/**
* A cent is a hundredth of a semitone.
* @typedef {Cents}
*/
Cents: 'cents',
/**
* Angle between 0 and 360.
* @typedef {Degrees}
*/
Degrees: 'degrees',
/**
* A number representing a midi note.
* @typedef {MIDI}
*/
MIDI: 'midi',
/**
* A colon-separated representation of time in the form of
* Bars:Beats:Sixteenths.
* @typedef {BarsBeatsSixteenths}
*/
BarsBeatsSixteenths: 'barsBeatsSixteenths',
/**
* Sampling is the reduction of a continuous signal to a discrete signal.
* Audio is typically sampled 44100 times per second.
* @typedef {Samples}
*/
Samples: 'samples',
/**
* Hertz are a frequency representation defined as one cycle per second.
* @typedef {Hertz}
*/
Hertz: 'hertz',
/**
* A frequency represented by a letter name,
* accidental and octave. This system is known as
* [Scientific Pitch Notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation).
* @typedef {Note}
*/
Note: 'note',
/**
* One millisecond is a thousandth of a second.
* @typedef {Milliseconds}
*/
Milliseconds: 'milliseconds',
/**
* Seconds are the time unit of the AudioContext. In the end,
* all values need to be evaluated to seconds.
* @typedef {Seconds}
*/
Seconds: 'seconds',
/**
* A string representing a duration relative to a measure.
* * "4n" = quarter note
* * "2m" = two measures
* * "8t" = eighth-note triplet
* @typedef {Notation}
*/
Notation: 'notation'
};
///////////////////////////////////////////////////////////////////////////
// AUGMENT TONE's PROTOTYPE
///////////////////////////////////////////////////////////////////////////
/**
* Convert Time into seconds.
*
* Unlike the method which it overrides, this takes into account
* transporttime and musical notation.
*
* Time : 1.40
* Notation: 4n or 1m or 2t
* Now Relative: +3n
* Math: 3n+16n or even complicated expressions ((3n*2)/6 + 1)
*
* @param {Time} time
* @return {Seconds}
*/
Tone.prototype.toSeconds = function (time) {
if (Tone.isNumber(time)) {
return time;
} else if (Tone.isUndef(time)) {
return this.now();
} else if (Tone.isString(time)) {
return new Tone.Time(time).toSeconds();
} else if (time instanceof Tone.TimeBase) {
return time.toSeconds();
}
};
/**
* Convert a frequency representation into a number.
* @param {Frequency} freq
* @return {Hertz} the frequency in hertz
*/
Tone.prototype.toFrequency = function (freq) {
if (Tone.isNumber(freq)) {
return freq;
} else if (Tone.isString(freq) || Tone.isUndef(freq)) {
return new Tone.Frequency(freq).valueOf();
} else if (freq instanceof Tone.TimeBase) {
return freq.toFrequency();
}
};
/**
* Convert a time representation into ticks.
* @param {Time} time
* @return {Ticks} the time in ticks
*/
Tone.prototype.toTicks = function (time) {
if (Tone.isNumber(time) || Tone.isString(time)) {
return new Tone.TransportTime(time).toTicks();
} else if (Tone.isUndef(time)) {
return Tone.Transport.ticks;
} else if (time instanceof Tone.TimeBase) {
return time.toTicks();
}
};
return Tone;
});
Module(function (Tone) {
/**
* @class Tone.Param wraps the native Web Audio's AudioParam to provide
* additional unit conversion functionality. It also
* serves as a base-class for classes which have a single,
* automatable parameter.
* @extends {Tone.AudioNode}
* @param {AudioParam} param The parameter to wrap.
* @param {Tone.Type} units The units of the audio param.
* @param {Boolean} convert If the param should be converted.
*/
Tone.Param = function () {
var options = Tone.defaults(arguments, [
'param',
'units',
'convert'
], Tone.Param);
Tone.AudioNode.call(this);
/**
* The native parameter to control
* @type {AudioParam}
* @private
*/
this._param = this.input = options.param;
/**
* The units of the parameter
* @type {Tone.Type}
*/
this.units = options.units;
/**
* If the value should be converted or not
* @type {Boolean}
*/
this.convert = options.convert;
/**
* True if the signal value is being overridden by
* a connected signal.
* @readOnly
* @type {boolean}
* @private
*/
this.overridden = false;
/**
* The timeline which tracks all of the automations.
* @type {Tone.Timeline}
* @private
*/
this._events = new Tone.Timeline(1000);
if (Tone.isDefined(options.value) && this._param) {
this.value = options.value;
}
};
Tone.extend(Tone.Param, Tone.AudioNode);
/**
* Defaults
* @type {Object}
* @const
*/
Tone.Param.defaults = {
'units': Tone.Type.Default,
'convert': true,
'param': undefined
};
/**
* The current value of the parameter.
* @memberOf Tone.Param#
* @type {Number}
* @name value
*/
Object.defineProperty(Tone.Param.prototype, 'value', {
get: function () {
var now = this.now();
return this._toUnits(this.getValueAtTime(now));
},
set: function (value) {
this._initialValue = this._fromUnits(value);
this.cancelScheduledValues(this.context.currentTime);
this.setValueAtTime(value, this.context.currentTime);
}
});
/**
* The minimum output value of the parameter
* @memberOf Tone.Param#
* @type {Number}
* @name value
*/
Object.defineProperty(Tone.Param.prototype, 'minValue', {
get: function () {
if (this.units === Tone.Type.Time || this.units === Tone.Type.Frequency || this.units === Tone.Type.NormalRange || this.units === Tone.Type.Positive || this.units === Tone.Type.BPM) {
return 0;
} else if (this.units === Tone.Type.AudioRange) {
return -1;
} else if (this.units === Tone.Type.Decibels) {
return -Infinity;
} else {
return this._param.minValue;
}
}
});
/**
* The maximum output value of the parameter
* @memberOf Tone.Param#
* @type {Number}
* @name value
*/
Object.defineProperty(Tone.Param.prototype, 'maxValue', {
get: function () {
if (this.units === Tone.Type.NormalRange || this.units === Tone.Type.AudioRange) {
return 1;
} else {
return this._param.maxValue;
}
}
});
/**
* Convert the given value from the type specified by Tone.Param.units
* into the destination value (such as Gain or Frequency).
* @private
* @param {*} val the value to convert
* @return {number} the number which the value should be set to
*/
Tone.Param.prototype._fromUnits = function (val) {
if ((this.convert || Tone.isUndef(this.convert)) && !this.overridden) {
switch (this.units) {
case Tone.Type.Time:
return this.toSeconds(val);
case Tone.Type.Frequency:
return this.toFrequency(val);
case Tone.Type.Decibels:
return Tone.dbToGain(val);
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 the parameters value into the units specified by Tone.Param.units.
* @private
* @param {number} val the value to convert
* @return {number}
*/
Tone.Param.prototype._toUnits = function (val) {
if (this.convert || Tone.isUndef(this.convert)) {
switch (this.units) {
case Tone.Type.Decibels:
return Tone.gainToDb(val);
default:
return val;
}
} else {
return val;
}
};
/**
* the minimum output value
* @type {Number}
* @private
*/
Tone.Param.prototype._minOutput = 0.00001;
/**
* The event types
* @enum {String}
* @private
*/
Tone.Param.AutomationType = {
Linear: 'linearRampToValueAtTime',
Exponential: 'exponentialRampToValueAtTime',
Target: 'setTargetAtTime',
SetValue: 'setValueAtTime'
};
/**
* Schedules a parameter value change at the given time.
* @param {*} value The value to set the signal.
* @param {Time} time The time when the change should occur.
* @returns {Tone.Param} this
* @example
* //set the frequency to "G4" in exactly 1 second from now.
* freq.setValueAtTime("G4", "+1");
*/
Tone.Param.prototype.setValueAtTime = function (value, time) {
time = this.toSeconds(time);
value = this._fromUnits(value);
this._events.add({
'type': Tone.Param.AutomationType.SetValue,
'value': value,
'time': time
});
this._param.setValueAtTime(value, time);
return this;
};
/**
* Get the signals value at the given time. Subsequent scheduling
* may invalidate the returned value.
* @param {Time} time When to get the value
* @returns {Number} The value at the given time
*/
Tone.Param.prototype.getValueAtTime = function (time) {
time = this.toSeconds(time);
var after = this._events.getAfter(time);
var before = this._events.get(time);
var initialValue = Tone.defaultArg(this._initialValue, this._param.defaultValue);
var value = initialValue;
//if it was set by
if (before === null) {
value = initialValue;
} else if (before.type === Tone.Param.AutomationType.Target) {
var previous = this._events.getBefore(before.time);
var previousVal;
if (previous === null) {
previousVal = initialValue;
} else {
previousVal = previous.value;
}
value = this._exponentialApproach(before.time, previousVal, before.value, before.constant, time);
} else if (after === null) {
value = before.value;
} else if (after.type === Tone.Param.AutomationType.Linear) {
value = this._linearInterpolate(before.time, before.value, after.time, after.value, time);
} else if (after.type === Tone.Param.AutomationType.Exponential) {
value = this._exponentialInterpolate(before.time, before.value, after.time, after.value, time);
} else {
value = before.value;
}
return value;
};
/**
* Creates a schedule point with the current value at the current time.
* This is useful for creating an automation anchor point in order to
* schedule changes from the current value.
*
* @param {number=} now (Optionally) pass the now value in.
* @returns {Tone.Param} this
*/
Tone.Param.prototype.setRampPoint = function (time) {
time = this.toSeconds(time);
var currentVal = this.getValueAtTime(time);
this.cancelAndHoldAtTime(time);
if (currentVal === 0) {
currentVal = this._minOutput;
}
this.setValueAtTime(this._toUnits(currentVal), time);
return this;
};
/**
* Schedules a linear continuous change in parameter value from the
* previous scheduled parameter value to the given value.
*
* @param {number} value
* @param {Time} endTime
* @returns {Tone.Param} this
*/
Tone.Param.prototype.linearRampToValueAtTime = function (value, endTime) {
value = this._fromUnits(value);
endTime = this.toSeconds(endTime);
this._events.add({
'type': Tone.Param.AutomationType.Linear,
'value': value,
'time': endTime
});
this._param.linearRampToValueAtTime(value, endTime);
return this;
};
/**
* Schedules an exponential continuous change in parameter value from
* the previous scheduled parameter value to the given value.
*
* @param {number} value
* @param {Time} endTime
* @returns {Tone.Param} this
*/
Tone.Param.prototype.exponentialRampToValueAtTime = function (value, endTime) {
value = this._fromUnits(value);
value = Math.max(this._minOutput, value);
endTime = this.toSeconds(endTime);
//store the event
this._events.add({
'type': Tone.Param.AutomationType.Exponential,
'time': endTime,
'value': value
});
this._param.exponentialRampToValueAtTime(value, endTime);
return this;
};
/**
* Schedules an exponential continuous change in parameter value from
* the current time and current value to the given value over the
* duration of the rampTime.
*
* @param {number} value The value to ramp to.
* @param {Time} rampTime the time that it takes the
* value to ramp from it's current value
* @param {Time} [startTime=now] When the ramp should start.
* @returns {Tone.Param} this
* @example
* //exponentially ramp to the value 2 over 4 seconds.
* signal.exponentialRampTo(2, 4);
*/
Tone.Param.prototype.exponentialRampTo = function (value, rampTime, startTime) {
startTime = this.toSeconds(startTime);
this.setRampPoint(startTime);
this.exponentialRampToValueAtTime(value, startTime + this.toSeconds(rampTime));
return this;
};
/**
* Schedules an linear continuous change in parameter value from
* the current time and current value to the given value over the
* duration of the rampTime.
*
* @param {number} value The value to ramp to.
* @param {Time} rampTime the time that it takes the
* value to ramp from it's current value
* @param {Time} [startTime=now] When the ramp should start.
* @returns {Tone.Param} this
* @example
* //linearly ramp to the value 4 over 3 seconds.
* signal.linearRampTo(4, 3);
*/
Tone.Param.prototype.linearRampTo = function (value, rampTime, startTime) {
startTime = this.toSeconds(startTime);
this.setRampPoint(startTime);
this.linearRampToValueAtTime(value, startTime + this.toSeconds(rampTime));
return this;
};
/**
* Start exponentially approaching the target value at the given time. Since it
* is an exponential approach it will continue approaching after the ramp duration. The
* rampTime is the time that it takes to reach over 99% of the way towards the value.
* @param {number} value The value to ramp to.
* @param {Time} rampTime the time that it takes the
* value to ramp from it's current value
* @param {Time} [startTime=now] When the ramp should start.
* @returns {Tone.Param} this
* @example
* //exponentially ramp to the value 2 over 4 seconds.
* signal.exponentialRampTo(2, 4);
*/
Tone.Param.prototype.targetRampTo = function (value, rampTime, startTime) {
startTime = this.toSeconds(startTime);
this.setRampPoint(startTime);
this.exponentialApproachValueAtTime(value, startTime, rampTime);
return this;
};
/**
* Start exponentially approaching the target value at the given time. Since it
* is an exponential approach it will continue approaching after the ramp duration. The
* rampTime is the time that it takes to reach over 99% of the way towards the value. This methods
* is similar to setTargetAtTime except the third argument is a time instead of a 'timeConstant'
* @param {number} value The value to ramp to.
* @param {Time} time When the ramp should start.
* @param {Time} rampTime the time that it takes the
* value to ramp from it's current value
* @returns {Tone.Param} this
* @example
* //exponentially ramp to the value 2 over 4 seconds.
* signal.exponentialRampTo(2, 4);
*/
Tone.Param.prototype.exponentialApproachValueAtTime = function (value, time, rampTime) {
var timeConstant = Math.log(this.toSeconds(rampTime) + 1) / Math.log(200);
time = this.toSeconds(time);
return this.setTargetAtTime(value, time, timeConstant);
};
/**
* Start exponentially approaching the target value at the given time with
* a rate having the given time constant.
* @param {number} value
* @param {Time} startTime
* @param {number} timeConstant
* @returns {Tone.Param} this
*/
Tone.Param.prototype.setTargetAtTime = function (value, startTime, timeConstant) {
value = this._fromUnits(value);
// The value will never be able to approach without timeConstant > 0.
if (timeConstant <= 0) {
throw new Error('timeConstant must be greater than 0');
}
startTime = this.toSeconds(startTime);
this._events.add({
'type': Tone.Param.AutomationType.Target,
'value': value,
'time': startTime,
'constant': timeConstant
});
this._param.setTargetAtTime(value, startTime, timeConstant);
return this;
};
/**
* Sets an array of arbitrary parameter values starting at the given time
* for the given duration.
*
* @param {Array} values
* @param {Time} startTime
* @param {Time} duration
* @param {NormalRange} [scaling=1] If the values in the curve should be scaled by some value
* @returns {Tone.Param} this
*/
Tone.Param.prototype.setValueCurveAtTime = function (values, startTime, duration, scaling) {
scaling = Tone.defaultArg(scaling, 1);
duration = this.toSeconds(duration);
startTime = this.toSeconds(startTime);
this.setValueAtTime(values[0] * scaling, startTime);
var segTime = duration / (values.length - 1);
for (var i = 1; i < values.length; i++) {
this.linearRampToValueAtTime(values[i] * scaling, startTime + i * segTime);
}
return this;
};
/**
* Cancels all scheduled parameter changes with times greater than or
* equal to startTime.
*
* @param {Time} time
* @returns {Tone.Param} this
*/
Tone.Param.prototype.cancelScheduledValues = function (time) {
time = this.toSeconds(time);
this._events.cancel(time);
this._param.cancelScheduledValues(time);
return this;
};
/**
* This is similar to [cancelScheduledValues](#cancelScheduledValues) except
* it holds the automated value at time until the next automated event.
* @param {Time} time
* @returns {Tone.Param} this
*/
Tone.Param.prototype.cancelAndHoldAtTime = function (time) {
var valueAtTime = this.getValueAtTime(time);
//if there is an event at the given time
//and that even is not a "set"
var before = this._events.get(time);
var after = this._events.getAfter(time);
if (before && before.time === time) {
//remove everything after
if (after) {
this._events.cancel(after.time);
} else {
this._events.cancel(time + 0.000001);
}
} else if (after) {
//cancel the next event(s)
this._events.cancel(after.time);
if (!this._param.cancelAndHoldAtTime) {
this._param.cancelScheduledValues(time);
}
if (after.type === Tone.Param.AutomationType.Linear) {
if (!this._param.cancelAndHoldAtTime) {
this.linearRampToValueAtTime(valueAtTime, time);
} else {
this._events.add({
'type': Tone.Param.AutomationType.Linear,
'value': valueAtTime,
'time': time
});
}
} else if (after.type === Tone.Param.AutomationType.Exponential) {
if (!this._param.cancelAndHoldAtTime) {
this.exponentialRampToValueAtTime(valueAtTime, time);
} else {
this._events.add({
'type': Tone.Param.AutomationType.Exponential,
'value': valueAtTime,
'time': time
});
}
}
}
//set the value at the given time
this._events.add({
'type': Tone.Param.AutomationType.SetValue,
'value': valueAtTime,
'time': time
});
if (this._param.cancelAndHoldAtTime) {
this._param.cancelAndHoldAtTime(time);
} else {
this._param.setValueAtTime(valueAtTime, time);
}
return this;
};
/**
* 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
* @param {Time} rampTime The time that it takes the
* value to ramp from it's current value
* @param {Time} [startTime=now] When the ramp should start.
* @returns {Tone.Param} this
* @example
* //ramp to the value either linearly or exponentially
* //depending on the "units" value of the signal
* signal.rampTo(0, 10);
* @example
* //schedule it to ramp starting at a specific time
* signal.rampTo(0, 10, 5)
*/
Tone.Param.prototype.rampTo = function (value, rampTime, startTime) {
rampTime = Tone.defaultArg(rampTime, 0.1);
if (this.units === Tone.Type.Frequency || this.units === Tone.Type.BPM || this.units === Tone.Type.Decibels) {
this.exponentialRampTo(value, rampTime, startTime);
} else {
this.linearRampTo(value, rampTime, startTime);
}
return this;
};
///////////////////////////////////////////////////////////////////////////
// AUTOMATION CURVE CALCULATIONS
// MIT License, copyright (c) 2014 Jordan Santell
///////////////////////////////////////////////////////////////////////////
// Calculates the the value along the curve produced by setTargetAtTime
Tone.Param.prototype._exponentialApproach = function (t0, v0, v1, timeConstant, t) {
return v1 + (v0 - v1) * Math.exp(-(t - t0) / timeConstant);
};
// Calculates the the value along the curve produced by linearRampToValueAtTime
Tone.Param.prototype._linearInterpolate = function (t0, v0, t1, v1, t) {
return v0 + (v1 - v0) * ((t - t0) / (t1 - t0));
};
// Calculates the the value along the curve produced by exponentialRampToValueAtTime
Tone.Param.prototype._exponentialInterpolate = function (t0, v0, t1, v1, t) {
return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0));
};
/**
* Clean up
* @returns {Tone.Param} this
*/
Tone.Param.prototype.dispose = function () {
Tone.AudioNode.prototype.dispose.call(this);
this._param = null;
this._events = null;
return this;
};
return Tone.Param;
});
Module(function (Tone) {
/**
* @class Wrapper around the OfflineAudioContext
* @extends {Tone.Context}
* @param {Number} channels The number of channels to render
* @param {Number} duration The duration to render in samples
* @param {Number} sampleRate the sample rate to render at
*/
Tone.OfflineContext = function (channels, duration, sampleRate) {
/**
* The offline context
* @private
* @type {OfflineAudioContext}
*/
var offlineContext = new OfflineAudioContext(channels, duration * sampleRate, sampleRate);
//wrap the methods/members
Tone.Context.call(this, {
'context': offlineContext,
'clockSource': 'offline',
'lookAhead': 0,
'updateInterval': 128 / sampleRate
});
/**
* A private reference to the duration
* @private
* @type {Number}
*/
this._duration = duration;
/**
* An artificial clock source
* @type {Number}
* @private
*/
this._currentTime = 0;
};
Tone.extend(Tone.OfflineContext, Tone.Context);
/**
* Override the now method to point to the internal clock time
* @return {Number}
*/
Tone.OfflineContext.prototype.now = function () {
return this._currentTime;
};
/**
* Render the output of the OfflineContext
* @return {Promise}
*/
Tone.OfflineContext.prototype.render = function () {
while (this._duration - this._currentTime >= 0) {
//invoke all the callbacks on that time
this.emit('tick');
//increment the clock
this._currentTime += this.blockTime;
}
return this._context.startRendering();
};
/**
* Close the context
* @return {Promise}
*/
Tone.OfflineContext.prototype.close = function () {
this._context = null;
return Promise.resolve();
};
return Tone.OfflineContext;
});
Module(function (Tone) {
if (Tone.supported) {
var ua = navigator.userAgent.toLowerCase();
var isMobileSafari = ua.includes('safari') && !ua.includes('chrome') && ua.includes('mobile');
if (isMobileSafari) {
//mobile safari has a bizarre bug with the offline context
//when a BufferSourceNode is started, it starts the offline context
//
//deferring all BufferSource starts till the last possible moment
//reduces the likelihood of this happening
Tone.OfflineContext.prototype.createBufferSource = function () {
var bufferSource = this._context.createBufferSource();
var _native_start = bufferSource.start;
bufferSource.start = function (time) {
this.setTimeout(function () {
_native_start.call(bufferSource, time);
}.bind(this), 0);
}.bind(this);
return bufferSource;
};
}
}
});
Module(function (Tone) {
/**
* @class A thin wrapper around the Native Web Audio GainNode.
* The GainNode is a basic building block of the Web Audio
* API and is useful for routing audio and adjusting gains.
* @extends {Tone}
* @param {Number=} gain The initial gain of the GainNode
* @param {Tone.Type=} units The units of the gain parameter.
*/
Tone.Gain = function () {
var options = Tone.defaults(arguments, [
'gain',
'units'
], Tone.Gain);
Tone.AudioNode.call(this);
/**
* The GainNode
* @type {GainNode}
* @private
*/
this.input = this.output = this._gainNode = this.context.createGain();
/**
* The gain parameter of the gain node.
* @type {Gain}
* @signal
*/
this.gain = new Tone.Param({
'param': this._gainNode.gain,
'units': options.units,
'value': options.gain,
'convert': options.convert
});
this._readOnly('gain');
};
Tone.extend(Tone.Gain, Tone.AudioNode);
/**
* The defaults
* @const
* @type {Object}
*/
Tone.Gain.defaults = {
'gain': 1,
'convert': true
};
/**
* Clean up.
* @return {Tone.Gain} this
*/
Tone.Gain.prototype.dispose = function () {
Tone.AudioNode.prototype.dispose.call(this);
this._gainNode.disconnect();
this._gainNode = null;
this._writable('gain');
this.gain.dispose();
this.gain = null;
};
return Tone.Gain;
});
Module(function (Tone) {
if (Tone.supported && !AudioContext.prototype.createConstantSource) {
var ConstantSourceNode = function (context) {
this.context = context;
var buffer = context.createBuffer(1, 128, context.sampleRate);
var arr = buffer.getChannelData(0);
for (var i = 0; i < arr.length; i++) {
arr[i] = 1;
}
this._bufferSource = context.createBufferSource();
this._bufferSource.channelCount = 1;
this._bufferSource.channelCountMode = 'explicit';
this._bufferSource.buffer = buffer;
this._bufferSource.loop = true;
var gainNode = this._output = context.createGain();
this.offset = gainNode.gain;
this._bufferSource.connect(gainNode);
};
ConstantSourceNode.prototype.start = function (time) {
this._bufferSource.start(time);
return this;
};
ConstantSourceNode.prototype.stop = function (time) {
this._bufferSource.stop(time);
return this;
};
ConstantSourceNode.prototype.connect = function () {
this._output.connect.apply(this._output, arguments);
return this;
};
ConstantSourceNode.prototype.disconnect = function () {
this._output.disconnect.apply(this._output, arguments);
return this;
};
AudioContext.prototype.createConstantSource = function () {
return new ConstantSourceNode(this);
};
Tone.Context.prototype.createConstantSource = function () {
return new ConstantSourceNode(this);
};
}
});
Module(function (Tone) {
/**
* @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 connect.
*
* @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 = [];
}
return Tone.SignalBase.prototype.disconnect.apply(this, arguments);
};
/**
* 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;
});
Module(function (Tone) {
/**
* @class Pow applies an exponent to the incoming signal. The incoming signal
* must be AudioRange.
*
* @extends {Tone.SignalBase}
* @constructor
* @param {Positive} exp The exponent to apply to the incoming signal, must be at least 2.
* @example
* var pow = new Tone.Pow(2);
* var sig = new Tone.Signal(0.5).connect(pow);
* //output of pow is 0.25.
*/
Tone.Pow = function (exp) {
Tone.SignalBase.call(this);
/**
* the exponent
* @private
* @type {number}
*/
this._exp = Tone.defaultArg(exp, 1);
/**
* @type {WaveShaperNode}
* @private
*/
this._expScaler = this.input = this.output = new Tone.WaveShaper(this._expFunc(this._exp), 8192);
};
Tone.extend(Tone.Pow, Tone.SignalBase);
/**
* The value of the exponent.
* @memberOf Tone.Pow#
* @type {number}
* @name value
*/
Object.defineProperty(Tone.Pow.prototype, 'value', {
get: function () {
return this._exp;
},
set: function (exp) {
this._exp = exp;
this._expScaler.setMap(this._expFunc(this._exp));
}
});
/**
* the function which maps the waveshaper
* @param {number} exp
* @return {function}
* @private
*/
Tone.Pow.prototype._expFunc = function (exp) {
return function (val) {
return Math.pow(Math.abs(val), exp);
};
};
/**
* Clean up.
* @returns {Tone.Pow} this
*/
Tone.Pow.prototype.dispose = function () {
Tone.SignalBase.prototype.dispose.call(this);
this._expScaler.dispose();
this._expScaler = null;
return this;
};
return Tone.Pow;
});
Module(function (Tone) {
/**
* @class Tone.Envelope is an [ADSR](https://en.wikipedia.org/wiki/Synthesizer#ADSR_envelope)
* envelope generator. Tone.Envelope outputs a signal which
* can be connected to an AudioParam or Tone.Signal.
*
*
* @constructor
* @extends {Tone.AudioNode}
* @param {Time} [attack] The amount of time it takes for the envelope to go from
* 0 to it's maximum value.
* @param {Time} [decay] The period of time after the attack that it takes for the envelope
* to fall to the sustain value.
* @param {NormalRange} [sustain] The percent of the maximum value that the envelope rests at until
* the release is triggered.
* @param {Time} [release] The amount of time after the release is triggered it takes to reach 0.
* @example
* //an amplitude envelope
* var gainNode = Tone.context.createGain();
* var env = new Tone.Envelope({
* "attack" : 0.1,
* "decay" : 0.2,
* "sustain" : 1,
* "release" : 0.8,
* });
* env.connect(gainNode.gain);
*/
Tone.Envelope = function () {
//get all of the defaults
var options = Tone.defaults(arguments, [
'attack',
'decay',
'sustain',
'release'
], Tone.Envelope);
Tone.AudioNode.call(this);
/**
* When triggerAttack is called, the attack time is the amount of
* time it takes for the envelope to reach it's maximum value.
* @type {Time}
*/
this.attack = options.attack;
/**
* After the attack portion of the envelope, the value will fall
* over the duration of the decay time to it's sustain value.
* @type {Time}
*/
this.decay = options.decay;
/**
* The sustain value is the value
* which the envelope rests at after triggerAttack is
* called, but before triggerRelease is invoked.
* @type {NormalRange}
*/
this.sustain = options.sustain;
/**
* After triggerRelease is called, the envelope's
* value will fall to it's miminum value over the
* duration of the release time.
* @type {Time}
*/
this.release = options.release;
/**
* the next time the envelope is at standby
* @type {number}
* @private
*/
this._attackCurve = 'linear';
/**
* the next time the envelope is at standby
* @type {number}
* @private
*/
this._releaseCurve = 'exponential';
/**
* the signal
* @type {Tone.Signal}
* @private
*/
this._sig = this.output = new Tone.Signal(0);
//set the attackCurve initially
this.attackCurve = options.attackCurve;
this.releaseCurve = options.releaseCurve;
};
Tone.extend(Tone.Envelope, Tone.AudioNode);
/**
* the default parameters
* @static
* @const
*/
Tone.Envelope.defaults = {
'attack': 0.01,
'decay': 0.1,
'sustain': 0.5,
'release': 1,
'attackCurve': 'linear',
'releaseCurve': 'exponential'
};
/**
* Read the current value of the envelope. Useful for
* syncronizing visual output to the envelope.
* @memberOf Tone.Envelope#
* @type {Number}
* @name value
* @readOnly
*/
Object.defineProperty(Tone.Envelope.prototype, 'value', {
get: function () {
return this.getValueAtTime(this.now());
}
});
/**
* The shape of the attack.
* Can be any of these strings:
*
*
linear
*
exponential
*
sine
*
cosine
*
bounce
*
ripple
*
step
*
* Can also be an array which describes the curve. Values
* in the array are evenly subdivided and linearly
* interpolated over the duration of the attack.
* @memberOf Tone.Envelope#
* @type {String|Array}
* @name attackCurve
* @example
* env.attackCurve = "linear";
* @example
* //can also be an array
* env.attackCurve = [0, 0.2, 0.3, 0.4, 1]
*/
Object.defineProperty(Tone.Envelope.prototype, 'attackCurve', {
get: function () {
if (Tone.isString(this._attackCurve)) {
return this._attackCurve;
} else if (Tone.isArray(this._attackCurve)) {
//look up the name in the curves array
for (var type in Tone.Envelope.Type) {
if (Tone.Envelope.Type[type].In === this._attackCurve) {
return type;
}
}
//otherwise just return the array
return this._attackCurve;
}
},
set: function (curve) {
//check if it's a valid type
if (Tone.Envelope.Type.hasOwnProperty(curve)) {
var curveDef = Tone.Envelope.Type[curve];
if (Tone.isObject(curveDef)) {
this._attackCurve = curveDef.In;
} else {
this._attackCurve = curveDef;
}
} else if (Tone.isArray(curve)) {
this._attackCurve = curve;
} else {
throw new Error('Tone.Envelope: invalid curve: ' + curve);
}
}
});
/**
* The shape of the release. See the attack curve types.
* @memberOf Tone.Envelope#
* @type {String|Array}
* @name releaseCurve
* @example
* env.releaseCurve = "linear";
*/
Object.defineProperty(Tone.Envelope.prototype, 'releaseCurve', {
get: function () {
if (Tone.isString(this._releaseCurve)) {
return this._releaseCurve;
} else if (Tone.isArray(this._releaseCurve)) {
//look up the name in the curves array
for (var type in Tone.Envelope.Type) {
if (Tone.Envelope.Type[type].Out === this._releaseCurve) {
return type;
}
}
//otherwise just return the array
return this._releaseCurve;
}
},
set: function (curve) {
//check if it's a valid type
if (Tone.Envelope.Type.hasOwnProperty(curve)) {
var curveDef = Tone.Envelope.Type[curve];
if (Tone.isObject(curveDef)) {
this._releaseCurve = curveDef.Out;
} else {
this._releaseCurve = curveDef;
}
} else if (Tone.isArray(curve)) {
this._releaseCurve = curve;
} else {
throw new Error('Tone.Envelope: invalid curve: ' + curve);
}
}
});
/**
* Trigger the attack/decay portion of the ADSR envelope.
* @param {Time} [time=now] When the attack should start.
* @param {NormalRange} [velocity=1] The velocity of the envelope scales the vales.
* number between 0-1
* @returns {Tone.Envelope} this
* @example
* //trigger the attack 0.5 seconds from now with a velocity of 0.2
* env.triggerAttack("+0.5", 0.2);
*/
Tone.Envelope.prototype.triggerAttack = function (time, velocity) {
time = this.toSeconds(time);
var originalAttack = this.toSeconds(this.attack);
var attack = originalAttack;
var decay = this.toSeconds(this.decay);
velocity = Tone.defaultArg(velocity, 1);
//check if it's not a complete attack
var currentValue = this.getValueAtTime(time);
if (currentValue > 0) {
//subtract the current value from the attack time
var attackRate = 1 / attack;
var remainingDistance = 1 - currentValue;
//the attack is now the remaining time
attack = remainingDistance / attackRate;
}
//attack
if (this._attackCurve === 'linear') {
this._sig.linearRampTo(velocity, attack, time);
} else if (this._attackCurve === 'exponential') {
this._sig.targetRampTo(velocity, attack, time);
} else if (attack > 0) {
this._sig.cancelAndHoldAtTime(time);
var curve = this._attackCurve;
//take only a portion of the curve
if (attack < originalAttack) {
var percentComplete = 1 - attack / originalAttack;
var sliceIndex = Math.floor(percentComplete * this._attackCurve.length);
curve = this._attackCurve.slice(sliceIndex);
//the first index is the current value
curve[0] = currentValue;
}
this._sig.setValueCurveAtTime(curve, time, attack, velocity);
}
//decay
if (decay) {
this._sig.targetRampTo(velocity * this.sustain, decay, attack + time);
}
return this;
};
/**
* Triggers the release of the envelope.
* @param {Time} [time=now] When the release portion of the envelope should start.
* @returns {Tone.Envelope} this
* @example
* //trigger release immediately
* env.triggerRelease();
*/
Tone.Envelope.prototype.triggerRelease = function (time) {
time = this.toSeconds(time);
var currentValue = this.getValueAtTime(time);
if (currentValue > 0) {
var release = this.toSeconds(this.release);
if (this._releaseCurve === 'linear') {
this._sig.linearRampTo(0, release, time);
} else if (this._releaseCurve === 'exponential') {
this._sig.targetRampTo(0, release, time);
} else {
var curve = this._releaseCurve;
if (Tone.isArray(curve)) {
this._sig.cancelAndHoldAtTime(time);
this._sig.setValueCurveAtTime(curve, time, release, currentValue);
}
}
}
return this;
};
/**
* Get the scheduled value at the given time. This will
* return the unconverted (raw) value.
* @param {Number} time The time in seconds.
* @return {Number} The scheduled value at the given time.
*/
Tone.Envelope.prototype.getValueAtTime = function (time) {
return this._sig.getValueAtTime(time);
};
/**
* triggerAttackRelease is shorthand for triggerAttack, then waiting
* some duration, then triggerRelease.
* @param {Time} duration The duration of the sustain.
* @param {Time} [time=now] When the attack should be triggered.
* @param {number} [velocity=1] The velocity of the envelope.
* @returns {Tone.Envelope} this
* @example
* //trigger the attack and then the release after 0.6 seconds.
* env.triggerAttackRelease(0.6);
*/
Tone.Envelope.prototype.triggerAttackRelease = function (duration, time, velocity) {
time = this.toSeconds(time);
this.triggerAttack(time, velocity);
this.triggerRelease(time + this.toSeconds(duration));
return this;
};
/**
* Cancels all scheduled envelope changes after the given time.
* @param {Time} after
* @returns {Tone.Envelope} this
*/
Tone.Envelope.prototype.cancel = function (after) {
this._sig.cancelScheduledValues(after);
return this;
};
/**
* Borrows the connect method from Tone.Signal.
* @function
* @private
*/
Tone.Envelope.prototype.connect = Tone.SignalBase.prototype.connect;
/**
* Generate some complex envelope curves.
*/
(function _createCurves() {
var curveLen = 128;
var i, k;
//cosine curve
var cosineCurve = [];
for (i = 0; i < curveLen; i++) {
cosineCurve[i] = Math.sin(i / (curveLen - 1) * (Math.PI / 2));
}
//ripple curve
var rippleCurve = [];
var rippleCurveFreq = 6.4;
for (i = 0; i < curveLen - 1; i++) {
k = i / (curveLen - 1);
var sineWave = Math.sin(k * (Math.PI * 2) * rippleCurveFreq - Math.PI / 2) + 1;
rippleCurve[i] = sineWave / 10 + k * 0.83;
}
rippleCurve[curveLen - 1] = 1;
//stairs curve
var stairsCurve = [];
var steps = 5;
for (i = 0; i < curveLen; i++) {
stairsCurve[i] = Math.ceil(i / (curveLen - 1) * steps) / steps;
}
//in-out easing curve
var sineCurve = [];
for (i = 0; i < curveLen; i++) {
k = i / (curveLen - 1);
sineCurve[i] = 0.5 * (1 - Math.cos(Math.PI * k));
}
//a bounce curve
var bounceCurve = [];
for (i = 0; i < curveLen; i++) {
k = i / (curveLen - 1);
var freq = Math.pow(k, 3) * 4 + 0.2;
var val = Math.cos(freq * Math.PI * 2 * k);
bounceCurve[i] = Math.abs(val * (1 - k));
}
/**
* Invert a value curve to make it work for the release
* @private
*/
function invertCurve(curve) {
var out = new Array(curve.length);
for (var j = 0; j < curve.length; j++) {
out[j] = 1 - curve[j];
}
return out;
}
/**
* reverse the curve
* @private
*/
function reverseCurve(curve) {
return curve.slice(0).reverse();
}
/**
* attack and release curve arrays
* @type {Object}
* @private
*/
Tone.Envelope.Type = {
'linear': 'linear',
'exponential': 'exponential',
'bounce': {
In: invertCurve(bounceCurve),
Out: bounceCurve
},
'cosine': {
In: cosineCurve,
Out: reverseCurve(cosineCurve)
},
'step': {
In: stairsCurve,
Out: invertCurve(stairsCurve)
},
'ripple': {
In: rippleCurve,
Out: invertCurve(rippleCurve)
},
'sine': {
In: sineCurve,
Out: invertCurve(sineCurve)
}
};
}());
/**
* Disconnect and dispose.
* @returns {Tone.Envelope} this
*/
Tone.Envelope.prototype.dispose = function () {
Tone.AudioNode.prototype.dispose.call(this);
this._sig.dispose();
this._sig = null;
this._attackCurve = null;
this._releaseCurve = null;
return this;
};
return Tone.Envelope;
});
Module(function (Tone) {
/**
* @class Tone.AmplitudeEnvelope is a Tone.Envelope connected to a gain node.
* Unlike Tone.Envelope, which outputs the envelope's value, Tone.AmplitudeEnvelope accepts
* an audio signal as the input and will apply the envelope to the amplitude
* of the signal. Read more about ADSR Envelopes on [Wikipedia](https://en.wikipedia.org/wiki/Synthesizer#ADSR_envelope).
*
* @constructor
* @extends {Tone.Envelope}
* @param {Time|Object} [attack] The amount of time it takes for the envelope to go from
* 0 to it's maximum value.
* @param {Time} [decay] The period of time after the attack that it takes for the envelope
* to fall to the sustain value.
* @param {NormalRange} [sustain] The percent of the maximum value that the envelope rests at until
* the release is triggered.
* @param {Time} [release] The amount of time after the release is triggered it takes to reach 0.
* @example
* var ampEnv = new Tone.AmplitudeEnvelope({
* "attack": 0.1,
* "decay": 0.2,
* "sustain": 1.0,
* "release": 0.8
* }).toMaster();
* //create an oscillator and connect it
* var osc = new Tone.Oscillator().connect(ampEnv).start();
* //trigger the envelopes attack and release "8t" apart
* ampEnv.triggerAttackRelease("8t");
*/
Tone.AmplitudeEnvelope = function () {
Tone.Envelope.apply(this, arguments);
/**
* the input node
* @type {GainNode}
* @private
*/
this.input = this.output = new Tone.Gain();
this._sig.connect(this.output.gain);
};
Tone.extend(Tone.AmplitudeEnvelope, Tone.Envelope);
/**
* Clean up
* @return {Tone.AmplitudeEnvelope} this
*/
Tone.AmplitudeEnvelope.prototype.dispose = function () {
Tone.Envelope.prototype.dispose.call(this);
return this;
};
return Tone.AmplitudeEnvelope;
});
Module(function (Tone) {
/**
* AnalyserNode.getFloatTimeDomainData polyfill
* @private
*/
if (Tone.supported) {
if (!AnalyserNode.prototype.getFloatTimeDomainData) {
//referenced https://github.com/mohayonao/get-float-time-domain-data
AnalyserNode.prototype.getFloatTimeDomainData = function (array) {
var uint8 = new Uint8Array(array.length);
this.getByteTimeDomainData(uint8);
for (var i = 0; i < uint8.length; i++) {
array[i] = (uint8[i] - 128) / 128;
}
};
}
}
});
Module(function (Tone) {
/**
* @class Wrapper around the native Web Audio's
* [AnalyserNode](http://webaudio.github.io/web-audio-api/#idl-def-AnalyserNode).
* Extracts FFT or Waveform data from the incoming signal.
* @extends {Tone.AudioNode}
* @param {String=} type The return type of the analysis, either "fft", or "waveform".
* @param {Number=} size The size of the FFT. Value must be a power of
* two in the range 32 to 32768.
*/
Tone.Analyser = function () {
var options = Tone.defaults(arguments, [
'type',
'size'
], Tone.Analyser);
Tone.AudioNode.call(this);
/**
* The analyser node.
* @private
* @type {AnalyserNode}
*/
this._analyser = this.input = this.output = this.context.createAnalyser();
/**
* The analysis type
* @type {String}
* @private
*/
this._type = options.type;
/**
* The buffer that the FFT data is written to
* @type {TypedArray}
* @private
*/
this._buffer = null;
//set the values initially
this.size = options.size;
this.type = options.type;
};
Tone.extend(Tone.Analyser, Tone.AudioNode);
/**
* The default values.
* @type {Object}
* @const
*/
Tone.Analyser.defaults = {
'size': 1024,
'type': 'fft',
'smoothing': 0.8
};
/**
* Possible return types of analyser.getValue()
* @enum {String}
*/
Tone.Analyser.Type = {
Waveform: 'waveform',
FFT: 'fft'
};
/**
* Run the analysis given the current settings and return the
* result as a TypedArray.
* @returns {TypedArray}
*/
Tone.Analyser.prototype.getValue = function () {
if (this._type === Tone.Analyser.Type.FFT) {
this._analyser.getFloatFrequencyData(this._buffer);
} else if (this._type === Tone.Analyser.Type.Waveform) {
this._analyser.getFloatTimeDomainData(this._buffer);
}
return this._buffer;
};
/**
* The size of analysis. This must be a power of two in the range 32 to 32768.
* @memberOf Tone.Analyser#
* @type {Number}
* @name size
*/
Object.defineProperty(Tone.Analyser.prototype, 'size', {
get: function () {
return this._analyser.frequencyBinCount;
},
set: function (size) {
this._analyser.fftSize = size * 2;
this._buffer = new Float32Array(size);
}
});
/**
* The analysis function returned by analyser.getValue(), either "fft" or "waveform".
* @memberOf Tone.Analyser#
* @type {String}
* @name type
*/
Object.defineProperty(Tone.Analyser.prototype, 'type', {
get: function () {
return this._type;
},
set: function (type) {
if (type !== Tone.Analyser.Type.Waveform && type !== Tone.Analyser.Type.FFT) {
throw new TypeError('Tone.Analyser: invalid type: ' + type);
}
this._type = type;
}
});
/**
* 0 represents no time averaging with the last analysis frame.
* @memberOf Tone.Analyser#
* @type {NormalRange}
* @name smoothing
*/
Object.defineProperty(Tone.Analyser.prototype, 'smoothing', {
get: function () {
return this._analyser.smoothingTimeConstant;
},
set: function (val) {
this._analyser.smoothingTimeConstant = val;
}
});
/**
* Clean up.
* @return {Tone.Analyser} this
*/
Tone.Analyser.prototype.dispose = function () {
Tone.AudioNode.prototype.dispose.call(this);
this._analyser.disconnect();
this._analyser = null;
this._buffer = null;
};
return Tone.Analyser;
});
Module(function (Tone) {
/**
* @class Tone.Compressor is a thin wrapper around the Web Audio
* [DynamicsCompressorNode](http://webaudio.github.io/web-audio-api/#the-dynamicscompressornode-interface).
* Compression reduces the volume of loud sounds or amplifies quiet sounds
* by narrowing or "compressing" an audio signal's dynamic range.
* Read more on [Wikipedia](https://en.wikipedia.org/wiki/Dynamic_range_compression).
*
* @extends {Tone.AudioNode}
* @constructor
* @param {Decibels|Object} [threshold] The value above which the compression starts to be applied.
* @param {Positive} [ratio] The gain reduction ratio.
* @example
* var comp = new Tone.Compressor(-30, 3);
*/
Tone.Compressor = function () {
var options = Tone.defaults(arguments, [
'threshold',
'ratio'
], Tone.Compressor);
Tone.AudioNode.call(this);
/**
* the compressor node
* @type {DynamicsCompressorNode}
* @private
*/
this._compressor = this.input = this.output = this.context.createDynamicsCompressor();
/**
* the threshold vaue
* @type {Decibels}
* @signal
*/
this.threshold = new Tone.Param({
'param': this._compressor.threshold,
'units': Tone.Type.Decibels,
'convert': false
});
/**
* The attack parameter
* @type {Time}
* @signal
*/
this.attack = new Tone.Param(this._compressor.attack, Tone.Type.Time);
/**
* The release parameter
* @type {Time}
* @signal
*/
this.release = new Tone.Param(this._compressor.release, Tone.Type.Time);
/**
* The knee parameter
* @type {Decibels}
* @signal
*/
this.knee = new Tone.Param({
'param': this._compressor.knee,
'units': Tone.Type.Decibels,
'convert': false
});
/**
* The ratio value
* @type {Number}
* @signal
*/
this.ratio = new Tone.Param({
'param': this._compressor.ratio,
'convert': false
});
//set the defaults
this._readOnly([
'knee',
'release',
'attack',
'ratio',
'threshold'
]);
this.set(options);
};
Tone.extend(Tone.Compressor, Tone.AudioNode);
/**
* @static
* @const
* @type {Object}
*/
Tone.Compressor.defaults = {
'ratio': 12,
'threshold': -24,
'release': 0.25,
'attack': 0.003,
'knee': 30
};
/**
* clean up
* @returns {Tone.Compressor} this
*/
Tone.Compressor.prototype.dispose = function () {
Tone.AudioNode.prototype.dispose.call(this);
this._writable([
'knee',
'release',
'attack',
'ratio',
'threshold'
]);
this._compressor.disconnect();
this._compressor = null;
this.attack.dispose();
this.attack = null;
this.release.dispose();
this.release = null;
this.threshold.dispose();
this.threshold = null;
this.ratio.dispose();
this.ratio = null;
this.knee.dispose();
this.knee = null;
return this;
};
return Tone.Compressor;
});
Module(function (Tone) {
/**
* @class Add a signal and a number or two signals. When no value is
* passed into the constructor, Tone.Add will sum input[0]
* and input[1]. If a value is passed into the constructor,
* the it will be added to the input.
*
* @constructor
* @extends {Tone.Signal}
* @param {number=} value If no value is provided, Tone.Add will sum the first
* and second inputs.
* @example
* var signal = new Tone.Signal(2);
* var add = new Tone.Add(2);
* signal.connect(add);
* //the output of add equals 4
* @example
* //if constructed with no arguments
* //it will add the first and second inputs
* var add = new Tone.Add();
* var sig0 = new Tone.Signal(3).connect(add, 0, 0);
* var sig1 = new Tone.Signal(4).connect(add, 0, 1);
* //the output of add equals 7.
*/
Tone.Add = function (value) {
Tone.Signal.call(this);
this.createInsOuts(2, 0);
/**
* the summing node
* @type {GainNode}
* @private
*/
this._sum = this.input[0] = this.input[1] = this.output = new Tone.Gain();
/**
* @private
* @type {Tone.Signal}
*/
this._param = this.input[1] = new Tone.Signal(value);
this._param.connect(this._sum);
};
Tone.extend(Tone.Add, Tone.Signal);
/**
* Clean up.
* @returns {Tone.Add} this
*/
Tone.Add.prototype.dispose = function () {
Tone.Signal.prototype.dispose.call(this);
this._sum.dispose();
this._sum = null;
return this;
};
return Tone.Add;
});
Module(function (Tone) {
/**
* @class Multiply two incoming signals. Or, if a number is given in the constructor,
* multiplies the incoming signal by that value.
*
* @constructor
* @extends {Tone.Signal}
* @param {number=} value Constant value to multiple. If no value is provided,
* it will return the product of the first and second inputs
* @example
* var mult = new Tone.Multiply();
* var sigA = new Tone.Signal(3);
* var sigB = new Tone.Signal(4);
* sigA.connect(mult, 0, 0);
* sigB.connect(mult, 0, 1);
* //output of mult is 12.
* @example
* var mult = new Tone.Multiply(10);
* var sig = new Tone.Signal(2).connect(mult);
* //the output of mult is 20.
*/
Tone.Multiply = function (value) {
Tone.Signal.call(this);
this.createInsOuts(2, 0);
/**
* the input node is the same as the output node
* it is also the GainNode which handles the scaling of incoming signal
*
* @type {GainNode}
* @private
*/
this._mult = this.input[0] = this.output = new Tone.Gain();
/**
* the scaling parameter
* @type {AudioParam}
* @private
*/
this._param = this.input[1] = this.output.gain;
this.value = Tone.defaultArg(value, 0);
};
Tone.extend(Tone.Multiply, Tone.Signal);
/**
* clean up
* @returns {Tone.Multiply} this
*/
Tone.Multiply.prototype.dispose = function () {
Tone.Signal.prototype.dispose.call(this);
this._mult.dispose();
this._mult = null;
this._param = null;
return this;
};
return Tone.Multiply;
});
Module(function (Tone) {
/**
* @class Negate the incoming signal. i.e. an input signal of 10 will output -10
*
* @constructor
* @extends {Tone.SignalBase}
* @example
* var neg = new Tone.Negate();
* var sig = new Tone.Signal(-2).connect(neg);
* //output of neg is positive 2.
*/
Tone.Negate = function () {
Tone.SignalBase.call(this);
/**
* negation is done by multiplying by -1
* @type {Tone.Multiply}
* @private
*/
this._multiply = this.input = this.output = new Tone.Multiply(-1);
};
Tone.extend(Tone.Negate, Tone.SignalBase);
/**
* clean up
* @returns {Tone.Negate} this
*/
Tone.Negate.prototype.dispose = function () {
Tone.SignalBase.prototype.dispose.call(this);
this._multiply.dispose();
this._multiply = null;
return this;
};
return Tone.Negate;
});
Module(function (Tone) {
/**
* @class Subtract the signal connected to input[1] from the signal connected
* to input[0]. If an argument is provided in the constructor, the
* signals .value will be subtracted from the incoming signal.
*
* @extends {Tone.Signal}
* @constructor
* @param {number=} value The value to subtract from the incoming signal. If the value
* is omitted, it will subtract the second signal from the first.
* @example
* var sub = new Tone.Subtract(1);
* var sig = new Tone.Signal(4).connect(sub);
* //the output of sub is 3.
* @example
* var sub = new Tone.Subtract();
* var sigA = new Tone.Signal(10);
* var sigB = new Tone.Signal(2.5);
* sigA.connect(sub, 0, 0);
* sigB.connect(sub, 0, 1);
* //output of sub is 7.5
*/
Tone.Subtract = function (value) {
Tone.Signal.call(this);
this.createInsOuts(2, 0);
/**
* the summing node
* @type {GainNode}
* @private
*/
this._sum = this.input[0] = this.output = new Tone.Gain();
/**
* negate the input of the second input before connecting it
* to the summing node.
* @type {Tone.Negate}
* @private
*/
this._neg = new Tone.Negate();
/**
* the node where the value is set
* @private
* @type {Tone.Signal}
*/
this._param = this.input[1] = new Tone.Signal(value);
this._param.chain(this._neg, this._sum);
};
Tone.extend(Tone.Subtract, Tone.Signal);
/**
* Clean up.
* @returns {Tone.SignalBase} this
*/
Tone.Subtract.prototype.dispose = function () {
Tone.Signal.prototype.dispose.call(this);
this._neg.dispose();
this._neg = null;
this._sum.disconnect();
this._sum = null;
return this;
};
return Tone.Subtract;
});
Module(function (Tone) {
/**
* @class Convert an incoming signal between 0, 1 to an equal power gain scale.
*
* @extends {Tone.SignalBase}
* @constructor
* @example
* var eqPowGain = new Tone.EqualPowerGain();
*/
Tone.EqualPowerGain = function () {
Tone.SignalBase.call(this);
/**
* @type {Tone.WaveShaper}
* @private
*/
this._eqPower = this.input = this.output = new Tone.WaveShaper(function (val) {
if (Math.abs(val) < 0.001) {
//should output 0 when input is 0
return 0;
} else {
return Tone.equalPowerScale(val);
}
}.bind(this), 4096);
};
Tone.extend(Tone.EqualPowerGain, Tone.SignalBase);
/**
* clean up
* @returns {Tone.EqualPowerGain} this
*/
Tone.EqualPowerGain.prototype.dispose = function () {
Tone.SignalBase.prototype.dispose.call(this);
this._eqPower.dispose();
this._eqPower = null;
return this;
};
return Tone.EqualPowerGain;
});
Module(function (Tone) {
/**
* @class Tone.Crossfade provides equal power fading between two inputs.
* More on crossfading technique [here](https://en.wikipedia.org/wiki/Fade_(audio_engineering)#Crossfading).
*
* @constructor
* @extends {Tone.AudioNode}
* @param {NormalRange} [initialFade=0.5]
* @example
* var crossFade = new Tone.CrossFade(0.5);
* //connect effect A to crossfade from
* //effect output 0 to crossfade input 0
* effectA.connect(crossFade, 0, 0);
* //connect effect B to crossfade from
* //effect output 0 to crossfade input 1
* effectB.connect(crossFade, 0, 1);
* crossFade.fade.value = 0;
* // ^ only effectA is output
* crossFade.fade.value = 1;
* // ^ only effectB is output
* crossFade.fade.value = 0.5;
* // ^ the two signals are mixed equally.
*/
Tone.CrossFade = function (initialFade) {
Tone.AudioNode.call(this);
this.createInsOuts(2, 1);
/**
* Alias for input[0].
* @type {Tone.Gain}
*/
this.a = this.input[0] = new Tone.Gain();
/**
* Alias for input[1].
* @type {Tone.Gain}
*/
this.b = this.input[1] = new Tone.Gain();
/**
* The mix between the two inputs. A fade value of 0
* will output 100% input[0] and
* a value of 1 will output 100% input[1].
* @type {NormalRange}
* @signal
*/
this.fade = new Tone.Signal(Tone.defaultArg(initialFade, 0.5), Tone.Type.NormalRange);
/**
* equal power gain cross fade
* @private
* @type {Tone.EqualPowerGain}
*/
this._equalPowerA = new Tone.EqualPowerGain();
/**
* equal power gain cross fade
* @private
* @type {Tone.EqualPowerGain}
*/
this._equalPowerB = new Tone.EqualPowerGain();
/**
* invert the incoming signal
* @private
* @type {Tone}
*/
this._one = this.context.getConstant(1);
/**
* invert the incoming signal
* @private
* @type {Tone.Subtract}
*/
this._invert = new Tone.Subtract();
//connections
this.a.connect(this.output);
this.b.connect(this.output);
this.fade.chain(this._equalPowerB, this.b.gain);
this._one.connect(this._invert, 0, 0);
this.fade.connect(this._invert, 0, 1);
this._invert.chain(this._equalPowerA, this.a.gain);
this._readOnly('fade');
};
Tone.extend(Tone.CrossFade, Tone.AudioNode);
/**
* clean up
* @returns {Tone.CrossFade} this
*/
Tone.CrossFade.prototype.dispose = function () {
Tone.AudioNode.prototype.dispose.call(this);
this._writable('fade');
this._equalPowerA.dispose();
this._equalPowerA = null;
this._equalPowerB.dispose();
this._equalPowerB = null;
this.fade.dispose();
this.fade = null;
this._invert.dispose();
this._invert = null;
this._one = null;
this.a.dispose();
this.a = null;
this.b.dispose();
this.b = null;
return this;
};
return Tone.CrossFade;
});
Module(function (Tone) {
/**
* @class Tone.Filter is a filter which allows for all of the same native methods
* as the [BiquadFilterNode](http://webaudio.github.io/web-audio-api/#the-biquadfilternode-interface).
* Tone.Filter has the added ability to set the filter rolloff at -12
* (default), -24 and -48.
*
* @constructor
* @extends {Tone.AudioNode}
* @param {Frequency|Object} [frequency] The cutoff frequency of the filter.
* @param {string=} type The type of filter.
* @param {number=} rolloff The drop in decibels per octave after the cutoff frequency.
* 3 choices: -12, -24, and -48
* @example
* var filter = new Tone.Filter(200, "highpass");
*/
Tone.Filter = function () {
var options = Tone.defaults(arguments, [
'frequency',
'type',
'rolloff'
], Tone.Filter);
Tone.AudioNode.call(this);
this.createInsOuts(1, 1);
/**
* the filter(s)
* @type {Array}
* @private
*/
this._filters = [];
/**
* The cutoff frequency of the filter.
* @type {Frequency}
* @signal
*/
this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency);
/**
* The detune parameter
* @type {Cents}
* @signal
*/
this.detune = new Tone.Signal(0, Tone.Type.Cents);
/**
* The gain of the filter, only used in certain filter types
* @type {Number}
* @signal
*/
this.gain = new Tone.Signal({
'value': options.gain,
'convert': false
});
/**
* The Q or Quality of the filter
* @type {Positive}
* @signal
*/
this.Q = new Tone.Signal(options.Q);
/**
* the type of the filter
* @type {string}
* @private
*/
this._type = options.type;
/**
* the rolloff value of the filter
* @type {number}
* @private
*/
this._rolloff = options.rolloff;
//set the rolloff;
this.rolloff = options.rolloff;
this._readOnly([
'detune',
'frequency',
'gain',
'Q'
]);
};
Tone.extend(Tone.Filter, Tone.AudioNode);
/**
* the default parameters
*
* @static
* @type {Object}
*/
Tone.Filter.defaults = {
'type': 'lowpass',
'frequency': 350,
'rolloff': -12,
'Q': 1,
'gain': 0
};
/**
* The type of the filter. Types: "lowpass", "highpass",
* "bandpass", "lowshelf", "highshelf", "notch", "allpass", or "peaking".
* @memberOf Tone.Filter#
* @type {string}
* @name type
*/
Object.defineProperty(Tone.Filter.prototype, 'type', {
get: function () {
return this._type;
},
set: function (type) {
var types = [
'lowpass',
'highpass',
'bandpass',
'lowshelf',
'highshelf',
'notch',
'allpass',
'peaking'
];
if (types.indexOf(type) === -1) {
throw new TypeError('Tone.Filter: invalid type ' + type);
}
this._type = type;
for (var i = 0; i < this._filters.length; i++) {
this._filters[i].type = type;
}
}
});
/**
* The rolloff of the filter which is the drop in db
* per octave. Implemented internally by cascading filters.
* Only accepts the values -12, -24, -48 and -96.
* @memberOf Tone.Filter#
* @type {number}
* @name rolloff
*/
Object.defineProperty(Tone.Filter.prototype, 'rolloff', {
get: function () {
return this._rolloff;
},
set: function (rolloff) {
rolloff = parseInt(rolloff, 10);
var possibilities = [
-12,
-24,
-48,
-96
];
var cascadingCount = possibilities.indexOf(rolloff);
//check the rolloff is valid
if (cascadingCount === -1) {
throw new RangeError('Tone.Filter: rolloff can only be -12, -24, -48 or -96');
}
cascadingCount += 1;
this._rolloff = rolloff;
//first disconnect the filters and throw them away
this.input.disconnect();
for (var i = 0; i < this._filters.length; i++) {
this._filters[i].disconnect();
this._filters[i] = null;
}
this._filters = new Array(cascadingCount);
for (var count = 0; count < cascadingCount; count++) {
var filter = this.context.createBiquadFilter();
filter.type = this._type;
this.frequency.connect(filter.frequency);
this.detune.connect(filter.detune);
this.Q.connect(filter.Q);
this.gain.connect(filter.gain);
this._filters[count] = filter;
}
//connect them up
var connectionChain = [this.input].concat(this._filters).concat([this.output]);
Tone.connectSeries.apply(Tone, connectionChain);
}
});
/**
* Clean up.
* @return {Tone.Filter} this
*/
Tone.Filter.prototype.dispose = function () {
Tone.AudioNode.prototype.dispose.call(this);
for (var i = 0; i < this._filters.length; i++) {
this._filters[i].disconnect();
this._filters[i] = null;
}
this._filters = null;
this._writable([
'detune',
'frequency',
'gain',
'Q'
]);
this.frequency.dispose();
this.Q.dispose();
this.frequency = null;
this.Q = null;
this.detune.dispose();
this.detune = null;
this.gain.dispose();
this.gain = null;
return this;
};
return Tone.Filter;
});
Module(function (Tone) {
/**
* @class Split the incoming signal into three bands (low, mid, high)
* with two crossover frequency controls.
*
* @extends {Tone.AudioNode}
* @constructor
* @param {Frequency|Object} [lowFrequency] the low/mid crossover frequency
* @param {Frequency} [highFrequency] the mid/high crossover frequency
*/
Tone.MultibandSplit = function () {
var options = Tone.defaults(arguments, [
'lowFrequency',
'highFrequency'
], Tone.MultibandSplit);
Tone.AudioNode.call(this);
/**
* the input
* @type {Tone.Gain}
* @private
*/
this.input = new Tone.Gain();
/**
* the outputs
* @type {Array}
* @private
*/
this.output = new Array(3);
/**
* The low band. Alias for output[0]
* @type {Tone.Filter}
*/
this.low = this.output[0] = new Tone.Filter(0, 'lowpass');
/**
* the lower filter of the mid band
* @type {Tone.Filter}
* @private
*/
this._lowMidFilter = new Tone.Filter(0, 'highpass');
/**
* The mid band output. Alias for output[1]
* @type {Tone.Filter}
*/
this.mid = this.output[1] = new Tone.Filter(0, 'lowpass');
/**
* The high band output. Alias for output[2]
* @type {Tone.Filter}
*/
this.high = this.output[2] = new Tone.Filter(0, 'highpass');
/**
* The low/mid crossover frequency.
* @type {Frequency}
* @signal
*/
this.lowFrequency = new Tone.Signal(options.lowFrequency, Tone.Type.Frequency);
/**
* The mid/high crossover frequency.
* @type {Frequency}
* @signal
*/
this.highFrequency = new Tone.Signal(options.highFrequency, Tone.Type.Frequency);
/**
* The quality of all the filters
* @type {Number}
* @signal
*/
this.Q = new Tone.Signal(options.Q);
this.input.fan(this.low, this.high);
this.input.chain(this._lowMidFilter, this.mid);
//the frequency control signal
this.lowFrequency.connect(this.low.frequency);
this.lowFrequency.connect(this._lowMidFilter.frequency);
this.highFrequency.connect(this.mid.frequency);
this.highFrequency.connect(this.high.frequency);
//the Q value
this.Q.connect(this.low.Q);
this.Q.connect(this._lowMidFilter.Q);
this.Q.connect(this.mid.Q);
this.Q.connect(this.high.Q);
this._readOnly([
'high',
'mid',
'low',
'highFrequency',
'lowFrequency'
]);
};
Tone.extend(Tone.MultibandSplit, Tone.AudioNode);
/**
* @private
* @static
* @type {Object}
*/
Tone.MultibandSplit.defaults = {
'lowFrequency': 400,
'highFrequency': 2500,
'Q': 1
};
/**
* Clean up.
* @returns {Tone.MultibandSplit} this
*/
Tone.MultibandSplit.prototype.dispose = function () {
Tone.AudioNode.prototype.dispose.call(this);
this._writable([
'high',
'mid',
'low',
'highFrequency',
'lowFrequency'
]);
this.low.dispose();
this.low = null;
this._lowMidFilter.dispose();
this._lowMidFilter = null;
this.mid.dispose();
this.mid = null;
this.high.dispose();
this.high = null;
this.lowFrequency.dispose();
this.lowFrequency = null;
this.highFrequency.dispose();
this.highFrequency = null;
this.Q.dispose();
this.Q = null;
return this;
};
return Tone.MultibandSplit;
});
Module(function (Tone) {
/**
* @class Tone.EQ3 is a three band EQ with control over low, mid, and high gain as
* well as the low and high crossover frequencies.
*
* @constructor
* @extends {Tone.AudioNode}
*
* @param {Decibels|Object} [lowLevel] The gain applied to the lows.
* @param {Decibels} [midLevel] The gain applied to the mid.
* @param {Decibels} [highLevel] The gain applied to the high.
* @example
* var eq = new Tone.EQ3(-10, 3, -20);
*/
Tone.EQ3 = function () {
var options = Tone.defaults(arguments, [
'low',
'mid',
'high'
], Tone.EQ3);
Tone.AudioNode.call(this);
/**
* the output node
* @type {GainNode}
* @private
*/
this.output = new Tone.Gain();
/**
* the multiband split
* @type {Tone.MultibandSplit}
* @private
*/
this._multibandSplit = this.input = new Tone.MultibandSplit({
'lowFrequency': options.lowFrequency,
'highFrequency': options.highFrequency
});
/**
* The gain for the lower signals
* @type {Tone.Gain}
* @private
*/
this._lowGain = new Tone.Gain(options.low, Tone.Type.Decibels);
/**
* The gain for the mid signals
* @type {Tone.Gain}
* @private
*/
this._midGain = new Tone.Gain(options.mid, Tone.Type.Decibels);
/**
* The gain in decibels of the high part
* @type {Tone.Gain}
* @private
*/
this._highGain = new Tone.Gain(options.high, Tone.Type.Decibels);
/**
* The gain in decibels of the low part
* @type {Decibels}
* @signal
*/
this.low = this._lowGain.gain;
/**
* The gain in decibels of the mid part
* @type {Decibels}
* @signal
*/
this.mid = this._midGain.gain;
/**
* The gain in decibels of the high part
* @type {Decibels}
* @signal
*/
this.high = this._highGain.gain;
/**
* The Q value for all of the filters.
* @type {Positive}
* @signal
*/
this.Q = this._multibandSplit.Q;
/**
* The low/mid crossover frequency.
* @type {Frequency}
* @signal
*/
this.lowFrequency = this._multibandSplit.lowFrequency;
/**
* The mid/high crossover frequency.
* @type {Frequency}
* @signal
*/
this.highFrequency = this._multibandSplit.highFrequency;
//the frequency bands
this._multibandSplit.low.chain(this._lowGain, this.output);
this._multibandSplit.mid.chain(this._midGain, this.output);
this._multibandSplit.high.chain(this._highGain, this.output);
this._readOnly([
'low',
'mid',
'high',
'lowFrequency',
'highFrequency'
]);
};
Tone.extend(Tone.EQ3, Tone.AudioNode);
/**
* the default values
*/
Tone.EQ3.defaults = {
'low': 0,
'mid': 0,
'high': 0,
'lowFrequency': 400,
'highFrequency': 2500
};
/**
* clean up
* @returns {Tone.EQ3} this
*/
Tone.EQ3.prototype.dispose = function () {
Tone.AudioNode.prototype.dispose.call(this);
this._writable([
'low',
'mid',
'high',
'lowFrequency',
'highFrequency'
]);
this._multibandSplit.dispose();
this._multibandSplit = null;
this.lowFrequency = null;
this.highFrequency = null;
this._lowGain.dispose();
this._lowGain = null;
this._midGain.dispose();
this._midGain = null;
this._highGain.dispose();
this._highGain = null;
this.low = null;
this.mid = null;
this.high = null;
this.Q = null;
return this;
};
return Tone.EQ3;
});
Module(function (Tone) {
/**
* @class Performs a linear scaling on an input signal.
* Scales a NormalRange input to between
* outputMin and outputMax.
*
* @constructor
* @extends {Tone.SignalBase}
* @param {number} [outputMin=0] The output value when the input is 0.
* @param {number} [outputMax=1] The output value when the input is 1.
* @example
* var scale = new Tone.Scale(50, 100);
* var signal = new Tone.Signal(0.5).connect(scale);
* //the output of scale equals 75
*/
Tone.Scale = function (outputMin, outputMax) {
Tone.SignalBase.call(this);
/**
* @private
* @type {number}
*/
this._outputMin = Tone.defaultArg(outputMin, 0);
/**
* @private
* @type {number}
*/
this._outputMax = Tone.defaultArg(outputMax, 1);
/**
* @private
* @type {Tone.Multiply}
* @private
*/
this._scale = this.input = new Tone.Multiply(1);
/**
* @private
* @type {Tone.Add}
* @private
*/
this._add = this.output = new Tone.Add(0);
this._scale.connect(this._add);
this._setRange();
};
Tone.extend(Tone.Scale, Tone.SignalBase);
/**
* The minimum output value. This number is output when
* the value input value is 0.
* @memberOf Tone.Scale#
* @type {number}
* @name min
*/
Object.defineProperty(Tone.Scale.prototype, 'min', {
get: function () {
return this._outputMin;
},
set: function (min) {
this._outputMin = min;
this._setRange();
}
});
/**
* The maximum output value. This number is output when
* the value input value is 1.
* @memberOf Tone.Scale#
* @type {number}
* @name max
*/
Object.defineProperty(Tone.Scale.prototype, 'max', {
get: function () {
return this._outputMax;
},
set: function (max) {
this._outputMax = max;
this._setRange();
}
});
/**
* set the values
* @private
*/
Tone.Scale.prototype._setRange = function () {
this._add.value = this._outputMin;
this._scale.value = this._outputMax - this._outputMin;
};
/**
* Clean up.
* @returns {Tone.Scale} this
*/
Tone.Scale.prototype.dispose = function () {
Tone.SignalBase.prototype.dispose.call(this);
this._add.dispose();
this._add = null;
this._scale.dispose();
this._scale = null;
return this;
};
return Tone.Scale;
});
Module(function (Tone) {
/**
* @class Performs an exponential scaling on an input signal.
* Scales a NormalRange value [0,1] exponentially
* to the output range of outputMin to outputMax.
*
* @constructor
* @extends {Tone.SignalBase}
* @param {number} [outputMin=0] The output value when the input is 0.
* @param {number} [outputMax=1] The output value when the input is 1.
* @param {number} [exponent=2] The exponent which scales the incoming signal.
* @example
* var scaleExp = new Tone.ScaleExp(0, 100, 2);
* var signal = new Tone.Signal(0.5).connect(scaleExp);
*/
Tone.ScaleExp = function (outputMin, outputMax, exponent) {
Tone.SignalBase.call(this);
/**
* scale the input to the output range
* @type {Tone.Scale}
* @private
*/
this._scale = this.output = new Tone.Scale(outputMin, outputMax);
/**
* @private
* @type {Tone.Pow}
* @private
*/
this._exp = this.input = new Tone.Pow(Tone.defaultArg(exponent, 2));
this._exp.connect(this._scale);
};
Tone.extend(Tone.ScaleExp, Tone.SignalBase);
/**
* Instead of interpolating linearly between the min and
* max values, setting the exponent will interpolate between
* the two values with an exponential curve.
* @memberOf Tone.ScaleExp#
* @type {number}
* @name exponent
*/
Object.defineProperty(Tone.ScaleExp.prototype, 'exponent', {
get: function () {
return this._exp.value;
},
set: function (exp) {
this._exp.value = exp;
}
});
/**
* The minimum output value. This number is output when
* the value input value is 0.
* @memberOf Tone.ScaleExp#
* @type {number}
* @name min
*/
Object.defineProperty(Tone.ScaleExp.prototype, 'min', {
get: function () {
return this._scale.min;
},
set: function (min) {
this._scale.min = min;
}
});
/**
* The maximum output value. This number is output when
* the value input value is 1.
* @memberOf Tone.ScaleExp#
* @type {number}
* @name max
*/
Object.defineProperty(Tone.ScaleExp.prototype, 'max', {
get: function () {
return this._scale.max;
},
set: function (max) {
this._scale.max = max;
}
});
/**
* Clean up.
* @returns {Tone.ScaleExp} this
*/
Tone.ScaleExp.prototype.dispose = function () {
Tone.SignalBase.prototype.dispose.call(this);
this._scale.dispose();
this._scale = null;
this._exp.dispose();
this._exp = null;
return this;
};
return Tone.ScaleExp;
});
Module(function (Tone) {
/**
* @class Wrapper around Web Audio's native [DelayNode](http://webaudio.github.io/web-audio-api/#the-delaynode-interface).
* @extends {Tone}
* @param {Time=} delayTime The delay applied to the incoming signal.
* @param {Time=} maxDelay The maximum delay time.
*/
Tone.Delay = function () {
var options = Tone.defaults(arguments, [
'delayTime',
'maxDelay'
], Tone.Delay);
Tone.AudioNode.call(this);
/**
* The maximum delay time initialized with the node
* @type {Number}
* @private
*/
this._maxDelay = Math.max(this.toSeconds(options.maxDelay), this.toSeconds(options.delayTime));
/**
* The native delay node
* @type {DelayNode}
* @private
*/
this._delayNode = this.input = this.output = this.context.createDelay(this._maxDelay);
/**
* The amount of time the incoming signal is
* delayed.
* @type {Time}
* @signal
*/
this.delayTime = new Tone.Param({
'param': this._delayNode.delayTime,
'units': Tone.Type.Time,
'value': options.delayTime
});
this._readOnly('delayTime');
};
Tone.extend(Tone.Delay, Tone.AudioNode);
/**
* The defaults
* @const
* @type {Object}
*/
Tone.Delay.defaults = {
'maxDelay': 1,
'delayTime': 0
};
/**
* The maximum delay time. This cannot be changed. The value is passed into the constructor.
* @memberof Tone.Delay#
* @type {Time}
* @name maxDelay
* @readOnly
*/
Object.defineProperty(Tone.Delay.prototype, 'maxDelay', {
get: function () {
return this._maxDelay;
}
});
/**
* Clean up.
* @return {Tone.Delay} this
*/
Tone.Delay.prototype.dispose = function () {
Tone.AudioNode.prototype.dispose.call(this);
this._delayNode.disconnect();
this._delayNode = null;
this._writable('delayTime');
this.delayTime = null;
return this;
};
return Tone.Delay;
});
Module(function (Tone) {
/**
* @class Comb filters are basic building blocks for physical modeling. Read more
* about comb filters on [CCRMA's website](https://ccrma.stanford.edu/~jos/pasp/Feedback_Comb_Filters.html).
*
* @extends {Tone.AudioNode}
* @constructor
* @param {Time|Object} [delayTime] The delay time of the filter.
* @param {NormalRange=} resonance The amount of feedback the filter has.
*/
Tone.FeedbackCombFilter = function () {
var options = Tone.defaults(arguments, [
'delayTime',
'resonance'
], Tone.FeedbackCombFilter);
Tone.AudioNode.call(this);
/**
* the delay node
* @type {DelayNode}
* @private
*/
this._delay = this.input = this.output = new Tone.Delay(options.delayTime);
/**
* The amount of delay of the comb filter.
* @type {Time}
* @signal
*/
this.delayTime = this._delay.delayTime;
/**
* the feedback node
* @type {GainNode}
* @private
*/
this._feedback = new Tone.Gain(options.resonance, Tone.Type.NormalRange);
/**
* The amount of feedback of the delayed signal.
* @type {NormalRange}
* @signal
*/
this.resonance = this._feedback.gain;
this._delay.chain(this._feedback, this._delay);
this._readOnly([
'resonance',
'delayTime'
]);
};
Tone.extend(Tone.FeedbackCombFilter, Tone.AudioNode);
/**
* the default parameters
* @static
* @const
* @type {Object}
*/
Tone.FeedbackCombFilter.defaults = {
'delayTime': 0.1,
'resonance': 0.5
};
/**
* clean up
* @returns {Tone.FeedbackCombFilter} this
*/
Tone.FeedbackCombFilter.prototype.dispose = function () {
Tone.AudioNode.prototype.dispose.call(this);
this._writable([
'resonance',
'delayTime'
]);
this._delay.dispose();
this._delay = null;
this.delayTime = null;
this._feedback.dispose();
this._feedback = null;
this.resonance = null;
return this;
};
return Tone.FeedbackCombFilter;
});
Module(function (Tone) {
/**
* @class Get the current waveform data of the connected audio source.
* @extends {Tone.AudioNode}
* @param {Number=} size The size of the FFT. Value must be a power of
* two in the range 32 to 32768.
*/
Tone.FFT = function () {
var options = Tone.defaults(arguments, ['size'], Tone.FFT);
options.type = Tone.Analyser.Type.FFT;
Tone.AudioNode.call(this);
/**
* The analyser node.
* @private
* @type {Tone.Analyser}
*/
this._analyser = this.input = this.output = new Tone.Analyser(options);
};
Tone.extend(Tone.FFT, Tone.AudioNode);
/**
* The default values.
* @type {Object}
* @const
*/
Tone.FFT.defaults = { 'size': 1024 };
/**
* Gets the waveform of the audio source. Returns the waveform data
* of length [size](#size) as a Float32Array with values between -1 and 1.
* @returns {TypedArray}
*/
Tone.FFT.prototype.getValue = function () {
return this._analyser.getValue();
};
/**
* The size of analysis. This must be a power of two in the range 32 to 32768.
* @memberOf Tone.FFT#
* @type {Number}
* @name size
*/
Object.defineProperty(Tone.FFT.prototype, 'size', {
get: function () {
return this._analyser.size;
},
set: function (size) {
this._analyser.size = size;
}
});
/**
* Clean up.
* @return {Tone.FFT} this
*/
Tone.FFT.prototype.dispose = function () {
Tone.AudioNode.prototype.dispose.call(this);
this._analyser.dispose();
this._analyser = null;
};
return Tone.FFT;
});
Module(function (Tone) {
/**
* @class Return the absolute value of an incoming signal.
*
* @constructor
* @extends {Tone.SignalBase}
* @example
* var signal = new Tone.Signal(-1);
* var abs = new Tone.Abs();
* signal.connect(abs);
* //the output of abs is 1.
*/
Tone.Abs = function () {
Tone.SignalBase.call(this);
/**
* @type {Tone.LessThan}
* @private
*/
this._abs = this.input = this.output = new Tone.WaveShaper(function (val) {
if (Math.abs(val) < 0.001) {
return 0;
} else {
return Math.abs(val);
}
}, 1024);
};
Tone.extend(Tone.Abs, Tone.SignalBase);
/**
* dispose method
* @returns {Tone.Abs} this
*/
Tone.Abs.prototype.dispose = function () {
Tone.SignalBase.prototype.dispose.call(this);
this._abs.dispose();
this._abs = null;
return this;
};
return Tone.Abs;
});
Module(function (Tone) {
/**
* @class Tone.Follower is a crude envelope follower which will follow
* the amplitude of an incoming signal.
* Take care with small (< 0.02) attack or decay values
* as follower has some ripple which is exaggerated
* at these values. Read more about envelope followers (also known
* as envelope detectors) on [Wikipedia](https://en.wikipedia.org/wiki/Envelope_detector).
*
* @constructor
* @extends {Tone.AudioNode}
* @param {Time|Object} [attack] The rate at which the follower rises.
* @param {Time=} release The rate at which the folower falls.
* @example
* var follower = new Tone.Follower(0.2, 0.4);
*/
Tone.Follower = function () {
var options = Tone.defaults(arguments, [
'attack',
'release'
], Tone.Follower);
Tone.AudioNode.call(this);
this.createInsOuts(1, 1);
/**
* @type {Tone.Abs}
* @private
*/
this._abs = new Tone.Abs();
/**
* the lowpass filter which smooths the input
* @type {BiquadFilterNode}
* @private
*/
this._filter = this.context.createBiquadFilter();
this._filter.type = 'lowpass';
this._filter.frequency.value = 0;
this._filter.Q.value = -100;
/**
* @type {WaveShaperNode}
* @private
*/
this._frequencyValues = new Tone.WaveShaper();
/**
* @type {Tone.Subtract}
* @private
*/
this._sub = new Tone.Subtract();
/**
* @type {Tone.Delay}
* @private
*/
this._delay = new Tone.Delay(this.blockTime);
/**
* this keeps it far from 0, even for very small differences
* @type {Tone.Multiply}
* @private
*/
this._mult = new Tone.Multiply(10000);
/**
* @private
* @type {number}
*/
this._attack = options.attack;
/**
* @private
* @type {number}
*/
this._release = options.release;
//the smoothed signal to get the values
this.input.chain(this._abs, this._filter, this.output);
//the difference path
this._abs.connect(this._sub, 0, 1);
this._filter.chain(this._delay, this._sub);
//threshold the difference and use the thresh to set the frequency
this._sub.chain(this._mult, this._frequencyValues, this._filter.frequency);
//set the attack and release values in the table
this._setAttackRelease(this._attack, this._release);
};
Tone.extend(Tone.Follower, Tone.AudioNode);
/**
* @static
* @type {Object}
*/
Tone.Follower.defaults = {
'attack': 0.05,
'release': 0.5
};
/**
* sets the attack and release times in the wave shaper
* @param {Time} attack
* @param {Time} release
* @private
*/
Tone.Follower.prototype._setAttackRelease = function (attack, release) {
var minTime = this.blockTime;
attack = Tone.Time(attack).toFrequency();
release = Tone.Time(release).toFrequency();
attack = Math.max(attack, minTime);
release = Math.max(release, minTime);
this._frequencyValues.setMap(function (val) {
if (val <= 0) {
return attack;
} else {
return release;
}
});
};
/**
* The attack time.
* @memberOf Tone.Follower#
* @type {Time}
* @name attack
*/
Object.defineProperty(Tone.Follower.prototype, 'attack', {
get: function () {
return this._attack;
},
set: function (attack) {
this._attack = attack;
this._setAttackRelease(this._attack, this._release);
}
});
/**
* The release time.
* @memberOf Tone.Follower#
* @type {Time}
* @name release
*/
Object.defineProperty(Tone.Follower.prototype, 'release', {
get: function () {
return this._release;
},
set: function (release) {
this._release = release;
this._setAttackRelease(this._attack, this._release);
}
});
/**
* Borrows the connect method from Signal so that the output can be used
* as a Tone.Signal control signal.
* @function
*/
Tone.Follower.prototype.connect = Tone.SignalBase.prototype.connect;
/**
* dispose
* @returns {Tone.Follower} this
*/
Tone.Follower.prototype.dispose = function () {
Tone.AudioNode.prototype.dispose.call(this);
this._filter.disconnect();
this._filter = null;
this._frequencyValues.disconnect();
this._frequencyValues = null;
this._delay.dispose();
this._delay = null;
this._sub.disconnect();
this._sub = null;
this._abs.dispose();
this._abs = null;
this._mult.dispose();
this._mult = null;
this._curve = null;
return this;
};
return Tone.Follower;
});
Module(function (Tone) {
/**
* @class Tone.ScaledEnvelop is an envelope which can be scaled
* to any range. It's useful for applying an envelope
* to a frequency or any other non-NormalRange signal
* parameter.
*
* @extends {Tone.Envelope}
* @constructor
* @param {Time|Object} [attack] the attack time in seconds
* @param {Time} [decay] the decay time in seconds
* @param {number} [sustain] a percentage (0-1) of the full amplitude
* @param {Time} [release] the release time in seconds
* @example
* var scaledEnv = new Tone.ScaledEnvelope({
* "attack" : 0.2,
* "min" : 200,
* "max" : 2000
* });
* scaledEnv.connect(oscillator.frequency);
*/
Tone.ScaledEnvelope = function () {
//get all of the defaults
var options = Tone.defaults(arguments, [
'attack',
'decay',
'sustain',
'release'
], Tone.Envelope);
Tone.Envelope.call(this, options);
options = Tone.defaultArg(options, Tone.ScaledEnvelope.defaults);
/**
* scale the incoming signal by an exponent
* @type {Tone.Pow}
* @private
*/
this._exp = this.output = new Tone.Pow(options.exponent);
/**
* scale the signal to the desired range
* @type {Tone.Multiply}
* @private
*/
this._scale = this.output = new Tone.Scale(options.min, options.max);
this._sig.chain(this._exp, this._scale);
};
Tone.extend(Tone.ScaledEnvelope, Tone.Envelope);
/**
* the default parameters
* @static
*/
Tone.ScaledEnvelope.defaults = {
'min': 0,
'max': 1,
'exponent': 1
};
/**
* The envelope's min output value. This is the value which it
* starts at.
* @memberOf Tone.ScaledEnvelope#
* @type {number}
* @name min
*/
Object.defineProperty(Tone.ScaledEnvelope.prototype, 'min', {
get: function () {
return this._scale.min;
},
set: function (min) {
this._scale.min = min;
}
});
/**
* The envelope's max output value. In other words, the value
* at the peak of the attack portion of the envelope.
* @memberOf Tone.ScaledEnvelope#
* @type {number}
* @name max
*/
Object.defineProperty(Tone.ScaledEnvelope.prototype, 'max', {
get: function () {
return this._scale.max;
},
set: function (max) {
this._scale.max = max;
}
});
/**
* The envelope's exponent value.
* @memberOf Tone.ScaledEnvelope#
* @type {number}
* @name exponent
*/
Object.defineProperty(Tone.ScaledEnvelope.prototype, 'exponent', {
get: function () {
return this._exp.value;
},
set: function (exp) {
this._exp.value = exp;
}
});
/**
* clean up
* @returns {Tone.ScaledEnvelope} this
*/
Tone.ScaledEnvelope.prototype.dispose = function () {
Tone.Envelope.prototype.dispose.call(this);
this._scale.dispose();
this._scale = null;
this._exp.dispose();
this._exp = null;
return this;
};
return Tone.ScaledEnvelope;
});
Module(function (Tone) {
/**
* @class Tone.FrequencyEnvelope is a Tone.ScaledEnvelope, but instead of `min` and `max`
* it's got a `baseFrequency` and `octaves` parameter.
*
* @extends {Tone.Envelope}
* @constructor
* @param {Time|Object} [attack] the attack time in seconds
* @param {Time} [decay] the decay time in seconds
* @param {number} [sustain] a percentage (0-1) of the full amplitude
* @param {Time} [release] the release time in seconds
* @example
* var env = new Tone.FrequencyEnvelope({
* "attack" : 0.2,
* "baseFrequency" : "C2",
* "octaves" : 4
* });
* scaledEnv.connect(oscillator.frequency);
*/
Tone.FrequencyEnvelope = function () {
var options = Tone.defaults(arguments, [
'attack',
'decay',
'sustain',
'release'
], Tone.Envelope);
Tone.ScaledEnvelope.call(this, options);
//merge it with the frequency envelope defaults
options = Tone.defaultArg(options, Tone.FrequencyEnvelope.defaults);
/**
* Stores the octave value
* @type {Positive}
* @private
*/
this._octaves = options.octaves;
//setup
this.baseFrequency = options.baseFrequency;
this.octaves = options.octaves;
};
Tone.extend(Tone.FrequencyEnvelope, Tone.Envelope);
/**
* the default parameters
* @static
*/
Tone.FrequencyEnvelope.defaults = {
'baseFrequency': 200,
'octaves': 4,
'exponent': 2
};
/**
* The envelope's mininum output value. This is the value which it
* starts at.
* @memberOf Tone.FrequencyEnvelope#
* @type {Frequency}
* @name baseFrequency
*/
Object.defineProperty(Tone.FrequencyEnvelope.prototype, 'baseFrequency', {
get: function () {
return this._scale.min;
},
set: function (min) {
this._scale.min = this.toFrequency(min);
//also update the octaves
this.octaves = this._octaves;
}
});
/**
* The number of octaves above the baseFrequency that the
* envelope will scale to.
* @memberOf Tone.FrequencyEnvelope#
* @type {Positive}
* @name octaves
*/
Object.defineProperty(Tone.FrequencyEnvelope.prototype, 'octaves', {
get: function () {
return this._octaves;
},
set: function (octaves) {
this._octaves = octaves;
this._scale.max = this.baseFrequency * Math.pow(2, octaves);
}
});
/**
* The envelope's exponent value.
* @memberOf Tone.FrequencyEnvelope#
* @type {number}
* @name exponent
*/
Object.defineProperty(Tone.FrequencyEnvelope.prototype, 'exponent', {
get: function () {
return this._exp.value;
},
set: function (exp) {
this._exp.value = exp;
}
});
/**
* clean up
* @returns {Tone.FrequencyEnvelope} this
*/
Tone.FrequencyEnvelope.prototype.dispose = function () {
Tone.ScaledEnvelope.prototype.dispose.call(this);
return this;
};
return Tone.FrequencyEnvelope;
});
Module(function (Tone) {
/**
* @class GreaterThanZero outputs 1 when the input is strictly greater than zero
*
* @constructor
* @extends {Tone.SignalBase}
* @example
* var gt0 = new Tone.GreaterThanZero();
* var sig = new Tone.Signal(0.01).connect(gt0);
* //the output of gt0 is 1.
* sig.value = 0;
* //the output of gt0 is 0.
*/
Tone.GreaterThanZero = function () {
Tone.SignalBase.call(this);
/**
* @type {Tone.WaveShaper}
* @private
*/
this._thresh = this.output = new Tone.WaveShaper(function (val) {
if (val <= 0) {
return 0;
} else {
return 1;
}
}, 127);
/**
* scale the first thresholded signal by a large value.
* this will help with values which are very close to 0
* @type {Tone.Multiply}
* @private
*/
this._scale = this.input = new Tone.Multiply(10000);
//connections
this._scale.connect(this._thresh);
};
Tone.extend(Tone.GreaterThanZero, Tone.SignalBase);
/**
* dispose method
* @returns {Tone.GreaterThanZero} this
*/
Tone.GreaterThanZero.prototype.dispose = function () {
Tone.SignalBase.prototype.dispose.call(this);
this._scale.dispose();
this._scale = null;
this._thresh.dispose();
this._thresh = null;
return this;
};
return Tone.GreaterThanZero;
});
Module(function (Tone) {
/**
* @class Output 1 if the signal is greater than the value, otherwise outputs 0.
* can compare two signals or a signal and a number.
*
* @constructor
* @extends {Tone.Signal}
* @param {number} [value=0] the value to compare to the incoming signal
* @example
* var gt = new Tone.GreaterThan(2);
* var sig = new Tone.Signal(4).connect(gt);
* //output of gt is equal 1.
*/
Tone.GreaterThan = function (value) {
Tone.Signal.call(this);
this.createInsOuts(2, 0);
/**
* subtract the amount from the incoming signal
* @type {Tone.Subtract}
* @private
*/
this._param = this.input[0] = new Tone.Subtract(value);
this.input[1] = this._param.input[1];
/**
* compare that amount to zero
* @type {Tone.GreaterThanZero}
* @private
*/
this._gtz = this.output = new Tone.GreaterThanZero();
//connect
this._param.connect(this._gtz);
};
Tone.extend(Tone.GreaterThan, Tone.Signal);
/**
* dispose method
* @returns {Tone.GreaterThan} this
*/
Tone.GreaterThan.prototype.dispose = function () {
Tone.Signal.prototype.dispose.call(this);
this._gtz.dispose();
this._gtz = null;
return this;
};
return Tone.GreaterThan;
});
Module(function (Tone) {
/**
* @class Tone.Gate only passes a signal through when the incoming
* signal exceeds a specified threshold. To do this, Gate uses
* a Tone.Follower to follow the amplitude of the incoming signal.
* A common implementation of this class is a [Noise Gate](https://en.wikipedia.org/wiki/Noise_gate).
*
* @constructor
* @extends {Tone.AudioNode}
* @param {Decibels|Object} [threshold] The threshold above which the gate will open.
* @param {Time=} attack The follower's attack time
* @param {Time=} release The follower's release time
* @example
* var gate = new Tone.Gate(-30, 0.2, 0.3).toMaster();
* var mic = new Tone.UserMedia().connect(gate);
* //the gate will only pass through the incoming
* //signal when it's louder than -30db
*/
Tone.Gate = function () {
var options = Tone.defaults(arguments, [
'threshold',
'attack',
'release'
], Tone.Gate);
Tone.AudioNode.call(this);
this.createInsOuts(1, 1);
/**
* @type {Tone.Follower}
* @private
*/
this._follower = new Tone.Follower(options.attack, options.release);
/**
* @type {Tone.GreaterThan}
* @private
*/
this._gt = new Tone.GreaterThan(Tone.dbToGain(options.threshold));
//the connections
this.input.connect(this.output);
//the control signal
this.input.chain(this._gt, this._follower, this.output.gain);
};
Tone.extend(Tone.Gate, Tone.AudioNode);
/**
* @const
* @static
* @type {Object}
*/
Tone.Gate.defaults = {
'attack': 0.1,
'release': 0.1,
'threshold': -40
};
/**
* The threshold of the gate in decibels
* @memberOf Tone.Gate#
* @type {Decibels}
* @name threshold
*/
Object.defineProperty(Tone.Gate.prototype, 'threshold', {
get: function () {
return Tone.gainToDb(this._gt.value);
},
set: function (thresh) {
this._gt.value = Tone.dbToGain(thresh);
}
});
/**
* The attack speed of the gate
* @memberOf Tone.Gate#
* @type {Time}
* @name attack
*/
Object.defineProperty(Tone.Gate.prototype, 'attack', {
get: function () {
return this._follower.attack;
},
set: function (attackTime) {
this._follower.attack = attackTime;
}
});
/**
* The release speed of the gate
* @memberOf Tone.Gate#
* @type {Time}
* @name release
*/
Object.defineProperty(Tone.Gate.prototype, 'release', {
get: function () {
return this._follower.release;
},
set: function (releaseTime) {
this._follower.release = releaseTime;
}
});
/**
* Clean up.
* @returns {Tone.Gate} this
*/
Tone.Gate.prototype.dispose = function () {
Tone.AudioNode.prototype.dispose.call(this);
this._follower.dispose();
this._gt.dispose();
this._follower = null;
this._gt = null;
return this;
};
return Tone.Gate;
});
Module(function (Tone) {
/**
* @class Tone.TickSignal extends Tone.Signal, but adds the capability
* to calculate the number of elapsed ticks. exponential and target curves
* are approximated with multiple linear ramps.
*
* Thank you Bruno Dias, H. Sofia Pinto, and David M. Matos, for your [WAC paper](https://smartech.gatech.edu/bitstream/handle/1853/54588/WAC2016-49.pdf)
* describing integrating timing functions for tempo calculations.
*
* @param {Number} value The initial value of the signal
* @extends {Tone.Signal}
*/
Tone.TickSignal = function (value) {
value = Tone.defaultArg(value, 1);
Tone.Signal.call(this, {
'units': Tone.Type.Ticks,
'value': value
});
//extend the memory
this._events.memory = Infinity;
//clear the clock from the beginning
this.cancelScheduledValues(0);
//set an initial event
this._events.add({
'type': Tone.Param.AutomationType.SetValue,
'time': 0,
'value': value
});
};
Tone.extend(Tone.TickSignal, Tone.Signal);
/**
* Wraps Tone.Signal methods so that they also
* record the ticks.
* @param {Function} method
* @return {Function}
* @private
*/
function _wrapScheduleMethods(method) {
return function (value, time) {
time = this.toSeconds(time);
method.apply(this, arguments);
var event = this._events.get(time);
var previousEvent = this._events.previousEvent(event);
var ticksUntilTime = this._getTicksUntilEvent(previousEvent, time);
event.ticks = Math.max(ticksUntilTime, 0);
return this;
};
}
Tone.TickSignal.prototype.setValueAtTime = _wrapScheduleMethods(Tone.Signal.prototype.setValueAtTime);
Tone.TickSignal.prototype.linearRampToValueAtTime = _wrapScheduleMethods(Tone.Signal.prototype.linearRampToValueAtTime);
/**
* Start exponentially approaching the target value at the given time with
* a rate having the given time constant.
* @param {number} value
* @param {Time} startTime
* @param {number} timeConstant
* @returns {Tone.TickSignal} this
*/
Tone.TickSignal.prototype.setTargetAtTime = function (value, time, constant) {
//aproximate it with multiple linear ramps
time = this.toSeconds(time);
this.setRampPoint(time);
value = this._fromUnits(value);
//start from previously scheduled value
var prevEvent = this._events.get(time);
var segments = Math.round(Math.max(1 / constant, 1));
for (var i = 0; i <= segments; i++) {
var segTime = constant * i + time;
var rampVal = this._exponentialApproach(prevEvent.time, prevEvent.value, value, constant, segTime);
this.linearRampToValueAtTime(this._toUnits(rampVal), segTime);
}
return this;
};
/**
* Schedules an exponential continuous change in parameter value from
* the previous scheduled parameter value to the given value.
* @param {number} value
* @param {Time} endTime
* @returns {Tone.TickSignal} this
*/
Tone.TickSignal.prototype.exponentialRampToValueAtTime = function (value, time) {
//aproximate it with multiple linear ramps
time = this.toSeconds(time);
value = this._fromUnits(value);
//start from previously scheduled value
var prevEvent = this._events.get(time);
if (prevEvent === null) {
prevEvent = {
'value': this._initialValue,
'time': 0
};
}
//approx 10 segments per second
var segments = Math.round(Math.max((time - prevEvent.time) * 10, 1));
var segmentDur = (time - prevEvent.time) / segments;
for (var i = 0; i <= segments; i++) {
var segTime = segmentDur * i + prevEvent.time;
var rampVal = this._exponentialInterpolate(prevEvent.time, prevEvent.value, time, value, segTime);
this.linearRampToValueAtTime(this._toUnits(rampVal), segTime);
}
return this;
};
/**
* Returns the tick value at the time. Takes into account
* any automation curves scheduled on the signal.
* @private
* @param {Time} time The time to get the tick count at
* @return {Ticks} The number of ticks which have elapsed at the time
* given any automations.
*/
Tone.TickSignal.prototype._getTicksUntilEvent = function (event, time) {
if (event === null) {
event = {
'ticks': 0,
'time': 0
};
} else if (Tone.isUndef(event.ticks)) {
var previousEvent = this._events.previousEvent(event);
event.ticks = this._getTicksUntilEvent(previousEvent, event.time);
}
var val0 = this.getValueAtTime(event.time);
var val1 = this.getValueAtTime(time);
//if it's right on the line, take the previous value
if (this._events.get(time).time === time && this._events.get(time).type === Tone.Param.AutomationType.SetValue) {
val1 = this.getValueAtTime(time - this.sampleTime);
}
return 0.5 * (time - event.time) * (val0 + val1) + event.ticks;
};
/**
* Returns the tick value at the time. Takes into account
* any automation curves scheduled on the signal.
* @param {Time} time The time to get the tick count at
* @return {Ticks} The number of ticks which have elapsed at the time
* given any automations.
*/
Tone.TickSignal.prototype.getTicksAtTime = function (time) {
time = this.toSeconds(time);
var event = this._events.get(time);
return Math.max(this._getTicksUntilEvent(event, time), 0);
};
/**
* Return the elapsed time of the number of ticks from the given time
* @param {Ticks} ticks The number of ticks to calculate
* @param {Time} time The time to get the next tick from
* @return {Seconds} The duration of the number of ticks from the given time in seconds
*/
Tone.TickSignal.prototype.getDurationOfTicks = function (ticks, time) {
time = this.toSeconds(time);
var currentTick = this.getTicksAtTime(time);
return this.getTimeOfTick(currentTick + ticks) - time;
};
/**
* Given a tick, returns the time that tick occurs at.
* @param {Ticks} tick
* @return {Time} The time that the tick occurs.
*/
Tone.TickSignal.prototype.getTimeOfTick = function (tick) {
var before = this._events.get(tick, 'ticks');
var after = this._events.getAfter(tick, 'ticks');
if (before && before.ticks === tick) {
return before.time;
} else if (before && after && after.type === Tone.Param.AutomationType.Linear && before.value !== after.value) {
var val0 = this.getValueAtTime(before.time);
var val1 = this.getValueAtTime(after.time);
var delta = (val1 - val0) / (after.time - before.time);
var k = Math.sqrt(Math.pow(val0, 2) - 2 * delta * (before.ticks - tick));
var sol1 = (-val0 + k) / delta;
var sol2 = (-val0 - k) / delta;
return (sol1 > 0 ? sol1 : sol2) + before.time;
} else if (before) {
if (before.value === 0) {
return Infinity;
} else {
return before.time + (tick - before.ticks) / before.value;
}
} else {
return tick / this._initialValue;
}
};
/**
* Convert some number of ticks their the duration in seconds accounting
* for any automation curves starting at the given time.
* @param {Ticks} ticks The number of ticks to convert to seconds.
* @param {Time} [when=now] When along the automation timeline to convert the ticks.
* @return {Tone.Time} The duration in seconds of the ticks.
*/
Tone.TickSignal.prototype.ticksToTime = function (ticks, when) {
when = this.toSeconds(when);
return new Tone.Time(this.getDurationOfTicks(ticks, when));
};
/**
* The inverse of [ticksToTime](#tickstotime). Convert a duration in
* seconds to the corresponding number of ticks accounting for any
* automation curves starting at the given time.
* @param {Time} duration The time interval to convert to ticks.
* @param {Time} [when=now] When along the automation timeline to convert the ticks.
* @return {Tone.Ticks} The duration in ticks.
*/
Tone.TickSignal.prototype.timeToTicks = function (duration, when) {
when = this.toSeconds(when);
duration = this.toSeconds(duration);
var startTicks = this.getTicksAtTime(when);
var endTicks = this.getTicksAtTime(when + duration);
return new Tone.Ticks(endTicks - startTicks);
};
return Tone.TickSignal;
});
Module(function (Tone) {
/**
* @class A Timeline State. Provides the methods: setStateAtTime("state", time)
* and getValueAtTime(time).
*
* @extends {Tone.Timeline}
* @param {String} initial The initial state of the TimelineState.
* Defaults to undefined
*/
Tone.TimelineState = function (initial) {
Tone.Timeline.call(this);
/**
* The initial state
* @private
* @type {String}
*/
this._initial = initial;
};
Tone.extend(Tone.TimelineState, Tone.Timeline);
/**
* Returns the scheduled state scheduled before or at
* the given time.
* @param {Number} time The time to query.
* @return {String} The name of the state input in setStateAtTime.
*/
Tone.TimelineState.prototype.getValueAtTime = function (time) {
var event = this.get(time);
if (event !== null) {
return event.state;
} else {
return this._initial;
}
};
/**
* Add a state to the timeline.
* @param {String} state The name of the state to set.
* @param {Number} time The time to query.
* @returns {Tone.TimelineState} this
*/
Tone.TimelineState.prototype.setStateAtTime = function (state, time) {
//all state changes need to be >= the previous state time
//TODO throw error if time < the previous event time
this.add({
'state': state,
'time': time
});
return this;
};
/**
* Return the event before the time with the given state
* @param {Tone.State} state The state to look for
* @param {Time} time When to check before
* @return {Object} The event with the given state before the time
*/
Tone.TimelineState.prototype.getLastState = function (state, time) {
time = this.toSeconds(time);
var index = this._search(time);
for (var i = index; i >= 0; i--) {
var event = this._timeline[i];
if (event.state === state) {
return event;
}
}
};
/**
* Return the event after the time with the given state
* @param {Tone.State} state The state to look for
* @param {Time} time When to check from
* @return {Object} The event with the given state after the time
*/
Tone.TimelineState.prototype.getNextState = function (state, time) {
time = this.toSeconds(time);
var index = this._search(time);
if (index !== -1) {
for (var i = index; i < this._timeline.length; i++) {
var event = this._timeline[i];
if (event.state === state) {
return event;
}
}
}
};
return Tone.TimelineState;
});
Module(function (Tone) {
/**
* @class Uses [Tone.TickSignal](TickSignal) to track elapsed ticks with
* complex automation curves.
*
* @constructor
* @param {Frequency} frequency The initial frequency that the signal ticks at
* @extends {Tone}
*/
Tone.TickSource = function () {
var options = Tone.defaults(arguments, ['frequency'], Tone.TickSource);
/**
* The frequency the callback function should be invoked.
* @type {Frequency}
* @signal
*/
this.frequency = new Tone.TickSignal(options.frequency, Tone.Type.Frequency);
this._readOnly('frequency');
/**
* The state timeline
* @type {Tone.TimelineState}
* @private
*/
this._state = new Tone.TimelineState(Tone.State.Stopped);
this._state.setStateAtTime(Tone.State.Stopped, 0);
/**
* The offset values of the ticks
* @type {Tone.Timeline}
* @private
*/
this._tickOffset = new Tone.Timeline();
//add the first event
this.setTicksAtTime(0, 0);
};
Tone.extend(Tone.TickSource);
/**
* The defaults
* @const
* @type {Object}
*/
Tone.TickSource.defaults = { 'frequency': 1 };
/**
* Returns the playback state of the source, either "started", "stopped" or "paused".
* @type {Tone.State}
* @readOnly
* @memberOf Tone.TickSource#
* @name state
*/
Object.defineProperty(Tone.TickSource.prototype, 'state', {
get: function () {
return this._state.getValueAtTime(this.now());
}
});
/**
* Start the clock at the given time. Optionally pass in an offset
* of where to start the tick counter from.
* @param {Time=} time The time the clock should start
* @param {Ticks=0} offset The number of ticks to start the source at
* @return {Tone.TickSource} this
*/
Tone.TickSource.prototype.start = function (time, offset) {
time = this.toSeconds(time);
if (this._state.getValueAtTime(time) !== Tone.State.Started) {
this._state.setStateAtTime(Tone.State.Started, time);
if (Tone.isDefined(offset)) {
this.setTicksAtTime(offset, time);
}
}
return this;
};
/**
* Stop the clock. Stopping the clock resets the tick counter to 0.
* @param {Time} [time=now] The time when the clock should stop.
* @returns {Tone.TickSource} this
* @example
* clock.stop();
*/
Tone.TickSource.prototype.stop = function (time) {
time = this.toSeconds(time);
//cancel the previous stop
if (this._state.getValueAtTime(time) === Tone.State.Stopped) {
var event = this._state.get(time);
if (event.time > 0) {
this._tickOffset.cancel(event.time);
this._state.cancel(event.time);
}
}
this._state.cancel(time);
this._state.setStateAtTime(Tone.State.Stopped, time);
this.setTicksAtTime(0, time);
return this;
};
/**
* Pause the clock. Pausing does not reset the tick counter.
* @param {Time} [time=now] The time when the clock should stop.
* @returns {Tone.TickSource} this
*/
Tone.TickSource.prototype.pause = function (time) {
time = this.toSeconds(time);
if (this._state.getValueAtTime(time) === Tone.State.Started) {
this._state.setStateAtTime(Tone.State.Paused, time);
}
return this;
};
/**
* Cancel start/stop/pause and setTickAtTime events scheduled after the given time.
* @param {Time} [time=now] When to clear the events after
* @returns {Tone.TickSource} this
*/
Tone.TickSource.prototype.cancel = function (time) {
time = this.toSeconds(time);
this._state.cancel(time);
this._tickOffset.cancel(time);
return this;
};
/**
* Get the elapsed ticks at the given time
* @param {Time} time When to get the tick value
* @return {Ticks} The number of ticks
*/
Tone.TickSource.prototype.getTicksAtTime = function (time) {
time = this.toSeconds(time);
var stopEvent = this._state.getLastState(Tone.State.Stopped, time);
//this event allows forEachBetween to iterate until the current time
var tmpEvent = {
state: Tone.State.Paused,
time: time
};
this._state.add(tmpEvent);
//keep track of the previous offset event
var lastState = stopEvent;
var elapsedTicks = 0;
//iterate through all the events since the last stop
this._state.forEachBetween(stopEvent.time, time + this.sampleTime, function (e) {
var periodStartTime = lastState.time;
//if there is an offset event in this period use that
var offsetEvent = this._tickOffset.get(e.time);
if (offsetEvent.time >= lastState.time) {
elapsedTicks = offsetEvent.ticks;
periodStartTime = offsetEvent.time;
}
if (lastState.state === Tone.State.Started && e.state !== Tone.State.Started) {
elapsedTicks += this.frequency.getTicksAtTime(e.time) - this.frequency.getTicksAtTime(periodStartTime);
}
lastState = e;
}.bind(this));
//remove the temporary event
this._state.remove(tmpEvent);
//return the ticks
return elapsedTicks;
};
/**
* The number of times the callback was invoked. Starts counting at 0
* and increments after the callback was invoked. Returns -1 when stopped.
* @memberOf Tone.TickSource#
* @name ticks
* @type {Ticks}
*/
Object.defineProperty(Tone.TickSource.prototype, 'ticks', {
get: function () {
return this.getTicksAtTime(this.now());
},
set: function (t) {
this.setTicksAtTime(t, this.now());
}
});
/**
* The time since ticks=0 that the TickSource has been running. Accounts
* for tempo curves
* @memberOf Tone.TickSource#
* @name seconds
* @type {Seconds}
*/
Object.defineProperty(Tone.TickSource.prototype, 'seconds', {
get: function () {
return this.getSecondsAtTime(this.now());
},
set: function (s) {
var now = this.now();
var ticks = this.frequency.timeToTicks(s, now);
this.setTicksAtTime(ticks, now);
}
});
/**
* Return the elapsed seconds at the given time.
* @param {Time} time When to get the elapsed seconds
* @return {Seconds} The number of elapsed seconds
*/
Tone.TickSource.prototype.getSecondsAtTime = function (time) {
time = this.toSeconds(time);
var stopEvent = this._state.getLastState(Tone.State.Stopped, time);
//this event allows forEachBetween to iterate until the current time
var tmpEvent = {
state: Tone.State.Paused,
time: time
};
this._state.add(tmpEvent);
//keep track of the previous offset event
var lastState = stopEvent;
var elapsedSeconds = 0;
//iterate through all the events since the last stop
this._state.forEachBetween(stopEvent.time, time + this.sampleTime, function (e) {
var periodStartTime = lastState.time;
//if there is an offset event in this period use that
var offsetEvent = this._tickOffset.get(e.time);
if (offsetEvent.time >= lastState.time) {
elapsedSeconds = offsetEvent.seconds;
periodStartTime = offsetEvent.time;
}
if (lastState.state === Tone.State.Started && e.state !== Tone.State.Started) {
elapsedSeconds += e.time - periodStartTime;
}
lastState = e;
}.bind(this));
//remove the temporary event
this._state.remove(tmpEvent);
//return the ticks
return elapsedSeconds;
};
/**
* Set the clock's ticks at the given time.
* @param {Ticks} ticks The tick value to set
* @param {Time} time When to set the tick value
* @return {Tone.TickSource} this
*/
Tone.TickSource.prototype.setTicksAtTime = function (ticks, time) {
time = this.toSeconds(time);
this._tickOffset.cancel(time);
this._tickOffset.add({
'time': time,
'ticks': ticks,
'seconds': this.frequency.getDurationOfTicks(ticks, time)
});
return this;
};
/**
* Returns the scheduled state at the given time.
* @param {Time} time The time to query.
* @return {String} The name of the state input in setStateAtTime.
* @example
* source.start("+0.1");
* source.getStateAtTime("+0.1"); //returns "started"
*/
Tone.TickSource.prototype.getStateAtTime = function (time) {
time = this.toSeconds(time);
return this._state.getValueAtTime(time);
};
/**
* Get the time of the given tick. The second argument
* is when to test before. Since ticks can be set (with setTicksAtTime)
* there may be multiple times for a given tick value.
* @param {Ticks} ticks The tick number.
* @param {Time=} before When to measure the tick value from.
* @return {Time} The time of the tick
*/
Tone.TickSource.prototype.getTimeOfTick = function (tick, before) {
before = Tone.defaultArg(before, this.now());
var offset = this._tickOffset.get(before);
var event = this._state.get(before);
var startTime = Math.max(offset.time, event.time);
var absoluteTicks = this.frequency.getTicksAtTime(startTime) + tick - offset.ticks;
return this.frequency.getTimeOfTick(absoluteTicks);
};
/**
* Invoke the callback event at all scheduled ticks between the
* start time and the end time
* @param {Time} startTime The beginning of the search range
* @param {Time} endTime The end of the search range
* @param {Function