mirror of
https://github.com/Tonejs/Tone.js
synced 2025-01-10 02:48:47 +00:00
14265 lines
No EOL
436 KiB
JavaScript
14265 lines
No EOL
436 KiB
JavaScript
(function (root) {
|
|
"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-2015 Yotam Mann
|
|
*/
|
|
Main(function () {
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// WEB AUDIO CONTEXT
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//borrowed from underscore.js
|
|
function isUndef(val) {
|
|
return val === void 0;
|
|
}
|
|
//borrowed from underscore.js
|
|
function isFunction(val) {
|
|
return typeof val === 'function';
|
|
}
|
|
var audioContext;
|
|
//polyfill for AudioContext and OfflineAudioContext
|
|
if (isUndef(window.AudioContext)) {
|
|
window.AudioContext = window.webkitAudioContext;
|
|
}
|
|
if (isUndef(window.OfflineAudioContext)) {
|
|
window.OfflineAudioContext = window.webkitOfflineAudioContext;
|
|
}
|
|
if (!isUndef(AudioContext)) {
|
|
audioContext = new AudioContext();
|
|
} else {
|
|
throw new Error('Web Audio is not supported in this browser');
|
|
}
|
|
//SHIMS////////////////////////////////////////////////////////////////////
|
|
if (!isFunction(AudioContext.prototype.createGain)) {
|
|
AudioContext.prototype.createGain = AudioContext.prototype.createGainNode;
|
|
}
|
|
if (!isFunction(AudioContext.prototype.createDelay)) {
|
|
AudioContext.prototype.createDelay = AudioContext.prototype.createDelayNode;
|
|
}
|
|
if (!isFunction(AudioContext.prototype.createPeriodicWave)) {
|
|
AudioContext.prototype.createPeriodicWave = AudioContext.prototype.createWaveTable;
|
|
}
|
|
if (!isFunction(AudioBufferSourceNode.prototype.start)) {
|
|
AudioBufferSourceNode.prototype.start = AudioBufferSourceNode.prototype.noteGrainOn;
|
|
}
|
|
if (!isFunction(AudioBufferSourceNode.prototype.stop)) {
|
|
AudioBufferSourceNode.prototype.stop = AudioBufferSourceNode.prototype.noteOff;
|
|
}
|
|
if (!isFunction(OscillatorNode.prototype.start)) {
|
|
OscillatorNode.prototype.start = OscillatorNode.prototype.noteOn;
|
|
}
|
|
if (!isFunction(OscillatorNode.prototype.stop)) {
|
|
OscillatorNode.prototype.stop = OscillatorNode.prototype.noteOff;
|
|
}
|
|
if (!isFunction(OscillatorNode.prototype.setPeriodicWave)) {
|
|
OscillatorNode.prototype.setPeriodicWave = OscillatorNode.prototype.setWaveTable;
|
|
}
|
|
//extend the connect function to include Tones
|
|
AudioNode.prototype._nativeConnect = AudioNode.prototype.connect;
|
|
AudioNode.prototype.connect = function (B, outNum, inNum) {
|
|
if (B.input) {
|
|
if (Array.isArray(B.input)) {
|
|
if (isUndef(inNum)) {
|
|
inNum = 0;
|
|
}
|
|
this.connect(B.input[inNum]);
|
|
} else {
|
|
this.connect(B.input, outNum, inNum);
|
|
}
|
|
} else {
|
|
try {
|
|
if (B instanceof AudioNode) {
|
|
this._nativeConnect(B, outNum, inNum);
|
|
} else {
|
|
this._nativeConnect(B, outNum);
|
|
}
|
|
} catch (e) {
|
|
throw new Error('error connecting to node: ' + B);
|
|
}
|
|
}
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// TONE
|
|
///////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* @class Tone is the base class of all other classes.
|
|
*
|
|
* @constructor
|
|
* @alias Tone
|
|
* @param {number} [inputs=1] the number of input nodes
|
|
* @param {number} [outputs=1] the number of output nodes
|
|
*/
|
|
var Tone = function (inputs, outputs) {
|
|
/**
|
|
* the input node(s)
|
|
* @type {GainNode|Array}
|
|
*/
|
|
if (isUndef(inputs) || inputs === 1) {
|
|
this.input = this.context.createGain();
|
|
} else if (inputs > 1) {
|
|
this.input = new Array(inputs);
|
|
}
|
|
/**
|
|
* the output node(s)
|
|
* @type {GainNode|Array}
|
|
*/
|
|
if (isUndef(outputs) || outputs === 1) {
|
|
this.output = this.context.createGain();
|
|
} else if (outputs > 1) {
|
|
this.output = new Array(inputs);
|
|
}
|
|
};
|
|
/**
|
|
* 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
|
|
* @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 (typeof params === 'object') {
|
|
rampTime = value;
|
|
} else if (typeof params === 'string') {
|
|
var tmpObj = {};
|
|
tmpObj[params] = value;
|
|
params = tmpObj;
|
|
}
|
|
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]];
|
|
}
|
|
attr = attrSplit[attrSplit.length - 1];
|
|
}
|
|
var param = parent[attr];
|
|
if (isUndef(param)) {
|
|
continue;
|
|
}
|
|
if (param instanceof Tone.Signal) {
|
|
if (param.value !== value) {
|
|
if (isUndef(rampTime)) {
|
|
param.value = value;
|
|
} else {
|
|
param.rampTo(value, rampTime);
|
|
}
|
|
}
|
|
} else if (param instanceof AudioParam) {
|
|
if (param.value !== value) {
|
|
param.value = 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.
|
|
* @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 (isUndef(params)) {
|
|
params = this._collectDefaults(this.constructor);
|
|
} else if (typeof params === 'string') {
|
|
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 (typeof params[attr] === 'object') {
|
|
subRet[attr] = param.get();
|
|
} else if (param instanceof Tone.Signal) {
|
|
subRet[attr] = param.value;
|
|
} else if (param instanceof AudioParam) {
|
|
subRet[attr] = param.value;
|
|
} else if (param instanceof Tone) {
|
|
subRet[attr] = param.get();
|
|
} else if (!isFunction(param) && !isUndef(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 (!isUndef(constr.defaults)) {
|
|
ret = Object.keys(constr.defaults);
|
|
}
|
|
if (!isUndef(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;
|
|
};
|
|
/**
|
|
* Set the preset if it exists.
|
|
* @param {string} presetName the name of the preset
|
|
* @returns {Tone} this
|
|
*/
|
|
Tone.prototype.setPreset = function (presetName) {
|
|
if (!this.isUndef(this.preset) && this.preset.hasOwnProperty(presetName)) {
|
|
this.set(this.preset[presetName]);
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* @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 (isFunction(Tone[className]) && isLetter && sameConstructor) {
|
|
return className;
|
|
}
|
|
}
|
|
return 'Tone';
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// CLASS VARS
|
|
///////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* A static pointer to the audio context accessible as Tone.context.
|
|
* @type {AudioContext}
|
|
*/
|
|
Tone.context = audioContext;
|
|
/**
|
|
* The audio context.
|
|
* @type {AudioContext}
|
|
*/
|
|
Tone.prototype.context = Tone.context;
|
|
/**
|
|
* the default buffer size
|
|
* @type {number}
|
|
* @static
|
|
* @const
|
|
*/
|
|
Tone.prototype.bufferSize = 2048;
|
|
/**
|
|
* the delay time of a single buffer frame
|
|
* @type {number}
|
|
* @static
|
|
* @const
|
|
*/
|
|
Tone.prototype.bufferTime = Tone.prototype.bufferSize / Tone.context.sampleRate;
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// CONNECTIONS
|
|
///////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* disconnect and dispose
|
|
* @returns {Tone} this
|
|
*/
|
|
Tone.prototype.dispose = function () {
|
|
if (!this.isUndef(this.input)) {
|
|
if (this.input instanceof AudioNode) {
|
|
this.input.disconnect();
|
|
}
|
|
this.input = null;
|
|
}
|
|
if (!this.isUndef(this.output)) {
|
|
if (this.output instanceof AudioNode) {
|
|
this.output.disconnect();
|
|
}
|
|
this.output = null;
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* a silent connection to the DesinationNode
|
|
* which will ensure that anything connected to it
|
|
* will not be garbage collected
|
|
*
|
|
* @private
|
|
*/
|
|
var _silentNode = null;
|
|
/**
|
|
* makes a connection to ensure that the node will not be garbage collected
|
|
* until 'dispose' is explicitly called
|
|
*
|
|
* use carefully. circumvents JS and WebAudio's normal Garbage Collection behavior
|
|
* @returns {Tone} this
|
|
*/
|
|
Tone.prototype.noGC = function () {
|
|
this.output.connect(_silentNode);
|
|
return this;
|
|
};
|
|
AudioNode.prototype.noGC = function () {
|
|
this.connect(_silentNode);
|
|
return this;
|
|
};
|
|
/**
|
|
* 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} this
|
|
*/
|
|
Tone.prototype.connect = function (unit, outputNum, inputNum) {
|
|
if (Array.isArray(this.output)) {
|
|
outputNum = this.defaultArg(outputNum, 0);
|
|
this.output[outputNum].connect(unit, 0, inputNum);
|
|
} else {
|
|
this.output.connect(unit, outputNum, inputNum);
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* disconnect the output
|
|
* @returns {Tone} this
|
|
*/
|
|
Tone.prototype.disconnect = function (outputNum) {
|
|
if (Array.isArray(this.output)) {
|
|
outputNum = this.defaultArg(outputNum, 0);
|
|
this.output[outputNum].disconnect();
|
|
} else {
|
|
this.output.disconnect();
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* connect together all of the arguments in series
|
|
* @param {...AudioParam|Tone|AudioNode}
|
|
* @returns {Tone} this
|
|
*/
|
|
Tone.prototype.connectSeries = function () {
|
|
if (arguments.length > 1) {
|
|
var currentUnit = arguments[0];
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var toUnit = arguments[i];
|
|
currentUnit.connect(toUnit);
|
|
currentUnit = toUnit;
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* fan out the connection from the first argument to the rest of the arguments
|
|
* @param {...AudioParam|Tone|AudioNode}
|
|
* @returns {Tone} this
|
|
*/
|
|
Tone.prototype.connectParallel = function () {
|
|
var connectFrom = arguments[0];
|
|
if (arguments.length > 1) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var connectTo = arguments[i];
|
|
connectFrom.connect(connectTo);
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* 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} this
|
|
*/
|
|
Tone.prototype.chain = function () {
|
|
if (arguments.length > 0) {
|
|
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}
|
|
* @returns {Tone} this
|
|
*/
|
|
Tone.prototype.fan = function () {
|
|
if (arguments.length > 0) {
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
this.connect(arguments[i]);
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
//give native nodes chain and fan methods
|
|
AudioNode.prototype.chain = Tone.prototype.chain;
|
|
AudioNode.prototype.fan = Tone.prototype.fan;
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// UTILITIES / HELPERS / MATHS
|
|
///////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* if a the given is undefined, use the fallback.
|
|
* if both given and fallback are objects, given
|
|
* will be augmented with whatever properties it's
|
|
* missing which are in fallback
|
|
*
|
|
* warning: if object is self referential, it will go into an an
|
|
* infinite recursive loop.
|
|
*
|
|
* @param {*} given
|
|
* @param {*} fallback
|
|
* @return {*}
|
|
*/
|
|
Tone.prototype.defaultArg = function (given, fallback) {
|
|
if (typeof given === 'object' && typeof fallback === 'object') {
|
|
var ret = {};
|
|
//make a deep copy of the given object
|
|
for (var givenProp in given) {
|
|
ret[givenProp] = this.defaultArg(given[givenProp], given[givenProp]);
|
|
}
|
|
for (var prop in fallback) {
|
|
ret[prop] = this.defaultArg(given[prop], fallback[prop]);
|
|
}
|
|
return ret;
|
|
} else {
|
|
return isUndef(given) ? fallback : given;
|
|
}
|
|
};
|
|
/**
|
|
* returns the args as an options object with given arguments
|
|
* mapped to the names provided.
|
|
*
|
|
* if the args given is an array containing an object, it is assumed
|
|
* that that's already the options object and will just return it.
|
|
*
|
|
* @param {Array} values the 'arguments' object of the function
|
|
* @param {Array} keys the names of the arguments as they
|
|
* should appear in the options object
|
|
* @param {Object=} defaults optional defaults to mixin to the returned
|
|
* options object
|
|
* @return {Object} the options object with the names mapped to the arguments
|
|
*/
|
|
Tone.prototype.optionsObject = function (values, keys, defaults) {
|
|
var options = {};
|
|
if (values.length === 1 && typeof values[0] === 'object') {
|
|
options = values[0];
|
|
} else {
|
|
for (var i = 0; i < keys.length; i++) {
|
|
options[keys[i]] = values[i];
|
|
}
|
|
}
|
|
if (!this.isUndef(defaults)) {
|
|
return this.defaultArg(options, defaults);
|
|
} else {
|
|
return options;
|
|
}
|
|
};
|
|
/**
|
|
* test if the arg is undefined
|
|
* @param {*} arg the argument to test
|
|
* @returns {boolean} true if the arg is undefined
|
|
* @function
|
|
*/
|
|
Tone.prototype.isUndef = isUndef;
|
|
/**
|
|
* test if the arg is a function
|
|
* @param {*} arg the argument to test
|
|
* @returns {boolean} true if the arg is a function
|
|
* @function
|
|
*/
|
|
Tone.prototype.isFunction = isFunction;
|
|
/**
|
|
* 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 });
|
|
}
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GAIN CONVERSIONS
|
|
///////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* equal power gain scale
|
|
* good for cross-fading
|
|
* @param {number} percent (0-1)
|
|
* @return {number} output gain (0-1)
|
|
*/
|
|
Tone.prototype.equalPowerScale = function (percent) {
|
|
var piFactor = 0.5 * Math.PI;
|
|
return Math.sin(percent * piFactor);
|
|
};
|
|
/**
|
|
* convert db scale to gain scale (0-1)
|
|
* @param {number} db
|
|
* @return {number}
|
|
*/
|
|
Tone.prototype.dbToGain = function (db) {
|
|
return Math.pow(2, db / 6);
|
|
};
|
|
/**
|
|
* convert gain scale to decibels
|
|
* @param {number} gain (0-1)
|
|
* @return {number}
|
|
*/
|
|
Tone.prototype.gainToDb = function (gain) {
|
|
return 20 * (Math.log(gain) / Math.LN10);
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// TIMING
|
|
///////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* @return {number} the currentTime from the AudioContext
|
|
*/
|
|
Tone.prototype.now = function () {
|
|
return this.context.currentTime;
|
|
};
|
|
/**
|
|
* convert a sample count to seconds
|
|
* @param {number} samples
|
|
* @return {number}
|
|
*/
|
|
Tone.prototype.samplesToSeconds = function (samples) {
|
|
return samples / this.context.sampleRate;
|
|
};
|
|
/**
|
|
* convert a time into samples
|
|
*
|
|
* @param {Tone.time} time
|
|
* @return {number}
|
|
*/
|
|
Tone.prototype.toSamples = function (time) {
|
|
var seconds = this.toSeconds(time);
|
|
return Math.round(seconds * this.context.sampleRate);
|
|
};
|
|
/**
|
|
* convert time to seconds
|
|
*
|
|
* this is a simplified version which only handles numbers and
|
|
* 'now' relative numbers. If the Transport is included this
|
|
* method is overridden to include many other features including
|
|
* notationTime, Frequency, and transportTime
|
|
*
|
|
* @param {number=} time
|
|
* @param {number=} now if passed in, this number will be
|
|
* used for all 'now' relative timings
|
|
* @return {number} seconds in the same timescale as the AudioContext
|
|
*/
|
|
Tone.prototype.toSeconds = function (time, now) {
|
|
now = this.defaultArg(now, this.now());
|
|
if (typeof time === 'number') {
|
|
return time; //assuming that it's seconds
|
|
} else if (typeof time === 'string') {
|
|
var plusTime = 0;
|
|
if (time.charAt(0) === '+') {
|
|
time = time.slice(1);
|
|
plusTime = now;
|
|
}
|
|
return parseFloat(time) + plusTime;
|
|
} else {
|
|
return now;
|
|
}
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// FREQUENCY CONVERSION
|
|
///////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* true if the input is in the format number+hz
|
|
* i.e.: 10hz
|
|
*
|
|
* @param {number} freq
|
|
* @return {boolean}
|
|
* @function
|
|
*/
|
|
Tone.prototype.isFrequency = function () {
|
|
var freqFormat = new RegExp(/\d*\.?\d+hz$/i);
|
|
return function (freq) {
|
|
return freqFormat.test(freq);
|
|
};
|
|
}();
|
|
/**
|
|
* Convert a frequency into seconds.
|
|
* Accepts numbers and strings: i.e. "10hz" or
|
|
* 10 both return 0.1.
|
|
*
|
|
* @param {number|string} freq
|
|
* @return {number}
|
|
*/
|
|
Tone.prototype.frequencyToSeconds = function (freq) {
|
|
return 1 / parseFloat(freq);
|
|
};
|
|
/**
|
|
* Convert a number in seconds to a frequency.
|
|
* @param {number} seconds
|
|
* @return {number}
|
|
*/
|
|
Tone.prototype.secondsToFrequency = function (seconds) {
|
|
return 1 / seconds;
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// 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
|
|
*
|
|
* @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 (isUndef(parent)) {
|
|
parent = Tone;
|
|
}
|
|
function TempConstructor() {
|
|
}
|
|
TempConstructor.prototype = parent.prototype;
|
|
child.prototype = new TempConstructor();
|
|
/** @override */
|
|
child.prototype.constructor = child;
|
|
child._super = parent;
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// TYPES / STATES
|
|
///////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* Possible types which a value can take on
|
|
* @enum {string}
|
|
*/
|
|
Tone.Type = {
|
|
/**
|
|
* The default value is a number which can take on any value between [-Infinity, Infinity]
|
|
*/
|
|
Default: 'number',
|
|
/**
|
|
* Time can be described in a number of ways. Read more <a href="https://github.com/TONEnoTONE/Tone.js/wiki/Time">here</>.
|
|
*
|
|
* <ul>
|
|
* <li>Numbers, which will be taken literally as the time (in seconds).</li>
|
|
* <li>Notation, ("4n", "8t") describes time in BPM and time signature relative values.</li>
|
|
* <li>Transport Time, ("4:3:2") will also provide tempo and time signature relative times
|
|
* in the form BARS:QUARTERS:SIXTEENTHS.</li>
|
|
* <li>Frequency, ("8hz") is converted to the length of the cycle in seconds.</li>
|
|
* <li>Now-Relative, ("+1") prefix any of the above with "+" and it will be interpreted as
|
|
* "the current time plus whatever expression follows".</li>
|
|
* <li>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.</li>
|
|
* <li>No Argument, for methods which accept time, no argument will be interpreted as
|
|
* "now" (i.e. the currentTime).</li>
|
|
* </ul>
|
|
*
|
|
* @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',
|
|
/**
|
|
* Gain is the ratio between the input and the output value of a signal.
|
|
* @typedef {Gain}
|
|
*/
|
|
Gain: 'gain',
|
|
/**
|
|
* 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 0.
|
|
* @typedef {Positive}
|
|
*/
|
|
Positive: 'positive',
|
|
/**
|
|
* A cent is a hundreth of a semitone.
|
|
* @typedef {Cents}
|
|
*/
|
|
Cents: 'cents',
|
|
/**
|
|
* Angle between 0 and 360.
|
|
* @typedef {Degrees}
|
|
*/
|
|
Degrees: 'degrees'
|
|
};
|
|
/**
|
|
* Possible play states.
|
|
* @enum {string}
|
|
*/
|
|
Tone.State = {
|
|
Started: 'started',
|
|
Stopped: 'stopped',
|
|
Paused: 'paused'
|
|
};
|
|
/**
|
|
* An empty function.
|
|
* @static
|
|
*/
|
|
Tone.noOp = function () {
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// CONTEXT
|
|
///////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* array of callbacks to be invoked when a new context is added
|
|
* @private
|
|
* @private
|
|
*/
|
|
var newContextCallbacks = [];
|
|
/**
|
|
* invoke this callback when a new context is added
|
|
* will be invoked initially with the first context
|
|
* @private
|
|
* @static
|
|
* @param {function(AudioContext)} callback the callback to be invoked
|
|
* with the audio context
|
|
*/
|
|
Tone._initAudioContext = function (callback) {
|
|
//invoke the callback with the existing AudioContext
|
|
callback(Tone.context);
|
|
//add it to the array
|
|
newContextCallbacks.push(callback);
|
|
};
|
|
/**
|
|
* 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) {
|
|
//set the prototypes
|
|
Tone.prototype.context = ctx;
|
|
Tone.context = ctx;
|
|
//invoke all the callbacks
|
|
for (var i = 0; i < newContextCallbacks.length; i++) {
|
|
newContextCallbacks[i](ctx);
|
|
}
|
|
};
|
|
/**
|
|
* Bind this to a touchstart event to start the audio on mobile devices.
|
|
* <br>
|
|
* http://stackoverflow.com/questions/12517000/no-sound-on-ios-6-web-audio-api/12569290#12569290
|
|
* @static
|
|
*/
|
|
Tone.startMobile = function () {
|
|
var osc = Tone.context.createOscillator();
|
|
var silent = Tone.context.createGain();
|
|
silent.gain.value = 0;
|
|
osc.connect(silent);
|
|
silent.connect(Tone.context.destination);
|
|
var now = Tone.context.currentTime;
|
|
osc.start(now);
|
|
osc.stop(now + 1);
|
|
};
|
|
//setup the context
|
|
Tone._initAudioContext(function (audioContext) {
|
|
//set the bufferTime
|
|
Tone.prototype.bufferTime = Tone.prototype.bufferSize / audioContext.sampleRate;
|
|
_silentNode = audioContext.createGain();
|
|
_silentNode.gain.value = 0;
|
|
_silentNode.connect(audioContext.destination);
|
|
});
|
|
Tone.version = 'r5-dev';
|
|
console.log('%c * Tone.js ' + Tone.version + ' * ', 'background: #000; color: #fff');
|
|
return Tone;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Buffer loading and storage. Tone.Buffer is used internally by all
|
|
* classes that make requests for audio files such as Tone.Player,
|
|
* Tone.Sampler and Tone.Convolver.
|
|
* <br><br>
|
|
* Aside from load callbacks from individual buffers, Tone.Buffer
|
|
* provides static methods which keep track of the loading progress
|
|
* of all of the buffers. These methods are <code>onload</code>, <code>onprogress</code>,
|
|
* and <code>onerror</code>.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone}
|
|
* @param {AudioBuffer|string} url The url to load, or the audio buffer to set.
|
|
* @param {function=} onload A callback which is invoked after the buffer is loaded.
|
|
* It's recommended to use Tone.Buffer.onload instead
|
|
* since it will give you a callback when ALL buffers are loaded.
|
|
* @example
|
|
* var buffer = new Tone.Buffer("path/to/sound.mp3", function(){
|
|
* //the buffer is now available.
|
|
* var buff = buffer.get();
|
|
* });
|
|
*/
|
|
Tone.Buffer = function () {
|
|
var options = this.optionsObject(arguments, [
|
|
'url',
|
|
'onload'
|
|
], Tone.Buffer.defaults);
|
|
/**
|
|
* stores the loaded AudioBuffer
|
|
* @type {AudioBuffer}
|
|
* @private
|
|
*/
|
|
this._buffer = null;
|
|
/**
|
|
* indicates if the buffer should be reversed or not
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
this._reversed = options.reverse;
|
|
/**
|
|
* The url of the buffer. <code>undefined</code> if it was
|
|
* constructed with a buffer
|
|
* @type {string}
|
|
* @readOnly
|
|
*/
|
|
this.url = undefined;
|
|
/**
|
|
* Indicates if the buffer is loaded or not.
|
|
* @type {boolean}
|
|
* @readOnly
|
|
*/
|
|
this.loaded = false;
|
|
/**
|
|
* The callback to invoke when everything is loaded.
|
|
* @type {function}
|
|
*/
|
|
this.onload = options.onload.bind(this, this);
|
|
if (options.url instanceof AudioBuffer) {
|
|
this._buffer.set(options.url);
|
|
this.onload(this);
|
|
} else if (typeof options.url === 'string') {
|
|
this.url = options.url;
|
|
Tone.Buffer._addToQueue(options.url, this);
|
|
}
|
|
};
|
|
Tone.extend(Tone.Buffer);
|
|
/**
|
|
* the default parameters
|
|
* @type {Object}
|
|
*/
|
|
Tone.Buffer.defaults = {
|
|
'url': undefined,
|
|
'onload': Tone.noOp,
|
|
'reverse': false
|
|
};
|
|
/**
|
|
* Pass in an AudioBuffer or Tone.Buffer to set the value
|
|
* of this buffer.
|
|
* @param {AudioBuffer|Tone.Buffer} buffer the buffer
|
|
* @returns {Tone.Buffer} this
|
|
*/
|
|
Tone.Buffer.prototype.set = function (buffer) {
|
|
if (buffer instanceof Tone.Buffer) {
|
|
this._buffer = buffer.get();
|
|
} else {
|
|
this._buffer = buffer;
|
|
}
|
|
this.loaded = true;
|
|
return this;
|
|
};
|
|
/**
|
|
* @return {AudioBuffer} The audio buffer stored in the object.
|
|
*/
|
|
Tone.Buffer.prototype.get = function () {
|
|
return this._buffer;
|
|
};
|
|
/**
|
|
* Load url into the buffer.
|
|
* @param {String} url The url to load
|
|
* @param {Function=} callback The callback to invoke on load.
|
|
* don't need to set if `onload` is
|
|
* already set.
|
|
* @returns {Tone.Buffer} this
|
|
*/
|
|
Tone.Buffer.prototype.load = function (url, callback) {
|
|
this.url = url;
|
|
this.onload = this.defaultArg(callback, this.onload);
|
|
Tone.Buffer._addToQueue(url, this);
|
|
return this;
|
|
};
|
|
/**
|
|
* dispose and disconnect
|
|
* @returns {Tone.Buffer} this
|
|
*/
|
|
Tone.Buffer.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
Tone.Buffer._removeFromQueue(this);
|
|
this._buffer = null;
|
|
this.onload = Tone.Buffer.defaults.onload;
|
|
return this;
|
|
};
|
|
/**
|
|
* The duration of the buffer.
|
|
* @memberOf Tone.Buffer#
|
|
* @type {number}
|
|
* @name duration
|
|
* @readOnly
|
|
*/
|
|
Object.defineProperty(Tone.Buffer.prototype, 'duration', {
|
|
get: function () {
|
|
if (this._buffer) {
|
|
return this._buffer.duration;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
});
|
|
/**
|
|
* Reverse the buffer.
|
|
* @private
|
|
* @return {Tone.Buffer} this
|
|
*/
|
|
Tone.Buffer.prototype._reverse = function () {
|
|
if (this.loaded) {
|
|
for (var i = 0; i < this._buffer.numberOfChannels; i++) {
|
|
Array.prototype.reverse.call(this._buffer.getChannelData(i));
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* Reverse the buffer.
|
|
* @memberOf Tone.Buffer#
|
|
* @type {boolean}
|
|
* @name reverse
|
|
*/
|
|
Object.defineProperty(Tone.Buffer.prototype, 'reverse', {
|
|
get: function () {
|
|
return this._reversed;
|
|
},
|
|
set: function (rev) {
|
|
if (this._reversed !== rev) {
|
|
this._reversed = rev;
|
|
this._reverse();
|
|
}
|
|
}
|
|
});
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// STATIC METHODS
|
|
///////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* the static queue for all of the xhr requests
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
Tone.Buffer._queue = [];
|
|
/**
|
|
* the array of current downloads
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
Tone.Buffer._currentDownloads = [];
|
|
/**
|
|
* the total number of downloads
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
Tone.Buffer._totalDownloads = 0;
|
|
/**
|
|
* the maximum number of simultaneous downloads
|
|
* @static
|
|
* @type {number}
|
|
*/
|
|
Tone.Buffer.MAX_SIMULTANEOUS_DOWNLOADS = 6;
|
|
/**
|
|
* Adds a file to be loaded to the loading queue
|
|
* @param {string} url the url to load
|
|
* @param {function} callback the callback to invoke once it's loaded
|
|
* @private
|
|
*/
|
|
Tone.Buffer._addToQueue = function (url, buffer) {
|
|
Tone.Buffer._queue.push({
|
|
url: url,
|
|
Buffer: buffer,
|
|
progress: 0,
|
|
xhr: null
|
|
});
|
|
this._totalDownloads++;
|
|
Tone.Buffer._next();
|
|
};
|
|
/**
|
|
* Remove an object from the queue's (if it's still there)
|
|
* Abort the XHR if it's in progress
|
|
* @param {Tone.Buffer} buffer the buffer to remove
|
|
* @private
|
|
*/
|
|
Tone.Buffer._removeFromQueue = function (buffer) {
|
|
var i;
|
|
for (i = 0; i < Tone.Buffer._queue.length; i++) {
|
|
var q = Tone.Buffer._queue[i];
|
|
if (q.Buffer === buffer) {
|
|
Tone.Buffer._queue.splice(i, 1);
|
|
}
|
|
}
|
|
for (i = 0; i < Tone.Buffer._currentDownloads.length; i++) {
|
|
var dl = Tone.Buffer._currentDownloads[i];
|
|
if (dl.Buffer === buffer) {
|
|
Tone.Buffer._currentDownloads.splice(i, 1);
|
|
dl.xhr.abort();
|
|
dl.xhr.onprogress = null;
|
|
dl.xhr.onload = null;
|
|
dl.xhr.onerror = null;
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* load the next buffer in the queue
|
|
* @private
|
|
*/
|
|
Tone.Buffer._next = function () {
|
|
if (Tone.Buffer._queue.length > 0) {
|
|
if (Tone.Buffer._currentDownloads.length < Tone.Buffer.MAX_SIMULTANEOUS_DOWNLOADS) {
|
|
var next = Tone.Buffer._queue.shift();
|
|
Tone.Buffer._currentDownloads.push(next);
|
|
next.xhr = Tone.Buffer.load(next.url, function (buffer) {
|
|
//remove this one from the queue
|
|
var index = Tone.Buffer._currentDownloads.indexOf(next);
|
|
Tone.Buffer._currentDownloads.splice(index, 1);
|
|
next.Buffer.set(buffer);
|
|
if (next.Buffer._reversed) {
|
|
next.Buffer._reverse();
|
|
}
|
|
next.Buffer.onload(next.Buffer);
|
|
Tone.Buffer._onprogress();
|
|
Tone.Buffer._next();
|
|
});
|
|
next.xhr.onprogress = function (event) {
|
|
next.progress = event.loaded / event.total;
|
|
Tone.Buffer._onprogress();
|
|
};
|
|
next.xhr.onerror = Tone.Buffer.onerror;
|
|
}
|
|
} else if (Tone.Buffer._currentDownloads.length === 0) {
|
|
Tone.Buffer.onload();
|
|
//reset the downloads
|
|
Tone.Buffer._totalDownloads = 0;
|
|
}
|
|
};
|
|
/**
|
|
* internal progress event handler
|
|
* @private
|
|
*/
|
|
Tone.Buffer._onprogress = function () {
|
|
var curretDownloadsProgress = 0;
|
|
var currentDLLen = Tone.Buffer._currentDownloads.length;
|
|
var inprogress = 0;
|
|
if (currentDLLen > 0) {
|
|
for (var i = 0; i < currentDLLen; i++) {
|
|
var dl = Tone.Buffer._currentDownloads[i];
|
|
curretDownloadsProgress += dl.progress;
|
|
}
|
|
inprogress = curretDownloadsProgress;
|
|
}
|
|
var currentDownloadProgress = currentDLLen - inprogress;
|
|
var completed = Tone.Buffer._totalDownloads - Tone.Buffer._queue.length - currentDownloadProgress;
|
|
Tone.Buffer.onprogress(completed / Tone.Buffer._totalDownloads);
|
|
};
|
|
/**
|
|
* Makes an xhr reqest for the selected url then decodes
|
|
* the file as an audio buffer. Invokes
|
|
* the callback once the audio buffer loads.
|
|
* @param {string} url The url of the buffer to load.
|
|
* filetype support depends on the
|
|
* browser.
|
|
* @param {function} callback The function to invoke when the url is loaded.
|
|
* @returns {XMLHttpRequest} returns the XHR
|
|
*/
|
|
Tone.Buffer.load = function (url, callback) {
|
|
var request = new XMLHttpRequest();
|
|
request.open('GET', url, true);
|
|
request.responseType = 'arraybuffer';
|
|
// decode asynchronously
|
|
request.onload = function () {
|
|
Tone.context.decodeAudioData(request.response, function (buff) {
|
|
if (!buff) {
|
|
throw new Error('could not decode audio data:' + url);
|
|
}
|
|
callback(buff);
|
|
});
|
|
};
|
|
//send the request
|
|
request.send();
|
|
return request;
|
|
};
|
|
/**
|
|
* Callback when all of the buffers in the queue have loaded
|
|
* @static
|
|
* @type {function}
|
|
* @example
|
|
* //invoked when all of the queued samples are done loading
|
|
* Tone.Buffer.onload = function(){
|
|
* console.log("everything is loaded");
|
|
* };
|
|
*/
|
|
Tone.Buffer.onload = Tone.noOp;
|
|
/**
|
|
* Callback function is invoked with the progress of all of the loads in the queue.
|
|
* The value passed to the callback is between 0-1.
|
|
* @static
|
|
* @type {function}
|
|
* @example
|
|
* Tone.Buffer.onprogress = function(percent){
|
|
* console.log("progress:" + (percent * 100).toFixed(1) + "%");
|
|
* };
|
|
*/
|
|
Tone.Buffer.onprogress = Tone.noOp;
|
|
/**
|
|
* Callback if one of the buffers in the queue encounters an error. The error
|
|
* is passed in as the argument.
|
|
* @static
|
|
* @type {function}
|
|
* @example
|
|
* Tone.Buffer.onerror = function(e){
|
|
* console.log("there was an error while loading the buffers: "+e);
|
|
* }
|
|
*/
|
|
Tone.Buffer.onerror = Tone.noOp;
|
|
return Tone.Buffer;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* buses are another way of routing audio
|
|
*
|
|
* augments Tone.prototype to include send and recieve
|
|
*/
|
|
/**
|
|
* All of the routes
|
|
*
|
|
* @type {Object}
|
|
* @static
|
|
* @private
|
|
*/
|
|
var Buses = {};
|
|
/**
|
|
* Send this signal to the channel name.
|
|
* @param {string} channelName A named channel to send the signal to.
|
|
* @param {Decibels} amount The amount of the source to send to the bus.
|
|
* @return {GainNode} The gain node which connects this node to the desired channel.
|
|
* Can be used to adjust the levels of the send.
|
|
* @example
|
|
* source.send("reverb", -12);
|
|
*/
|
|
Tone.prototype.send = function (channelName, amount) {
|
|
if (!Buses.hasOwnProperty(channelName)) {
|
|
Buses[channelName] = this.context.createGain();
|
|
}
|
|
var sendKnob = this.context.createGain();
|
|
sendKnob.gain.value = this.dbToGain(this.defaultArg(amount, 1));
|
|
this.output.chain(sendKnob, Buses[channelName]);
|
|
return sendKnob;
|
|
};
|
|
/**
|
|
* Recieve the input from the desired channelName to the input
|
|
*
|
|
* @param {string} channelName A named channel to send the signal to.
|
|
* @param {AudioNode} [input] If no input is selected, the
|
|
* input of the current node is
|
|
* chosen.
|
|
* @returns {Tone} this
|
|
* @example
|
|
* reverbEffect.receive("reverb");
|
|
*/
|
|
Tone.prototype.receive = function (channelName, input) {
|
|
if (!Buses.hasOwnProperty(channelName)) {
|
|
Buses[channelName] = this.context.createGain();
|
|
}
|
|
if (this.isUndef(input)) {
|
|
input = this.input;
|
|
}
|
|
Buses[channelName].connect(input);
|
|
return this;
|
|
};
|
|
return Tone;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Base class for all Signals
|
|
*
|
|
* @constructor
|
|
* @extends {Tone}
|
|
*/
|
|
Tone.SignalBase = function () {
|
|
};
|
|
Tone.extend(Tone.SignalBase);
|
|
/**
|
|
* 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 normal `connect`.
|
|
*
|
|
* @override
|
|
* @param {AudioParam|AudioNode|Tone.Signal|Tone} node
|
|
* @param {number} [outputNumber=0]
|
|
* @param {number} [inputNumber=0]
|
|
* @returns {Tone.SignalBase} this
|
|
*/
|
|
Tone.SignalBase.prototype.connect = function (node, outputNumber, inputNumber) {
|
|
//zero it out so that the signal can have full control
|
|
if (node.constructor === Tone.Signal) {
|
|
//cancel changes
|
|
node._value.cancelScheduledValues(0);
|
|
//reset the value
|
|
node._value.value = 0;
|
|
//mark the value as overridden
|
|
node.overridden = true;
|
|
} else if (node instanceof AudioParam) {
|
|
node.cancelScheduledValues(0);
|
|
node.value = 0;
|
|
}
|
|
Tone.prototype.connect.call(this, node, outputNumber, inputNumber);
|
|
return this;
|
|
};
|
|
return Tone.SignalBase;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Wraps the WaveShaperNode
|
|
*
|
|
* @extends {Tone.SignalBase}
|
|
* @constructor
|
|
* @param {function(number, number)|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 shapping function
|
|
* @param {number} [bufferLen=1024] the length of the WaveShaperNode buffer.
|
|
* @example
|
|
* var timesTwo = new Tone.WaveShaper(function(val){
|
|
* return val * 2;
|
|
* }, 2048);
|
|
*/
|
|
Tone.WaveShaper = function (mapping, bufferLen) {
|
|
/**
|
|
* 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) || this.isUndef(mapping)) {
|
|
this._curve = new Float32Array(this.defaultArg(mapping, 1024));
|
|
} else if (this.isFunction(mapping)) {
|
|
this._curve = new Float32Array(this.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(number, 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
|
|
* @returns {Tone.WaveShaper} this
|
|
*/
|
|
Tone.WaveShaper.prototype.setMap = function (mapping) {
|
|
for (var i = 0, len = this._curve.length; i < len; i++) {
|
|
var normalized = i / len * 2 - 1;
|
|
this._curve[i] = mapping(normalized, i);
|
|
}
|
|
this._shaper.curve = this._curve;
|
|
return this;
|
|
};
|
|
/**
|
|
* The array to set as the waveshaper curve
|
|
* @memberOf Tone.WaveShaper#
|
|
* @type {Array}
|
|
* @name curve
|
|
*/
|
|
Object.defineProperty(Tone.WaveShaper.prototype, 'curve', {
|
|
get: function () {
|
|
return this._shaper.curve;
|
|
},
|
|
set: function (mapping) {
|
|
//fixes safari WaveShaperNode bug
|
|
if (this._isSafari()) {
|
|
var first = mapping[0];
|
|
mapping.unshift(first);
|
|
}
|
|
this._curve = new Float32Array(mapping);
|
|
this._shaper.curve = this._curve;
|
|
}
|
|
});
|
|
/**
|
|
* The oversampling. 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) {
|
|
this._shaper.oversample = oversampling;
|
|
}
|
|
});
|
|
/**
|
|
* returns true if the browser is safari
|
|
* @return {boolean}
|
|
* @private
|
|
*/
|
|
Tone.WaveShaper.prototype._isSafari = function () {
|
|
var ua = navigator.userAgent.toLowerCase();
|
|
return ua.indexOf('safari') !== -1 && ua.indexOf('chrome') === -1;
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.WaveShaper} this
|
|
*/
|
|
Tone.WaveShaper.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._shaper.disconnect();
|
|
this._shaper = null;
|
|
this._curve = null;
|
|
return this;
|
|
};
|
|
return Tone.WaveShaper;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Constant audio-rate signal.
|
|
* Tone.Signal is a core component which allows for sample-accurate
|
|
* synchronization of many components. Tone.Signal can be scheduled
|
|
* with all of the functions available to AudioParams
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.SignalBase}
|
|
* @param {number|AudioParam} [value=0] initial value or the AudioParam to control
|
|
* note that the signal has no output
|
|
* if an AudioParam is passed in.
|
|
* @param {Tone.Type} [units=Tone.Type.Default] unit the units the signal is in
|
|
* @example
|
|
* var signal = new Tone.Signal(10);
|
|
*/
|
|
Tone.Signal = function () {
|
|
var options = this.optionsObject(arguments, [
|
|
'value',
|
|
'units'
|
|
], Tone.Signal.defaults);
|
|
/**
|
|
* the units the signal is in
|
|
* @type {string}
|
|
*/
|
|
this.units = options.units;
|
|
/**
|
|
* When true, converts the set value
|
|
* based on the units given. When false,
|
|
* applies no conversion and the units
|
|
* are merely used as a label.
|
|
* @type {boolean}
|
|
*/
|
|
this.convert = options.convert;
|
|
/**
|
|
* True if the signal value is being overridden by
|
|
* a connected signal.
|
|
* @readOnly
|
|
* @type {boolean}
|
|
*/
|
|
this.overridden = false;
|
|
/**
|
|
* The node where the constant signal value is scaled.
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this.output = this._scaler = this.context.createGain();
|
|
/**
|
|
* The node where the value is set.
|
|
* @type {AudioParam}
|
|
* @private
|
|
*/
|
|
this.input = this._value = this._scaler.gain;
|
|
if (options.value instanceof AudioParam) {
|
|
this._scaler.connect(options.value);
|
|
//zero out the value
|
|
options.value.value = 0;
|
|
} else {
|
|
if (!this.isUndef(options.param)) {
|
|
this._scaler.connect(options.param);
|
|
options.param.value = 0;
|
|
}
|
|
this.value = options.value;
|
|
}
|
|
//connect the constant 1 output to the node output
|
|
Tone.Signal._constant.chain(this._scaler);
|
|
};
|
|
Tone.extend(Tone.Signal, Tone.SignalBase);
|
|
/**
|
|
* The default values
|
|
* @type {Object}
|
|
* @static
|
|
* @const
|
|
*/
|
|
Tone.Signal.defaults = {
|
|
'value': 0,
|
|
'param': undefined,
|
|
'units': Tone.Type.Default,
|
|
'convert': true
|
|
};
|
|
/**
|
|
* The value of the signal.
|
|
* @memberOf Tone.Signal#
|
|
* @type {*}
|
|
* @name value
|
|
*/
|
|
Object.defineProperty(Tone.Signal.prototype, 'value', {
|
|
get: function () {
|
|
return this._toUnits(this._value.value);
|
|
},
|
|
set: function (value) {
|
|
var convertedVal = this._fromUnits(value);
|
|
//is this what you want?
|
|
this.cancelScheduledValues(0);
|
|
this._value.value = convertedVal;
|
|
}
|
|
});
|
|
/**
|
|
* @private
|
|
* @param {*} val the value to convert
|
|
* @return {number} the number which the value should be set to
|
|
*/
|
|
Tone.Signal.prototype._fromUnits = function (val) {
|
|
if (this.convert || this.isUndef(this.convert)) {
|
|
switch (this.units) {
|
|
case Tone.Type.Time:
|
|
return this.toSeconds(val);
|
|
case Tone.Type.Frequency:
|
|
return this.toFrequency(val);
|
|
case Tone.Type.Decibels:
|
|
return this.dbToGain(val);
|
|
case Tone.Type.NormalRange:
|
|
return Math.min(Math.max(val, 0), 1);
|
|
case Tone.Type.AudioRange:
|
|
return Math.min(Math.max(val, -1), 1);
|
|
case Tone.Type.Positive:
|
|
return Math.max(val, 0);
|
|
default:
|
|
return val;
|
|
}
|
|
} else {
|
|
return val;
|
|
}
|
|
};
|
|
/**
|
|
* convert to the desired units
|
|
* @private
|
|
* @param {number} val the value to convert
|
|
* @return {number}
|
|
*/
|
|
Tone.Signal.prototype._toUnits = function (val) {
|
|
if (this.convert || this.isUndef(this.convert)) {
|
|
switch (this.units) {
|
|
case Tone.Type.Decibels:
|
|
return this.gainToDb(val);
|
|
default:
|
|
return val;
|
|
}
|
|
} else {
|
|
return val;
|
|
}
|
|
};
|
|
/**
|
|
* Schedules a parameter value change at the given time.
|
|
* @param {number} value
|
|
* @param {Time} time
|
|
* @returns {Tone.Signal} this
|
|
*/
|
|
Tone.Signal.prototype.setValueAtTime = function (value, time) {
|
|
value = this._fromUnits(value);
|
|
this._value.setValueAtTime(value, this.toSeconds(time));
|
|
return this;
|
|
};
|
|
/**
|
|
* Creates a schedule point with the current value at the current time.
|
|
*
|
|
* @param {number=} now (optionally) pass the now value in
|
|
* @returns {Tone.Signal} this
|
|
*/
|
|
Tone.Signal.prototype.setCurrentValueNow = function (now) {
|
|
now = this.defaultArg(now, this.now());
|
|
var currentVal = this._value.value;
|
|
this.cancelScheduledValues(now);
|
|
this._value.setValueAtTime(currentVal, now);
|
|
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.Signal} this
|
|
*/
|
|
Tone.Signal.prototype.linearRampToValueAtTime = function (value, endTime) {
|
|
value = this._fromUnits(value);
|
|
this._value.linearRampToValueAtTime(value, this.toSeconds(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.Signal} this
|
|
*/
|
|
Tone.Signal.prototype.exponentialRampToValueAtTime = function (value, endTime) {
|
|
value = this._fromUnits(value);
|
|
value = Math.max(0.00001, value);
|
|
this._value.exponentialRampToValueAtTime(value, this.toSeconds(endTime));
|
|
return this;
|
|
};
|
|
/**
|
|
* Schedules an exponential continuous change in parameter value from
|
|
* the current time and current value to the given value.
|
|
*
|
|
* @param {number} value
|
|
* @param {Time} rampTime the time that it takes the
|
|
* value to ramp from it's current value
|
|
* @returns {Tone.Signal} this
|
|
* @example
|
|
* //exponentially ramp to the value 2 over 4 seconds.
|
|
* signal.exponentialRampToValueNow(2, 4);
|
|
*/
|
|
Tone.Signal.prototype.exponentialRampToValueNow = function (value, rampTime) {
|
|
var now = this.now();
|
|
// exponentialRampToValueAt cannot ever ramp from 0, apparently.
|
|
// More info: https://bugzilla.mozilla.org/show_bug.cgi?id=1125600#c2
|
|
var currentVal = this.value;
|
|
this.setValueAtTime(Math.max(currentVal, 0.0001), now);
|
|
this.exponentialRampToValueAtTime(value, now + this.toSeconds(rampTime));
|
|
return this;
|
|
};
|
|
/**
|
|
* Schedules an linear continuous change in parameter value from
|
|
* the current time and current value to the given value at the given time.
|
|
*
|
|
* @param {number} value
|
|
* @param {Time} rampTime the time that it takes the
|
|
* value to ramp from it's current value
|
|
* @returns {Tone.Signal} this
|
|
* @example
|
|
* //linearly ramp to the value 4 over 3 seconds.
|
|
* signal.linearRampToValueNow(4, 3);
|
|
*/
|
|
Tone.Signal.prototype.linearRampToValueNow = function (value, rampTime) {
|
|
var now = this.now();
|
|
this.setCurrentValueNow(now);
|
|
this.linearRampToValueAtTime(value, now + this.toSeconds(rampTime));
|
|
return this;
|
|
};
|
|
/**
|
|
* 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.Signal} this
|
|
*/
|
|
Tone.Signal.prototype.setTargetAtTime = function (value, startTime, timeConstant) {
|
|
value = this._fromUnits(value);
|
|
// The value will never be able to approach without timeConstant > 0.
|
|
// http://www.w3.org/TR/webaudio/#dfn-setTargetAtTime, where the equation
|
|
// is described. 0 results in a division by 0.
|
|
timeConstant = Math.max(0.00001, timeConstant);
|
|
this._value.setTargetAtTime(value, this.toSeconds(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
|
|
* @returns {Tone.Signal} this
|
|
*/
|
|
Tone.Signal.prototype.setValueCurveAtTime = function (values, startTime, duration) {
|
|
for (var i = 0; i < values.length; i++) {
|
|
values[i] = this._fromUnits(values[i]);
|
|
}
|
|
this._value.setValueCurveAtTime(values, this.toSeconds(startTime), this.toSeconds(duration));
|
|
return this;
|
|
};
|
|
/**
|
|
* Cancels all scheduled parameter changes with times greater than or
|
|
* equal to startTime.
|
|
*
|
|
* @param {Time} startTime
|
|
* @returns {Tone.Signal} this
|
|
*/
|
|
Tone.Signal.prototype.cancelScheduledValues = function (startTime) {
|
|
this._value.cancelScheduledValues(this.toSeconds(startTime));
|
|
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
|
|
* @returns {Tone.Signal} this
|
|
* @example
|
|
* //ramp to the value either linearly or exponentially
|
|
* //depending on the "units" value of the signal
|
|
* signal.rampTo(0, 10);
|
|
*/
|
|
Tone.Signal.prototype.rampTo = function (value, rampTime) {
|
|
rampTime = this.defaultArg(rampTime, 0);
|
|
if (this.units === Tone.Type.Frequency || this.units === Tone.Type.BPM) {
|
|
this.exponentialRampToValueNow(value, rampTime);
|
|
} else {
|
|
this.linearRampToValueNow(value, rampTime);
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* dispose and disconnect
|
|
* @returns {Tone.Signal} this
|
|
*/
|
|
Tone.Signal.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._value = null;
|
|
this._scaler = null;
|
|
return this;
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// STATIC
|
|
///////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* the constant signal generator
|
|
* @static
|
|
* @private
|
|
* @const
|
|
* @type {OscillatorNode}
|
|
*/
|
|
Tone.Signal._generator = null;
|
|
/**
|
|
* the signal generator waveshaper. makes the incoming signal
|
|
* only output 1 for all inputs.
|
|
* @static
|
|
* @private
|
|
* @const
|
|
* @type {Tone.WaveShaper}
|
|
*/
|
|
Tone.Signal._constant = null;
|
|
/**
|
|
* initializer function
|
|
*/
|
|
Tone._initAudioContext(function (audioContext) {
|
|
Tone.Signal._generator = audioContext.createOscillator();
|
|
Tone.Signal._constant = new Tone.WaveShaper([
|
|
1,
|
|
1
|
|
]);
|
|
Tone.Signal._generator.connect(Tone.Signal._constant);
|
|
Tone.Signal._generator.start(0);
|
|
Tone.Signal._generator.noGC();
|
|
});
|
|
return Tone.Signal;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class a sample accurate clock built on an oscillator.
|
|
* Invokes the tick method at the set rate
|
|
*
|
|
* @constructor
|
|
* @extends {Tone}
|
|
* @param {Frequency} frequency the rate of the callback
|
|
* @param {function} callback the callback to be invoked with the time of the audio event
|
|
* @example
|
|
* //the callback will be invoked approximately once a second
|
|
* //and will print the time exactly once a second apart.
|
|
* var clock = new Tone.Clock(1, function(time){
|
|
* console.log(time);
|
|
* });
|
|
*/
|
|
Tone.Clock = function (frequency, callback) {
|
|
/**
|
|
* the oscillator
|
|
* @type {OscillatorNode}
|
|
* @private
|
|
*/
|
|
this._oscillator = null;
|
|
/**
|
|
* the script processor which listens to the oscillator
|
|
* @type {ScriptProcessorNode}
|
|
* @private
|
|
*/
|
|
this._jsNode = this.context.createScriptProcessor(this.bufferSize, 1, 1);
|
|
this._jsNode.onaudioprocess = this._processBuffer.bind(this);
|
|
/**
|
|
* the rate control signal
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.frequency = new Tone.Signal(frequency, Tone.Type.Frequency);
|
|
/**
|
|
* whether the tick is on the up or down
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
this._upTick = false;
|
|
/**
|
|
* the callback which is invoked on every tick
|
|
* with the time of that tick as the argument
|
|
* @type {function(number)}
|
|
*/
|
|
this.tick = callback;
|
|
/**
|
|
* Callback is invoked when the clock is stopped.
|
|
* @type {function}
|
|
* @example
|
|
* clock.onended = function(){
|
|
* console.log("the clock is stopped");
|
|
* }
|
|
*/
|
|
this.onended = Tone.noOp;
|
|
//setup
|
|
this._jsNode.noGC();
|
|
};
|
|
Tone.extend(Tone.Clock);
|
|
/**
|
|
* Start the clock.
|
|
* @param {Time} [time=now] the time when the clock should start
|
|
* @returns {Tone.Clock} this
|
|
* @example
|
|
* clock.start();
|
|
*/
|
|
Tone.Clock.prototype.start = function (time) {
|
|
if (!this._oscillator) {
|
|
this._oscillator = this.context.createOscillator();
|
|
this._oscillator.type = 'square';
|
|
this._oscillator.connect(this._jsNode);
|
|
//connect it up
|
|
this.frequency.connect(this._oscillator.frequency);
|
|
this._upTick = false;
|
|
var startTime = this.toSeconds(time);
|
|
this._oscillator.start(startTime);
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* Stop the clock.
|
|
* @param {Time} [time=now] The time when the clock should stop.
|
|
* @returns {Tone.Clock} this
|
|
* @example
|
|
* clock.stop();
|
|
*/
|
|
Tone.Clock.prototype.stop = function (time) {
|
|
if (this._oscillator) {
|
|
var now = this.now();
|
|
var stopTime = this.toSeconds(time, now);
|
|
this._oscillator.stop(stopTime);
|
|
this._oscillator = null;
|
|
if (time) {
|
|
//set a timeout for when it stops
|
|
setTimeout(this.onended, (stopTime - now) * 1000);
|
|
} else {
|
|
this.onended();
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* @private
|
|
* @param {AudioProcessingEvent} event
|
|
*/
|
|
Tone.Clock.prototype._processBuffer = function (event) {
|
|
var now = this.defaultArg(event.playbackTime, this.now());
|
|
var bufferSize = this._jsNode.bufferSize;
|
|
var incomingBuffer = event.inputBuffer.getChannelData(0);
|
|
var upTick = this._upTick;
|
|
var self = this;
|
|
for (var i = 0; i < bufferSize; i++) {
|
|
var sample = incomingBuffer[i];
|
|
if (sample > 0 && !upTick) {
|
|
upTick = true;
|
|
//get the callback out of audio thread
|
|
setTimeout(function () {
|
|
//to account for the double buffering
|
|
var tickTime = now + self.samplesToSeconds(i + bufferSize * 2);
|
|
return function () {
|
|
if (self.tick) {
|
|
self.tick(tickTime);
|
|
}
|
|
};
|
|
}(), 0); // jshint ignore:line
|
|
} else if (sample < 0 && upTick) {
|
|
upTick = false;
|
|
}
|
|
}
|
|
this._upTick = upTick;
|
|
};
|
|
/**
|
|
* Clean up.
|
|
* @returns {Tone.Clock} this
|
|
*/
|
|
Tone.Clock.prototype.dispose = function () {
|
|
this._jsNode.disconnect();
|
|
this.frequency.dispose();
|
|
this.frequency = null;
|
|
if (this._oscillator) {
|
|
this._oscillator.disconnect();
|
|
this._oscillator = null;
|
|
}
|
|
this._jsNode.onaudioprocess = Tone.noOp;
|
|
this._jsNode = null;
|
|
this.tick = null;
|
|
this.onended = Tone.noOp;
|
|
return this;
|
|
};
|
|
return Tone.Clock;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class A single master output which is connected to the
|
|
* AudioDestinationNode (aka your speakers).
|
|
* It provides useful conveniences such as the ability
|
|
* to set the volume and mute the entire application.
|
|
* It also gives you the ability to apply master effects like compression,
|
|
* limiting or effects to your application. <br><br>
|
|
* Like Tone.Transport, Tone.Master is created
|
|
* on initialization. You don't need to constuct it.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone}
|
|
*/
|
|
Tone.Master = function () {
|
|
Tone.call(this);
|
|
/**
|
|
* the unmuted volume
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._unmutedVolume = 1;
|
|
/**
|
|
* if the master is muted
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
this._muted = false;
|
|
/**
|
|
* the volume of the output in decibels
|
|
* @type {Decibels}
|
|
* @signal
|
|
*/
|
|
this.volume = new Tone.Signal(this.output.gain, Tone.Type.Decibels);
|
|
//connections
|
|
this.input.chain(this.output, this.context.destination);
|
|
};
|
|
Tone.extend(Tone.Master);
|
|
/**
|
|
* @type {Object}
|
|
* @const
|
|
*/
|
|
Tone.Master.defaults = {
|
|
'volume': 0,
|
|
'mute': false
|
|
};
|
|
/**
|
|
* Set `mute` to true to stop all output
|
|
* @memberOf Tone.Master#
|
|
* @type {boolean}
|
|
* @name mute
|
|
* @example
|
|
* //mute the output
|
|
* Tone.Master.mute = true;
|
|
*/
|
|
Object.defineProperty(Tone.Master.prototype, 'mute', {
|
|
get: function () {
|
|
return this._muted;
|
|
},
|
|
set: function (mute) {
|
|
this._muted = mute;
|
|
if (!this._muted && mute) {
|
|
this._unmutedVolume = this.volume.value;
|
|
//maybe it should ramp here?
|
|
this.volume.value = -Infinity;
|
|
} else if (this._muted && !mute) {
|
|
this.volume.value = this._unmutedVolume;
|
|
}
|
|
}
|
|
});
|
|
/**
|
|
* Add a master effects chain. This will disconnect any nodes which were previously
|
|
* chained.
|
|
* @param {AudioNode|Tone...} args All arguments will be connected in a row
|
|
* and the Master will be routed through it.
|
|
* @return {Tone.Master} this
|
|
* @example
|
|
* //some overall compression to keep the levels in check
|
|
* var masterCompressor = new Tone.Compressor({
|
|
* "threshold" : -6,
|
|
* "ratio" : 3,
|
|
* "attack" : 0.5,
|
|
* "release" : 0.1
|
|
* });
|
|
* //give a little boost to the lows
|
|
* var lowBump = new Tone.Filter(200, "lowshelf");
|
|
* //route everything through the filter
|
|
* //and compressor before going to the speakers
|
|
* Tone.Master.chain(lowBump, masterCompressor);
|
|
*/
|
|
Tone.Master.prototype.chain = function () {
|
|
this.input.disconnect();
|
|
this.input.chain.apply(this.input, arguments);
|
|
arguments[arguments.length - 1].connect(this.output);
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// AUGMENT TONE's PROTOTYPE
|
|
///////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* Connect 'this' to the master output. Shorthand for this.connect(Tone.Master)
|
|
* @returns {Tone} this
|
|
* @example
|
|
* //connect an oscillator to the master output
|
|
* var osc = new Tone.Oscillator().toMaster();
|
|
*/
|
|
Tone.prototype.toMaster = function () {
|
|
this.connect(Tone.Master);
|
|
return this;
|
|
};
|
|
/**
|
|
* Also augment AudioNode's prototype to include toMaster
|
|
* as a convenience
|
|
* @returns {AudioNode} this
|
|
*/
|
|
AudioNode.prototype.toMaster = function () {
|
|
this.connect(Tone.Master);
|
|
return this;
|
|
};
|
|
var MasterConstructor = Tone.Master;
|
|
/**
|
|
* initialize the module and listen for new audio contexts
|
|
*/
|
|
Tone._initAudioContext(function () {
|
|
//a single master output
|
|
if (!Tone.prototype.isUndef(Tone.Master)) {
|
|
Tone.Master = new MasterConstructor();
|
|
} else {
|
|
MasterConstructor.prototype.dispose.call(Tone.Master);
|
|
MasterConstructor.call(Tone.Master);
|
|
}
|
|
});
|
|
return Tone.Master;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Multiply the incoming signal by a number or Multiply two signals.
|
|
* input 0: multiplicand.
|
|
* input 1: multiplier.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Signal}
|
|
* @param {number=} value constant value to multiple. if no value is provided
|
|
* it will be multiplied by the value of input 1.
|
|
* @example
|
|
* var mult = new Tone.Multiply(3);
|
|
* var sig = new Tone.Signal(2).connect(mult);
|
|
* //output of mult is 6.
|
|
*/
|
|
Tone.Multiply = function (value) {
|
|
Tone.call(this, 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 = this.context.createGain();
|
|
/**
|
|
* the scaling parameter
|
|
* @type {AudioParam}
|
|
* @private
|
|
*/
|
|
this._value = this.input[1] = this.output.gain;
|
|
this._value.value = this.defaultArg(value, 0);
|
|
};
|
|
Tone.extend(Tone.Multiply, Tone.Signal);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Multiply} this
|
|
*/
|
|
Tone.Multiply.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._mult.disconnect();
|
|
this._mult = null;
|
|
this._value = null;
|
|
return this;
|
|
};
|
|
return Tone.Multiply;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Oscillator-based transport allows for timing musical events.
|
|
* Supports tempo curves and time changes. A single transport is created
|
|
* on initialization. Unlike browser-based timing (setInterval, requestAnimationFrame)
|
|
* Tone.Transport timing events pass in the exact time of the scheduled event
|
|
* in the argument of the callback function. Pass that time value to the object
|
|
* you're scheduling.
|
|
*
|
|
* @extends {Tone}
|
|
* @example
|
|
* //repeated event every 8th note
|
|
* Tone.Transport.setInterval(function(time){
|
|
* //do something with the time
|
|
* }, "8n");
|
|
* @example
|
|
* //one time event 1 second in the future
|
|
* Tone.Transport.setTimeout(function(time){
|
|
* //do something with the time
|
|
* }, 1);
|
|
* @example
|
|
* //event fixed to the Transports timeline.
|
|
* Tone.Transport.setTimeline(function(time){
|
|
* //do something with the time
|
|
* }, "16:0:0");
|
|
*/
|
|
Tone.Transport = function () {
|
|
/**
|
|
* watches the main oscillator for timing ticks
|
|
* initially starts at 120bpm
|
|
*
|
|
* @private
|
|
* @type {Tone.Clock}
|
|
*/
|
|
this._clock = new Tone.Clock(0, this._processTick.bind(this));
|
|
this._clock.onended = this._onended.bind(this);
|
|
/**
|
|
* If the transport loops or not.
|
|
* @type {boolean}
|
|
*/
|
|
this.loop = false;
|
|
/**
|
|
* the bpm value
|
|
* @type {BPM}
|
|
* @signal
|
|
*/
|
|
this.bpm = new Tone.Signal(120, Tone.Type.BPM);
|
|
/**
|
|
* the signal scalar
|
|
* @type {Tone.Multiply}
|
|
* @private
|
|
*/
|
|
this._bpmMult = new Tone.Multiply(1 / 60 * tatum);
|
|
/**
|
|
* The state of the transport.
|
|
* @type {Tone.State}
|
|
*/
|
|
this.state = Tone.State.Stopped;
|
|
//connect it all up
|
|
this.bpm.chain(this._bpmMult, this._clock.frequency);
|
|
};
|
|
Tone.extend(Tone.Transport);
|
|
/**
|
|
* the defaults
|
|
* @type {Object}
|
|
* @const
|
|
* @static
|
|
*/
|
|
Tone.Transport.defaults = {
|
|
'bpm': 120,
|
|
'swing': 0,
|
|
'swingSubdivision': '16n',
|
|
'timeSignature': 4,
|
|
'loopStart': 0,
|
|
'loopEnd': '4m'
|
|
};
|
|
/**
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
var tatum = 12;
|
|
/**
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
var timelineTicks = 0;
|
|
/**
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
var transportTicks = 0;
|
|
/**
|
|
* Which subdivision the swing is applied to.
|
|
* defaults to an 16th note
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
var swingSubdivision = '16n';
|
|
/**
|
|
* controls which beat the swing is applied to
|
|
* defaults to an 16th note
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
var swingTatum = 3;
|
|
/**
|
|
* controls which beat the swing is applied to
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
var swingAmount = 0;
|
|
/**
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
var transportTimeSignature = 4;
|
|
/**
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
var loopStart = 0;
|
|
/**
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
var loopEnd = tatum * 4;
|
|
/**
|
|
* @private
|
|
* @type {Array}
|
|
*/
|
|
var intervals = [];
|
|
/**
|
|
* @private
|
|
* @type {Array}
|
|
*/
|
|
var timeouts = [];
|
|
/**
|
|
* @private
|
|
* @type {Array}
|
|
*/
|
|
var transportTimeline = [];
|
|
/**
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
var timelineProgress = 0;
|
|
/**
|
|
* All of the synced components
|
|
* @private
|
|
* @type {Array}
|
|
*/
|
|
var SyncedSources = [];
|
|
/**
|
|
* All of the synced Signals
|
|
* @private
|
|
* @type {Array}
|
|
*/
|
|
var SyncedSignals = [];
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// TICKS
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* called on every tick
|
|
* @param {number} tickTime clock relative tick time
|
|
* @private
|
|
*/
|
|
Tone.Transport.prototype._processTick = function (tickTime) {
|
|
if (this.state === Tone.State.Started) {
|
|
if (swingAmount > 0 && timelineTicks % tatum !== 0 && //not on a downbeat
|
|
timelineTicks % swingTatum === 0) {
|
|
//add some swing
|
|
tickTime += this._ticksToSeconds(swingTatum) * swingAmount;
|
|
}
|
|
processIntervals(tickTime);
|
|
processTimeouts(tickTime);
|
|
processTimeline(tickTime);
|
|
transportTicks += 1;
|
|
timelineTicks += 1;
|
|
if (this.loop) {
|
|
if (timelineTicks === loopEnd) {
|
|
this._setTicks(loopStart);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* jump to a specific tick in the timeline
|
|
* updates the timeline callbacks
|
|
*
|
|
* @param {number} ticks the tick to jump to
|
|
* @private
|
|
*/
|
|
Tone.Transport.prototype._setTicks = function (ticks) {
|
|
timelineTicks = ticks;
|
|
for (var i = 0; i < transportTimeline.length; i++) {
|
|
var timeout = transportTimeline[i];
|
|
if (timeout.callbackTick() >= ticks) {
|
|
timelineProgress = i;
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// EVENT PROCESSING
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* process the intervals
|
|
* @param {number} time
|
|
*/
|
|
var processIntervals = function (time) {
|
|
for (var i = 0, len = intervals.length; i < len; i++) {
|
|
var interval = intervals[i];
|
|
if (interval.testInterval(transportTicks)) {
|
|
interval.doCallback(time);
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* process the timeouts
|
|
* @param {number} time
|
|
*/
|
|
var processTimeouts = function (time) {
|
|
var removeTimeouts = 0;
|
|
for (var i = 0, len = timeouts.length; i < len; i++) {
|
|
var timeout = timeouts[i];
|
|
var callbackTick = timeout.callbackTick();
|
|
if (callbackTick <= transportTicks) {
|
|
timeout.doCallback(time);
|
|
removeTimeouts++;
|
|
} else if (callbackTick > transportTicks) {
|
|
break;
|
|
}
|
|
}
|
|
//remove the timeouts off the front of the array after they've been called
|
|
timeouts.splice(0, removeTimeouts);
|
|
};
|
|
/**
|
|
* process the transportTimeline events
|
|
* @param {number} time
|
|
*/
|
|
var processTimeline = function (time) {
|
|
for (var i = timelineProgress, len = transportTimeline.length; i < len; i++) {
|
|
var evnt = transportTimeline[i];
|
|
var callbackTick = evnt.callbackTick();
|
|
if (callbackTick === timelineTicks) {
|
|
timelineProgress = i;
|
|
evnt.doCallback(time);
|
|
} else if (callbackTick > timelineTicks) {
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// INTERVAL
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* Set a callback for a recurring event.
|
|
*
|
|
* @param {function} callback
|
|
* @param {Time} interval
|
|
* @return {number} the id of the interval
|
|
* @example
|
|
* //triggers a callback every 8th note with the exact time of the event
|
|
* Tone.Transport.setInterval(function(time){
|
|
* envelope.triggerAttack(time);
|
|
* }, "8n");
|
|
*/
|
|
Tone.Transport.prototype.setInterval = function (callback, interval, ctx) {
|
|
var tickTime = this._toTicks(interval);
|
|
var timeout = new TimelineEvent(callback, ctx, tickTime, transportTicks);
|
|
intervals.push(timeout);
|
|
return timeout.id;
|
|
};
|
|
/**
|
|
* clear an interval from the processing array
|
|
* @param {number} rmInterval the interval to remove
|
|
* @return {boolean} true if the event was removed
|
|
*/
|
|
Tone.Transport.prototype.clearInterval = function (rmInterval) {
|
|
for (var i = 0; i < intervals.length; i++) {
|
|
var interval = intervals[i];
|
|
if (interval.id === rmInterval) {
|
|
intervals.splice(i, 1);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
/**
|
|
* removes all of the intervals that are currently set
|
|
* @return {boolean} true if the event was removed
|
|
*/
|
|
Tone.Transport.prototype.clearIntervals = function () {
|
|
var willRemove = intervals.length > 0;
|
|
intervals = [];
|
|
return willRemove;
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// TIMEOUT
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* Set a timeout to occur after time from now. NB: the transport must be
|
|
* running for this to be triggered. All timeout events are cleared when the
|
|
* transport is stopped.
|
|
*
|
|
* @param {function} callback
|
|
* @param {Time} time
|
|
* @return {number} the id of the timeout for clearing timeouts
|
|
* @example
|
|
* //trigger an event to happen 1 second from now
|
|
* Tone.Transport.setTimeout(function(time){
|
|
* player.start(time);
|
|
* }, 1)
|
|
*/
|
|
Tone.Transport.prototype.setTimeout = function (callback, time, ctx) {
|
|
var ticks = this._toTicks(time);
|
|
var timeout = new TimelineEvent(callback, ctx, ticks + transportTicks, 0);
|
|
//put it in the right spot
|
|
for (var i = 0, len = timeouts.length; i < len; i++) {
|
|
var testEvnt = timeouts[i];
|
|
if (testEvnt.callbackTick() > timeout.callbackTick()) {
|
|
timeouts.splice(i, 0, timeout);
|
|
return timeout.id;
|
|
}
|
|
}
|
|
//otherwise push it on the end
|
|
timeouts.push(timeout);
|
|
return timeout.id;
|
|
};
|
|
/**
|
|
* clear the timeout based on it's ID
|
|
* @param {number} timeoutID
|
|
* @return {boolean} true if the timeout was removed
|
|
*/
|
|
Tone.Transport.prototype.clearTimeout = function (timeoutID) {
|
|
for (var i = 0; i < timeouts.length; i++) {
|
|
var testTimeout = timeouts[i];
|
|
if (testTimeout.id === timeoutID) {
|
|
timeouts.splice(i, 1);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
/**
|
|
* removes all of the timeouts that are currently set
|
|
* @return {boolean} true if the event was removed
|
|
*/
|
|
Tone.Transport.prototype.clearTimeouts = function () {
|
|
var willRemove = timeouts.length > 0;
|
|
timeouts = [];
|
|
return willRemove;
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// TIMELINE
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* Timeline events are synced to the transportTimeline of the Tone.Transport
|
|
* Unlike Timeout, Timeline events will restart after the
|
|
* Tone.Transport has been stopped and restarted.
|
|
*
|
|
* @param {function} callback
|
|
* @param {Tome.Time} timeout
|
|
* @return {number} the id for clearing the transportTimeline event
|
|
* @example
|
|
* //trigger the start of a part on the 16th measure
|
|
* Tone.Transport.setTimeline(function(time){
|
|
* part.start(time);
|
|
* }, "16m");
|
|
*/
|
|
Tone.Transport.prototype.setTimeline = function (callback, timeout, ctx) {
|
|
var ticks = this._toTicks(timeout);
|
|
var timelineEvnt = new TimelineEvent(callback, ctx, ticks, 0);
|
|
//put it in the right spot
|
|
for (var i = timelineProgress, len = transportTimeline.length; i < len; i++) {
|
|
var testEvnt = transportTimeline[i];
|
|
if (testEvnt.callbackTick() > timelineEvnt.callbackTick()) {
|
|
transportTimeline.splice(i, 0, timelineEvnt);
|
|
return timelineEvnt.id;
|
|
}
|
|
}
|
|
//otherwise push it on the end
|
|
transportTimeline.push(timelineEvnt);
|
|
return timelineEvnt.id;
|
|
};
|
|
/**
|
|
* clear the transportTimeline event from the
|
|
* @param {number} timelineID
|
|
* @return {boolean} true if it was removed
|
|
*/
|
|
Tone.Transport.prototype.clearTimeline = function (timelineID) {
|
|
for (var i = 0; i < transportTimeline.length; i++) {
|
|
var testTimeline = transportTimeline[i];
|
|
if (testTimeline.id === timelineID) {
|
|
transportTimeline.splice(i, 1);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
/**
|
|
* remove all events from the timeline
|
|
* @returns {boolean} true if the events were removed
|
|
*/
|
|
Tone.Transport.prototype.clearTimelines = function () {
|
|
timelineProgress = 0;
|
|
var willRemove = transportTimeline.length > 0;
|
|
transportTimeline = [];
|
|
return willRemove;
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// TIME CONVERSIONS
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* turns the time into
|
|
* @param {Time} time
|
|
* @return {number}
|
|
* @private
|
|
*/
|
|
Tone.Transport.prototype._toTicks = function (time) {
|
|
//get the seconds
|
|
var seconds = this.toSeconds(time);
|
|
var quarter = this.notationToSeconds('4n');
|
|
var quarters = seconds / quarter;
|
|
var tickNum = quarters * tatum;
|
|
//quantize to tick value
|
|
return Math.round(tickNum);
|
|
};
|
|
/**
|
|
* convert ticks into seconds
|
|
*
|
|
* @param {number} ticks
|
|
* @param {number=} bpm
|
|
* @param {number=} timeSignature
|
|
* @return {number} seconds
|
|
* @private
|
|
*/
|
|
Tone.Transport.prototype._ticksToSeconds = function (ticks, bpm, timeSignature) {
|
|
ticks = Math.floor(ticks);
|
|
var quater = this.notationToSeconds('4n', bpm, timeSignature);
|
|
return quater * ticks / tatum;
|
|
};
|
|
/**
|
|
* returns the time of the next beat
|
|
* @param {string} [subdivision="4n"]
|
|
* @return {number} the time in seconds of the next subdivision
|
|
*/
|
|
Tone.Transport.prototype.nextBeat = function (subdivision) {
|
|
subdivision = this.defaultArg(subdivision, '4n');
|
|
var tickNum = this._toTicks(subdivision);
|
|
var remainingTicks = transportTicks % tickNum;
|
|
var nextTick = remainingTicks;
|
|
if (remainingTicks > 0) {
|
|
nextTick = tickNum - remainingTicks;
|
|
}
|
|
return this._ticksToSeconds(nextTick);
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// START/STOP/PAUSE
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* start the transport and all sources synced to the transport
|
|
*
|
|
* @param {Time} time
|
|
* @param {Time=} offset the offset position to start
|
|
* @returns {Tone.Transport} this
|
|
*/
|
|
Tone.Transport.prototype.start = function (time, offset) {
|
|
if (this.state === Tone.State.Stopped || this.state === Tone.State.Paused) {
|
|
if (!this.isUndef(offset)) {
|
|
this._setTicks(this._toTicks(offset));
|
|
}
|
|
this.state = Tone.State.Started;
|
|
var startTime = this.toSeconds(time);
|
|
this._clock.start(startTime);
|
|
//call start on each of the synced sources
|
|
for (var i = 0; i < SyncedSources.length; i++) {
|
|
var source = SyncedSources[i].source;
|
|
var delay = SyncedSources[i].delay;
|
|
source.start(startTime + delay);
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* stop the transport and all sources synced to the transport
|
|
*
|
|
* @param {Time} time
|
|
* @returns {Tone.Transport} this
|
|
*/
|
|
Tone.Transport.prototype.stop = function (time) {
|
|
if (this.state === Tone.State.Started || this.state === Tone.State.Paused) {
|
|
var stopTime = this.toSeconds(time);
|
|
this._clock.stop(stopTime);
|
|
//call start on each of the synced sources
|
|
for (var i = 0; i < SyncedSources.length; i++) {
|
|
var source = SyncedSources[i].source;
|
|
source.stop(stopTime);
|
|
}
|
|
} else {
|
|
this._onended();
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* invoked when the transport is stopped
|
|
* @private
|
|
*/
|
|
Tone.Transport.prototype._onended = function () {
|
|
transportTicks = 0;
|
|
this._setTicks(0);
|
|
this.clearTimeouts();
|
|
this.state = Tone.State.Stopped;
|
|
};
|
|
/**
|
|
* pause the transport and all sources synced to the transport
|
|
*
|
|
* @param {Time} time
|
|
* @returns {Tone.Transport} this
|
|
*/
|
|
Tone.Transport.prototype.pause = function (time) {
|
|
if (this.state === Tone.State.Started) {
|
|
this.state = Tone.State.Paused;
|
|
var stopTime = this.toSeconds(time);
|
|
this._clock.stop(stopTime);
|
|
//call pause on each of the synced sources
|
|
for (var i = 0; i < SyncedSources.length; i++) {
|
|
var source = SyncedSources[i].source;
|
|
source.pause(stopTime);
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// SETTERS/GETTERS
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* Time signature as just the numerator over 4.
|
|
* For example 4/4 would be just 4 and 6/8 would be 3.
|
|
* @memberOf Tone.Transport#
|
|
* @type {number}
|
|
* @name timeSignature
|
|
*/
|
|
Object.defineProperty(Tone.Transport.prototype, 'timeSignature', {
|
|
get: function () {
|
|
return transportTimeSignature;
|
|
},
|
|
set: function (numerator) {
|
|
transportTimeSignature = numerator;
|
|
}
|
|
});
|
|
/**
|
|
* The loop start point
|
|
* @memberOf Tone.Transport#
|
|
* @type {Time}
|
|
* @name loopStart
|
|
*/
|
|
Object.defineProperty(Tone.Transport.prototype, 'loopStart', {
|
|
get: function () {
|
|
return this._ticksToSeconds(loopStart);
|
|
},
|
|
set: function (startPosition) {
|
|
loopStart = this._toTicks(startPosition);
|
|
}
|
|
});
|
|
/**
|
|
* The loop end point
|
|
* @memberOf Tone.Transport#
|
|
* @type {Time}
|
|
* @name loopEnd
|
|
*/
|
|
Object.defineProperty(Tone.Transport.prototype, 'loopEnd', {
|
|
get: function () {
|
|
return this._ticksToSeconds(loopEnd);
|
|
},
|
|
set: function (endPosition) {
|
|
loopEnd = this._toTicks(endPosition);
|
|
}
|
|
});
|
|
/**
|
|
* shorthand loop setting
|
|
* @param {Time} startPosition
|
|
* @param {Time} endPosition
|
|
* @returns {Tone.Transport} this
|
|
*/
|
|
Tone.Transport.prototype.setLoopPoints = function (startPosition, endPosition) {
|
|
this.loopStart = startPosition;
|
|
this.loopEnd = endPosition;
|
|
return this;
|
|
};
|
|
/**
|
|
* The swing value. Between 0-1 where 1 equal to
|
|
* the note + half the subdivision.
|
|
* @memberOf Tone.Transport#
|
|
* @type {NormalRange}
|
|
* @name swing
|
|
*/
|
|
Object.defineProperty(Tone.Transport.prototype, 'swing', {
|
|
get: function () {
|
|
return swingAmount * 2;
|
|
},
|
|
set: function (amount) {
|
|
//scale the values to a normal range
|
|
swingAmount = amount * 0.5;
|
|
}
|
|
});
|
|
/**
|
|
* Set the subdivision which the swing will be applied to.
|
|
* The default values is a 16th note. Value must be less
|
|
* than a quarter note.
|
|
*
|
|
*
|
|
* @memberOf Tone.Transport#
|
|
* @type {Time}
|
|
* @name swingSubdivision
|
|
*/
|
|
Object.defineProperty(Tone.Transport.prototype, 'swingSubdivision', {
|
|
get: function () {
|
|
return swingSubdivision;
|
|
},
|
|
set: function (subdivision) {
|
|
//scale the values to a normal range
|
|
swingSubdivision = subdivision;
|
|
swingTatum = this._toTicks(subdivision);
|
|
}
|
|
});
|
|
/**
|
|
* The Transport's position in MEASURES:BEATS:SIXTEENTHS.
|
|
* Setting the value will jump to that position right away.
|
|
*
|
|
* @memberOf Tone.Transport#
|
|
* @type {string}
|
|
* @name position
|
|
*/
|
|
Object.defineProperty(Tone.Transport.prototype, 'position', {
|
|
get: function () {
|
|
var quarters = timelineTicks / tatum;
|
|
var measures = Math.floor(quarters / transportTimeSignature);
|
|
var sixteenths = Math.floor(quarters % 1 * 4);
|
|
quarters = Math.floor(quarters) % transportTimeSignature;
|
|
var progress = [
|
|
measures,
|
|
quarters,
|
|
sixteenths
|
|
];
|
|
return progress.join(':');
|
|
},
|
|
set: function (progress) {
|
|
var ticks = this._toTicks(progress);
|
|
this._setTicks(ticks);
|
|
}
|
|
});
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// SYNCING
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* Sync a source to the transport so that
|
|
* @param {Tone.Source} source the source to sync to the transport
|
|
* @param {Time} delay (optionally) start the source with a delay from the transport
|
|
* @returns {Tone.Transport} this
|
|
*/
|
|
Tone.Transport.prototype.syncSource = function (source, startDelay) {
|
|
SyncedSources.push({
|
|
source: source,
|
|
delay: this.toSeconds(this.defaultArg(startDelay, 0))
|
|
});
|
|
return this;
|
|
};
|
|
/**
|
|
* remove the source from the list of Synced Sources
|
|
*
|
|
* @param {Tone.Source} source [description]
|
|
* @returns {Tone.Transport} this
|
|
*/
|
|
Tone.Transport.prototype.unsyncSource = function (source) {
|
|
for (var i = 0; i < SyncedSources.length; i++) {
|
|
if (SyncedSources[i].source === source) {
|
|
SyncedSources.splice(i, 1);
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* attaches the signal to the tempo control signal so that
|
|
* any changes in the tempo will change the signal in the same
|
|
* ratio.
|
|
*
|
|
* @param {Tone.Signal} signal
|
|
* @param {number=} ratio Optionally pass in the ratio between
|
|
* the two signals. Otherwise it will be computed
|
|
* based on their current values.
|
|
* @returns {Tone.Transport} this
|
|
*/
|
|
Tone.Transport.prototype.syncSignal = function (signal, ratio) {
|
|
if (!ratio) {
|
|
//get the sync ratio
|
|
if (signal._value.value !== 0) {
|
|
ratio = signal._value.value / this.bpm.value;
|
|
} else {
|
|
ratio = 0;
|
|
}
|
|
}
|
|
var ratioSignal = this.context.createGain();
|
|
ratioSignal.gain.value = ratio;
|
|
this.bpm.chain(ratioSignal, signal._value);
|
|
SyncedSignals.push({
|
|
'ratio': ratioSignal,
|
|
'signal': signal,
|
|
'initial': signal._value.value
|
|
});
|
|
signal._value.value = 0;
|
|
return this;
|
|
};
|
|
/**
|
|
* Unsyncs a previously synced signal from the transport's control
|
|
* @param {Tone.Signal} signal
|
|
* @returns {Tone.Transport} this
|
|
*/
|
|
Tone.Transport.prototype.unsyncSignal = function (signal) {
|
|
for (var i = 0; i < SyncedSignals.length; i++) {
|
|
var syncedSignal = SyncedSignals[i];
|
|
if (syncedSignal.signal === signal) {
|
|
syncedSignal.ratio.disconnect();
|
|
syncedSignal.signal._value.value = syncedSignal.initial;
|
|
SyncedSignals.splice(i, 1);
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Transport} this
|
|
*/
|
|
Tone.Transport.prototype.dispose = function () {
|
|
this._clock.dispose();
|
|
this._clock = null;
|
|
this.bpm.dispose();
|
|
this.bpm = null;
|
|
this._bpmMult.dispose();
|
|
this._bpmMult = null;
|
|
return this;
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// TIMELINE EVENT
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* @static
|
|
* @type {number}
|
|
*/
|
|
var TimelineEventIDCounter = 0;
|
|
/**
|
|
* A Timeline event
|
|
*
|
|
* @constructor
|
|
* @private
|
|
* @param {function(number)} callback
|
|
* @param {Object} context
|
|
* @param {number} tickTime
|
|
* @param {number} startTicks
|
|
*/
|
|
var TimelineEvent = function (callback, context, tickTime, startTicks) {
|
|
this.startTicks = startTicks;
|
|
this.tickTime = tickTime;
|
|
this.callback = callback;
|
|
this.context = context;
|
|
this.id = TimelineEventIDCounter++;
|
|
};
|
|
/**
|
|
* invoke the callback in the correct context
|
|
* passes in the playback time
|
|
*
|
|
* @param {number} playbackTime
|
|
*/
|
|
TimelineEvent.prototype.doCallback = function (playbackTime) {
|
|
this.callback.call(this.context, playbackTime);
|
|
};
|
|
/**
|
|
* get the tick which the callback is supposed to occur on
|
|
*
|
|
* @return {number}
|
|
*/
|
|
TimelineEvent.prototype.callbackTick = function () {
|
|
return this.startTicks + this.tickTime;
|
|
};
|
|
/**
|
|
* test if the tick occurs on the interval
|
|
*
|
|
* @param {number} tick
|
|
* @return {boolean}
|
|
*/
|
|
TimelineEvent.prototype.testInterval = function (tick) {
|
|
return (tick - this.startTicks) % this.tickTime === 0;
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// AUGMENT TONE'S PROTOTYPE TO INCLUDE TRANSPORT TIMING
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* tests if a string is musical notation
|
|
* i.e.:
|
|
* 4n = quarter note
|
|
* 2m = two measures
|
|
* 8t = eighth-note triplet
|
|
*
|
|
* @return {boolean}
|
|
* @method isNotation
|
|
* @lends Tone.prototype.isNotation
|
|
*/
|
|
Tone.prototype.isNotation = function () {
|
|
var notationFormat = new RegExp(/[0-9]+[mnt]$/i);
|
|
return function (note) {
|
|
return notationFormat.test(note);
|
|
};
|
|
}();
|
|
/**
|
|
* tests if a string is transportTime
|
|
* i.e. :
|
|
* 1:2:0 = 1 measure + two quarter notes + 0 sixteenth notes
|
|
*
|
|
* @return {boolean}
|
|
*
|
|
* @method isTransportTime
|
|
* @lends Tone.prototype.isTransportTime
|
|
*/
|
|
Tone.prototype.isTransportTime = function () {
|
|
var transportTimeFormat = new RegExp(/^\d+(\.\d+)?:\d+(\.\d+)?(:\d+(\.\d+)?)?$/i);
|
|
return function (transportTime) {
|
|
return transportTimeFormat.test(transportTime);
|
|
};
|
|
}();
|
|
/**
|
|
*
|
|
* convert notation format strings to seconds
|
|
*
|
|
* @param {string} notation
|
|
* @param {number=} bpm
|
|
* @param {number=} timeSignature
|
|
* @return {number}
|
|
*
|
|
*/
|
|
Tone.prototype.notationToSeconds = function (notation, bpm, timeSignature) {
|
|
bpm = this.defaultArg(bpm, Tone.Transport.bpm.value);
|
|
timeSignature = this.defaultArg(timeSignature, transportTimeSignature);
|
|
var beatTime = 60 / bpm;
|
|
var subdivision = parseInt(notation, 10);
|
|
var beats = 0;
|
|
if (subdivision === 0) {
|
|
beats = 0;
|
|
}
|
|
var lastLetter = notation.slice(-1);
|
|
if (lastLetter === 't') {
|
|
beats = 4 / subdivision * 2 / 3;
|
|
} else if (lastLetter === 'n') {
|
|
beats = 4 / subdivision;
|
|
} else if (lastLetter === 'm') {
|
|
beats = subdivision * timeSignature;
|
|
} else {
|
|
beats = 0;
|
|
}
|
|
return beatTime * beats;
|
|
};
|
|
/**
|
|
* convert transportTime into seconds.
|
|
*
|
|
* ie: 4:2:3 == 4 measures + 2 quarters + 3 sixteenths
|
|
*
|
|
* @param {string} transportTime
|
|
* @param {number=} bpm
|
|
* @param {number=} timeSignature
|
|
* @return {number} seconds
|
|
*
|
|
* @lends Tone.prototype.transportTimeToSeconds
|
|
*/
|
|
Tone.prototype.transportTimeToSeconds = function (transportTime, bpm, timeSignature) {
|
|
bpm = this.defaultArg(bpm, Tone.Transport.bpm.value);
|
|
timeSignature = this.defaultArg(timeSignature, transportTimeSignature);
|
|
var measures = 0;
|
|
var quarters = 0;
|
|
var sixteenths = 0;
|
|
var split = transportTime.split(':');
|
|
if (split.length === 2) {
|
|
measures = parseFloat(split[0]);
|
|
quarters = parseFloat(split[1]);
|
|
} else if (split.length === 1) {
|
|
quarters = parseFloat(split[0]);
|
|
} else if (split.length === 3) {
|
|
measures = parseFloat(split[0]);
|
|
quarters = parseFloat(split[1]);
|
|
sixteenths = parseFloat(split[2]);
|
|
}
|
|
var beats = measures * timeSignature + quarters + sixteenths / 4;
|
|
return beats * this.notationToSeconds('4n');
|
|
};
|
|
/**
|
|
* Convert seconds to the closest transportTime in the form
|
|
* measures:quarters:sixteenths
|
|
*
|
|
* @method toTransportTime
|
|
*
|
|
* @param {Time} seconds
|
|
* @param {number=} bpm
|
|
* @param {number=} timeSignature
|
|
* @return {string}
|
|
*
|
|
* @lends Tone.prototype.toTransportTime
|
|
*/
|
|
Tone.prototype.toTransportTime = function (time, bpm, timeSignature) {
|
|
var seconds = this.toSeconds(time, bpm, timeSignature);
|
|
bpm = this.defaultArg(bpm, Tone.Transport.bpm.value);
|
|
timeSignature = this.defaultArg(timeSignature, transportTimeSignature);
|
|
var quarterTime = this.notationToSeconds('4n');
|
|
var quarters = seconds / quarterTime;
|
|
var measures = Math.floor(quarters / timeSignature);
|
|
var sixteenths = Math.floor(quarters % 1 * 4);
|
|
quarters = Math.floor(quarters) % timeSignature;
|
|
var progress = [
|
|
measures,
|
|
quarters,
|
|
sixteenths
|
|
];
|
|
return progress.join(':');
|
|
};
|
|
/**
|
|
* Convert a frequency representation into a number.
|
|
*
|
|
* @param {Frequency} freq
|
|
* @param {number=} now if passed in, this number will be
|
|
* used for all 'now' relative timings
|
|
* @return {number} the frequency in hertz
|
|
*/
|
|
Tone.prototype.toFrequency = function (freq, now) {
|
|
if (this.isFrequency(freq)) {
|
|
return parseFloat(freq);
|
|
} else if (this.isNotation(freq) || this.isTransportTime(freq)) {
|
|
return this.secondsToFrequency(this.toSeconds(freq, now));
|
|
} else {
|
|
return freq;
|
|
}
|
|
};
|
|
/**
|
|
* Convert Time into seconds.
|
|
*
|
|
* Unlike the method which it overrides, this takes into account
|
|
* transporttime and musical notation.
|
|
*
|
|
* Time : 1.40
|
|
* Notation: 4n|1m|2t
|
|
* TransportTime: 2:4:1 (measure:quarters:sixteens)
|
|
* Now Relative: +3n
|
|
* Math: 3n+16n or even very complicated expressions ((3n*2)/6 + 1)
|
|
*
|
|
* @override
|
|
* @param {Time} time
|
|
* @param {number=} now if passed in, this number will be
|
|
* used for all 'now' relative timings
|
|
* @return {number}
|
|
*/
|
|
Tone.prototype.toSeconds = function (time, now) {
|
|
now = this.defaultArg(now, this.now());
|
|
if (typeof time === 'number') {
|
|
return time; //assuming that it's seconds
|
|
} else if (typeof time === 'string') {
|
|
var plusTime = 0;
|
|
if (time.charAt(0) === '+') {
|
|
plusTime = now;
|
|
time = time.slice(1);
|
|
}
|
|
var components = time.split(/[\(\)\-\+\/\*]/);
|
|
if (components.length > 1) {
|
|
var originalTime = time;
|
|
for (var i = 0; i < components.length; i++) {
|
|
var symb = components[i].trim();
|
|
if (symb !== '') {
|
|
var val = this.toSeconds(symb);
|
|
time = time.replace(symb, val);
|
|
}
|
|
}
|
|
try {
|
|
//i know eval is evil, but i think it's safe here
|
|
time = eval(time); // jshint ignore:line
|
|
} catch (e) {
|
|
throw new EvalError('problem evaluating Tone.Type.Time: ' + originalTime);
|
|
}
|
|
} else if (this.isNotation(time)) {
|
|
time = this.notationToSeconds(time);
|
|
} else if (this.isTransportTime(time)) {
|
|
time = this.transportTimeToSeconds(time);
|
|
} else if (this.isFrequency(time)) {
|
|
time = this.frequencyToSeconds(time);
|
|
} else {
|
|
time = parseFloat(time);
|
|
}
|
|
return time + plusTime;
|
|
} else {
|
|
return now;
|
|
}
|
|
};
|
|
var TransportConstructor = Tone.Transport;
|
|
Tone._initAudioContext(function () {
|
|
if (typeof Tone.Transport === 'function') {
|
|
//a single transport object
|
|
Tone.Transport = new Tone.Transport();
|
|
} else {
|
|
//stop the clock
|
|
Tone.Transport.stop();
|
|
//get the previous bpm
|
|
var bpm = Tone.Transport.bpm.value;
|
|
//destory the old clock
|
|
Tone.Transport._clock.dispose();
|
|
//make new Transport insides
|
|
TransportConstructor.call(Tone.Transport);
|
|
//set the bpm
|
|
Tone.Transport.bpm.value = bpm;
|
|
}
|
|
});
|
|
return Tone.Transport;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class A timed note. Creating a note will register a callback
|
|
* which will be invoked on the channel at the time with
|
|
* whatever value was specified.
|
|
*
|
|
* @constructor
|
|
* @param {number|string} channel the channel name of the note
|
|
* @param {Time} time the time when the note will occur
|
|
* @param {string|number|Object|Array} value the value of the note
|
|
*/
|
|
Tone.Note = function (channel, time, value) {
|
|
/**
|
|
* the value of the note. This value is returned
|
|
* when the channel callback is invoked.
|
|
*
|
|
* @type {string|number|Object}
|
|
*/
|
|
this.value = value;
|
|
/**
|
|
* the channel name or number
|
|
*
|
|
* @type {string|number}
|
|
* @private
|
|
*/
|
|
this._channel = channel;
|
|
/**
|
|
* an internal reference to the id of the timeline
|
|
* callback which is set.
|
|
*
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._timelineID = Tone.Transport.setTimeline(this._trigger.bind(this), time);
|
|
};
|
|
/**
|
|
* invoked by the timeline
|
|
* @private
|
|
* @param {number} time the time at which the note should play
|
|
*/
|
|
Tone.Note.prototype._trigger = function (time) {
|
|
//invoke the callback
|
|
channelCallbacks(this._channel, time, this.value);
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Note} this
|
|
*/
|
|
Tone.Note.prototype.dispose = function () {
|
|
Tone.Tranport.clearTimeline(this._timelineID);
|
|
this.value = null;
|
|
return this;
|
|
};
|
|
/**
|
|
* @private
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
var NoteChannels = {};
|
|
/**
|
|
* invoke all of the callbacks on a specific channel
|
|
* @private
|
|
*/
|
|
function channelCallbacks(channel, time, value) {
|
|
if (NoteChannels.hasOwnProperty(channel)) {
|
|
var callbacks = NoteChannels[channel];
|
|
for (var i = 0, len = callbacks.length; i < len; i++) {
|
|
var callback = callbacks[i];
|
|
if (Array.isArray(value)) {
|
|
callback.apply(window, [time].concat(value));
|
|
} else {
|
|
callback(time, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* listen to a specific channel, get all of the note callbacks
|
|
* @static
|
|
* @param {string|number} channel the channel to route note events from
|
|
* @param {function(*)} callback callback to be invoked when a note will occur
|
|
* on the specified channel
|
|
*/
|
|
Tone.Note.route = function (channel, callback) {
|
|
if (NoteChannels.hasOwnProperty(channel)) {
|
|
NoteChannels[channel].push(callback);
|
|
} else {
|
|
NoteChannels[channel] = [callback];
|
|
}
|
|
};
|
|
/**
|
|
* Remove a previously routed callback from a channel.
|
|
* @static
|
|
* @param {string|number} channel The channel to unroute note events from
|
|
* @param {function(*)} callback Callback which was registered to the channel.
|
|
*/
|
|
Tone.Note.unroute = function (channel, callback) {
|
|
if (NoteChannels.hasOwnProperty(channel)) {
|
|
var channelCallback = NoteChannels[channel];
|
|
var index = channelCallback.indexOf(callback);
|
|
if (index !== -1) {
|
|
NoteChannels[channel].splice(index, 1);
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* Parses a score and registers all of the notes along the timeline.
|
|
* <br><br>
|
|
* Scores are a JSON object with instruments at the top level
|
|
* and an array of time and values. The value of a note can be 0 or more
|
|
* parameters.
|
|
* <br><br>
|
|
* The only requirement for the score format is that the time is the first (or only)
|
|
* value in the array. All other values are optional and will be passed into the callback
|
|
* function registered using `Note.route(channelName, callback)`.
|
|
* <br><br>
|
|
* To convert MIDI files to score notation, take a look at utils/MidiToScore.js
|
|
*
|
|
* @example
|
|
* //an example JSON score which sets up events on channels
|
|
* var score = {
|
|
* "synth" : [["0", "C3"], ["0:1", "D3"], ["0:2", "E3"], ... ],
|
|
* "bass" : [["0", "C2"], ["1:0", "A2"], ["2:0", "C2"], ["3:0", "A2"], ... ],
|
|
* "kick" : ["0", "0:2", "1:0", "1:2", "2:0", ... ],
|
|
* //...
|
|
* };
|
|
* //parse the score into Notes
|
|
* Tone.Note.parseScore(score);
|
|
* //route all notes on the "synth" channel
|
|
* Tone.Note.route("synth", function(time, note){
|
|
* //trigger synth
|
|
* });
|
|
* @static
|
|
* @param {Object} score
|
|
* @return {Array} an array of all of the notes that were created
|
|
*/
|
|
Tone.Note.parseScore = function (score) {
|
|
var notes = [];
|
|
for (var inst in score) {
|
|
var part = score[inst];
|
|
if (inst === 'tempo') {
|
|
Tone.Transport.bpm.value = part;
|
|
} else if (inst === 'timeSignature') {
|
|
Tone.Transport.timeSignature = part[0] / (part[1] / 4);
|
|
} else if (Array.isArray(part)) {
|
|
for (var i = 0; i < part.length; i++) {
|
|
var noteDescription = part[i];
|
|
var note;
|
|
if (Array.isArray(noteDescription)) {
|
|
var time = noteDescription[0];
|
|
var value = noteDescription.slice(1);
|
|
note = new Tone.Note(inst, time, value);
|
|
} else {
|
|
note = new Tone.Note(inst, noteDescription);
|
|
}
|
|
notes.push(note);
|
|
}
|
|
} else {
|
|
throw new TypeError('score parts must be Arrays');
|
|
}
|
|
}
|
|
return notes;
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// MUSIC NOTES
|
|
//
|
|
// Augments Tone.prototype to include note methods
|
|
///////////////////////////////////////////////////////////////////////////
|
|
var noteToIndex = {
|
|
'c': 0,
|
|
'c#': 1,
|
|
'db': 1,
|
|
'd': 2,
|
|
'd#': 3,
|
|
'eb': 3,
|
|
'e': 4,
|
|
'f': 5,
|
|
'f#': 6,
|
|
'gb': 6,
|
|
'g': 7,
|
|
'g#': 8,
|
|
'ab': 8,
|
|
'a': 9,
|
|
'a#': 10,
|
|
'bb': 10,
|
|
'b': 11
|
|
};
|
|
var noteIndexToNote = [
|
|
'C',
|
|
'C#',
|
|
'D',
|
|
'D#',
|
|
'E',
|
|
'F',
|
|
'F#',
|
|
'G',
|
|
'G#',
|
|
'A',
|
|
'A#',
|
|
'B'
|
|
];
|
|
var middleC = 261.6255653005986;
|
|
/**
|
|
* Convert a note name to frequency.
|
|
* @param {string} note
|
|
* @return {number}
|
|
* @example
|
|
* var freq = tone.noteToFrequency("A4"); //returns 440
|
|
*/
|
|
Tone.prototype.noteToFrequency = function (note) {
|
|
//break apart the note by frequency and octave
|
|
var parts = note.split(/(\d+)/);
|
|
if (parts.length === 3) {
|
|
var index = noteToIndex[parts[0].toLowerCase()];
|
|
var octave = parts[1];
|
|
var noteNumber = index + parseInt(octave, 10) * 12;
|
|
return Math.pow(2, (noteNumber - 48) / 12) * middleC;
|
|
} else {
|
|
return 0;
|
|
}
|
|
};
|
|
/**
|
|
* Test if a string is in note format: i.e. "C4".
|
|
* @param {string|number} note The note to test
|
|
* @return {boolean} true if it's in the form of a note
|
|
* @method isNotation
|
|
* @lends Tone.prototype.isNote
|
|
* @function
|
|
*/
|
|
Tone.prototype.isNote = function () {
|
|
var noteFormat = new RegExp(/[a-g]{1}([b#]{1}|[b#]{0})[0-9]+$/i);
|
|
return function (note) {
|
|
if (typeof note === 'string') {
|
|
note = note.toLowerCase();
|
|
}
|
|
return noteFormat.test(note);
|
|
};
|
|
}();
|
|
/**
|
|
* A pointer to the previous toFrequency method
|
|
* @private
|
|
* @function
|
|
*/
|
|
Tone.prototype._overwrittenToFrequency = Tone.prototype.toFrequency;
|
|
/**
|
|
* A method which accepts frequencies in the form
|
|
* of notes (`"C#4"`), frequencies as strings ("49hz"), frequency numbers,
|
|
* or Time and converts them to their frequency as a number in hertz.
|
|
* @param {Frequency} note the note name or notation
|
|
* @param {number=} now if passed in, this number will be
|
|
* used for all 'now' relative timings
|
|
* @return {number} the frequency as a number
|
|
*/
|
|
Tone.prototype.toFrequency = function (note, now) {
|
|
if (this.isNote(note)) {
|
|
note = this.noteToFrequency(note);
|
|
}
|
|
return this._overwrittenToFrequency(note, now);
|
|
};
|
|
/**
|
|
* Convert a note name (i.e. A4, C#5, etc to a frequency).
|
|
* @param {number} freq
|
|
* @return {string}
|
|
*/
|
|
Tone.prototype.frequencyToNote = function (freq) {
|
|
var log = Math.log(freq / middleC) / Math.LN2;
|
|
var noteNumber = Math.round(12 * log) + 48;
|
|
var octave = Math.floor(noteNumber / 12);
|
|
var noteName = noteIndexToNote[noteNumber % 12];
|
|
return noteName + octave.toString();
|
|
};
|
|
/**
|
|
* Convert an interval (in semitones) to a frequency ratio.
|
|
*
|
|
* @param {number} interval the number of semitones above the base note
|
|
* @return {number} the frequency ratio
|
|
* @example
|
|
* tone.intervalToFrequencyRatio(0); // returns 1
|
|
* tone.intervalToFrequencyRatio(12); // returns 2
|
|
*/
|
|
Tone.prototype.intervalToFrequencyRatio = function (interval) {
|
|
return Math.pow(2, interval / 12);
|
|
};
|
|
/**
|
|
* Convert a midi note number into a note name.
|
|
*
|
|
* @param {number} midiNumber the midi note number
|
|
* @return {string} the note's name and octave
|
|
* @example
|
|
* tone.midiToNote(60); // returns "C3"
|
|
*/
|
|
Tone.prototype.midiToNote = function (midiNumber) {
|
|
var octave = Math.floor(midiNumber / 12) - 2;
|
|
var note = midiNumber % 12;
|
|
return noteIndexToNote[note] + octave;
|
|
};
|
|
/**
|
|
* Convert a note to it's midi value.
|
|
*
|
|
* @param {string} note the note name (i.e. "C3")
|
|
* @return {number} the midi value of that note
|
|
* @example
|
|
* tone.noteToMidi("C3"); // returns 60
|
|
*/
|
|
Tone.prototype.noteToMidi = function (note) {
|
|
//break apart the note by frequency and octave
|
|
var parts = note.split(/(\d+)/);
|
|
if (parts.length === 3) {
|
|
var index = noteToIndex[parts[0].toLowerCase()];
|
|
var octave = parts[1];
|
|
return index + (parseInt(octave, 10) + 2) * 12;
|
|
} else {
|
|
return 0;
|
|
}
|
|
};
|
|
return Tone.Note;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Pow applies an exponent to the incoming signal. The incoming signal
|
|
* must be in the range -1,1
|
|
*
|
|
* @extends {Tone.SignalBase}
|
|
* @constructor
|
|
* @param {number} 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) {
|
|
/**
|
|
* the exponent
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
this._exp = this.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.prototype.dispose.call(this);
|
|
this._expScaler.dispose();
|
|
this._expScaler = null;
|
|
return this;
|
|
};
|
|
return Tone.Pow;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class ADSR envelope generator attaches to an AudioParam or Signal.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone}
|
|
* @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 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 = this.optionsObject(arguments, [
|
|
'attack',
|
|
'decay',
|
|
'sustain',
|
|
'release'
|
|
], Tone.Envelope.defaults);
|
|
/**
|
|
* The attack time
|
|
* @type {Time}
|
|
*/
|
|
this.attack = options.attack;
|
|
/**
|
|
* The decay time
|
|
* @type {Time}
|
|
*/
|
|
this.decay = options.decay;
|
|
/**
|
|
* the sustain is a value between 0-1
|
|
* @type {NormalRange}
|
|
*/
|
|
this.sustain = options.sustain;
|
|
/**
|
|
* The release time
|
|
* @type {Time}
|
|
*/
|
|
this.release = options.release;
|
|
/**
|
|
* the next time the envelope is attacked
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._nextAttack = Infinity;
|
|
/**
|
|
* the next time the envelope is decayed
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._nextDecay = Infinity;
|
|
/**
|
|
* the next time the envelope is sustain
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._nextSustain = Infinity;
|
|
/**
|
|
* the next time the envelope is released
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._nextRelease = Infinity;
|
|
/**
|
|
* the next time the envelope is at standby
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._nextStandby = Infinity;
|
|
/**
|
|
* the next time the envelope is at standby
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._attackCurve = Tone.Envelope.Type.Linear;
|
|
/**
|
|
* the last recorded velocity value
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._peakValue = 1;
|
|
/**
|
|
* the minimum output value
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._minOutput = 0.0001;
|
|
/**
|
|
* the signal
|
|
* @type {Tone.Signal}
|
|
* @private
|
|
*/
|
|
this._sig = this.output = new Tone.Signal(0);
|
|
//set the attackCurve initially
|
|
this.attackCurve = options.attackCurve;
|
|
};
|
|
Tone.extend(Tone.Envelope);
|
|
/**
|
|
* the default parameters
|
|
* @static
|
|
* @const
|
|
*/
|
|
Tone.Envelope.defaults = {
|
|
'attack': 0.01,
|
|
'decay': 0.1,
|
|
'sustain': 0.5,
|
|
'release': 1,
|
|
'attackCurve': 'linear'
|
|
};
|
|
/**
|
|
* the envelope time multipler
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
Tone.Envelope.prototype._timeMult = 0.25;
|
|
/**
|
|
* The slope of the attack. Either "linear" or "exponential".
|
|
* @memberOf Tone.Envelope#
|
|
* @type {string}
|
|
* @name attackCurve
|
|
* @example
|
|
* env.attackCurve = "linear";
|
|
*/
|
|
Object.defineProperty(Tone.Envelope.prototype, 'attackCurve', {
|
|
get: function () {
|
|
return this._attackCurve;
|
|
},
|
|
set: function (type) {
|
|
if (type === Tone.Envelope.Type.Linear || type === Tone.Envelope.Type.Exponential) {
|
|
this._attackCurve = type;
|
|
} else {
|
|
throw Error('attackCurve must be either "linear" or "exponential". Invalid type: ', type);
|
|
}
|
|
}
|
|
});
|
|
/**
|
|
* Get the phase of the envelope at the specified time.
|
|
* @param {number} time
|
|
* @return {Tone.Envelope.Phase}
|
|
* @private
|
|
*/
|
|
Tone.Envelope.prototype._phaseAtTime = function (time) {
|
|
if (this._nextRelease > time) {
|
|
if (this._nextAttack <= time && this._nextDecay > time) {
|
|
return Tone.Envelope.Phase.Attack;
|
|
} else if (this._nextDecay <= time && this._nextSustain > time) {
|
|
return Tone.Envelope.Phase.Decay;
|
|
} else if (this._nextSustain <= time && this._nextRelease > time) {
|
|
return Tone.Envelope.Phase.Sustain;
|
|
} else {
|
|
return Tone.Envelope.Phase.Standby;
|
|
}
|
|
} else if (this._nextRelease < time && this._nextStandby > time) {
|
|
return Tone.Envelope.Phase.Release;
|
|
} else {
|
|
return Tone.Envelope.Phase.Standby;
|
|
}
|
|
};
|
|
/**
|
|
* https://github.com/jsantell/web-audio-automation-timeline
|
|
* MIT License, copyright (c) 2014 Jordan Santell
|
|
* @private
|
|
*/
|
|
Tone.Envelope.prototype._exponentialApproach = function (t0, v0, v1, timeConstant, t) {
|
|
return v1 + (v0 - v1) * Math.exp(-(t - t0) / timeConstant);
|
|
};
|
|
/**
|
|
* @private
|
|
*/
|
|
Tone.Envelope.prototype._linearInterpolate = function (t0, v0, t1, v1, t) {
|
|
return v0 + (v1 - v0) * ((t - t0) / (t1 - t0));
|
|
};
|
|
/**
|
|
* @private
|
|
*/
|
|
Tone.Envelope.prototype._exponentialInterpolate = function (t0, v0, t1, v1, t) {
|
|
return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0));
|
|
};
|
|
/**
|
|
* Get the envelopes value at the given time
|
|
* @param {number} time
|
|
* @param {number} velocity
|
|
* @return {number}
|
|
* @private
|
|
*/
|
|
Tone.Envelope.prototype._valueAtTime = function (time) {
|
|
var attack = this.toSeconds(this.attack);
|
|
var decay = this.toSeconds(this.decay);
|
|
var release = this.toSeconds(this.release);
|
|
switch (this._phaseAtTime(time)) {
|
|
case Tone.Envelope.Phase.Attack:
|
|
if (this._attackCurve === Tone.Envelope.Type.Linear) {
|
|
return this._linearInterpolate(this._nextAttack, this._minOutput, this._nextAttack + attack, this._peakValue, time);
|
|
} else {
|
|
return this._exponentialInterpolate(this._nextAttack, this._minOutput, this._nextAttack + attack, this._peakValue, time);
|
|
}
|
|
break;
|
|
case Tone.Envelope.Phase.Decay:
|
|
return this._exponentialApproach(this._nextDecay, this._peakValue, this.sustain * this._peakValue, decay * this._timeMult, time);
|
|
case Tone.Envelope.Phase.Release:
|
|
return this._exponentialApproach(this._nextRelease, this._peakValue, this._minOutput, release * this._timeMult, time);
|
|
case Tone.Envelope.Phase.Sustain:
|
|
return this.sustain * this._peakValue;
|
|
case Tone.Envelope.Phase.Standby:
|
|
return this._minOutput;
|
|
}
|
|
};
|
|
/**
|
|
* Trigger the attack/decay portion of the ADSR envelope.
|
|
* @param {Time} [time=now]
|
|
* @param {number} [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) {
|
|
//to seconds
|
|
time = this.toSeconds(time);
|
|
var attack = this.toSeconds(this.attack);
|
|
var decay = this.toSeconds(this.decay);
|
|
//get the phase and position
|
|
var valueAtTime = this._valueAtTime(time);
|
|
var attackPast = valueAtTime * attack;
|
|
//compute the timing
|
|
this._nextAttack = time - attackPast;
|
|
this._nextDecay = this._nextAttack + attack;
|
|
this._nextSustain = this._nextDecay + decay;
|
|
this._nextRelease = Infinity;
|
|
//get the values
|
|
this._peakValue = this.defaultArg(velocity, 1);
|
|
var scaledMax = this._peakValue;
|
|
var sustainVal = this.sustain * scaledMax;
|
|
//set the curve
|
|
this._sig.cancelScheduledValues(time);
|
|
this._sig.setValueAtTime(valueAtTime, time);
|
|
if (this._attackCurve === Tone.Envelope.Type.Linear) {
|
|
this._sig.linearRampToValueAtTime(scaledMax, this._nextDecay);
|
|
} else {
|
|
this._sig.exponentialRampToValueAtTime(scaledMax, this._nextDecay);
|
|
}
|
|
this._sig.setTargetAtTime(sustainVal, this._nextDecay, decay * this._timeMult);
|
|
return this;
|
|
};
|
|
/**
|
|
* Triggers the release of the envelope.
|
|
* @param {Time} [time=now]
|
|
* @returns {Tone.Envelope} this
|
|
* @example
|
|
* //trigger release immediately
|
|
* env.triggerRelease();
|
|
*/
|
|
Tone.Envelope.prototype.triggerRelease = function (time) {
|
|
time = this.toSeconds(time);
|
|
var phase = this._phaseAtTime(time);
|
|
var release = this.toSeconds(this.release);
|
|
//computer the value at the start of the next release
|
|
var valueAtTime = this._valueAtTime(time);
|
|
this._peakValue = valueAtTime;
|
|
this._nextRelease = time;
|
|
this._nextStandby = this._nextRelease + release;
|
|
//set the values
|
|
this._sig.cancelScheduledValues(this._nextRelease);
|
|
//if the phase is in the attack still, must reschedule the rest of the attack
|
|
if (phase === Tone.Envelope.Phase.Attack) {
|
|
this._sig.setCurrentValueNow();
|
|
if (this.attackCurve === Tone.Envelope.Type.Linear) {
|
|
this._sig.linearRampToValueAtTime(this._peakValue, this._nextRelease);
|
|
} else {
|
|
this._sig.exponentialRampToValueAtTime(this._peakValue, this._nextRelease);
|
|
}
|
|
} else {
|
|
this._sig.setValueAtTime(this._peakValue, this._nextRelease);
|
|
}
|
|
this._sig.setTargetAtTime(this._minOutput, this._nextRelease, release * this._timeMult);
|
|
return this;
|
|
};
|
|
/**
|
|
* Trigger the attack and release after a sustain time
|
|
* @param {Time} duration the duration of the note
|
|
* @param {Time} [time=now] the time of the attack
|
|
* @param {number} [velocity=1] the velocity of the note
|
|
* @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;
|
|
};
|
|
/**
|
|
* Borrows the connect method from Tone.Signal.
|
|
* @function
|
|
*/
|
|
Tone.Envelope.prototype.connect = Tone.Signal.prototype.connect;
|
|
/**
|
|
* disconnect and dispose
|
|
* @returns {Tone.Envelope} this
|
|
*/
|
|
Tone.Envelope.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._sig.dispose();
|
|
this._sig = null;
|
|
return this;
|
|
};
|
|
/**
|
|
* The phase of the envelope.
|
|
* @enum {string}
|
|
*/
|
|
Tone.Envelope.Phase = {
|
|
Attack: 'attack',
|
|
Decay: 'decay',
|
|
Sustain: 'sustain',
|
|
Release: 'release',
|
|
Standby: 'standby'
|
|
};
|
|
/**
|
|
* The phase of the envelope.
|
|
* @enum {string}
|
|
*/
|
|
Tone.Envelope.Type = {
|
|
Linear: 'linear',
|
|
Exponential: 'exponential'
|
|
};
|
|
return Tone.Envelope;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class An Envelope connected to a gain node which can be used as an amplitude 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(0.1, 0.2, 1, 0.8);
|
|
* var osc = new Tone.Oscillator();
|
|
* //or with an object
|
|
* osc.chain(ampEnv, Tone.Master);
|
|
*/
|
|
Tone.AmplitudeEnvelope = function () {
|
|
Tone.Envelope.apply(this, arguments);
|
|
/**
|
|
* the input node
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this.input = this.output = this.context.createGain();
|
|
this._sig.connect(this.output.gain);
|
|
};
|
|
Tone.extend(Tone.AmplitudeEnvelope, Tone.Envelope);
|
|
return Tone.AmplitudeEnvelope;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class A thin wrapper around the DynamicsCompressorNode. Compression reduces the
|
|
* volume of loud sounds or amplifies quiet sounds by narrowing or "compressing"
|
|
* an audio signal's dynamic range. [<a href="https://en.wikipedia.org/wiki/Dynamic_range_compression">Wikipedia</a>]
|
|
*
|
|
* @extends {Tone}
|
|
* @constructor
|
|
* @param {Decibels=} 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 = this.optionsObject(arguments, [
|
|
'threshold',
|
|
'ratio'
|
|
], Tone.Compressor.defaults);
|
|
/**
|
|
* the compressor node
|
|
* @type {DynamicsCompressorNode}
|
|
* @private
|
|
*/
|
|
this._compressor = this.input = this.output = this.context.createDynamicsCompressor();
|
|
/**
|
|
* the threshold vaue
|
|
* @type {Decibels}
|
|
* @signal
|
|
*/
|
|
this.threshold = this._compressor.threshold;
|
|
/**
|
|
* The attack parameter
|
|
* @type {Time}
|
|
* @signal
|
|
*/
|
|
this.attack = new Tone.Signal(this._compressor.attack, Tone.Type.Time);
|
|
/**
|
|
* The release parameter
|
|
* @type {Time}
|
|
* @signal
|
|
*/
|
|
this.release = new Tone.Signal(this._compressor.release, Tone.Type.Time);
|
|
/**
|
|
* The knee parameter
|
|
* @type {Decibels}
|
|
* @signal
|
|
*/
|
|
this.knee = this._compressor.knee;
|
|
/**
|
|
* The ratio value
|
|
* @type {Number}
|
|
* @signal
|
|
*/
|
|
this.ratio = this._compressor.ratio;
|
|
//set the defaults
|
|
this._readOnly([
|
|
'knee',
|
|
'release',
|
|
'attack',
|
|
'ratio',
|
|
'threshold'
|
|
]);
|
|
this.set(options);
|
|
};
|
|
Tone.extend(Tone.Compressor);
|
|
/**
|
|
* @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.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 = null;
|
|
this.ratio = null;
|
|
this.knee = null;
|
|
return this;
|
|
};
|
|
return Tone.Compressor;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Add a signal and a number or two signals. <br><br>
|
|
* input 0: augend. input 1: addend. <br><br>
|
|
* Add can be used in two ways, either constructed with a value,
|
|
* or constructed with no initial value and with signals connected
|
|
* to each of its two inputs.
|
|
*
|
|
* @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.call(this, 2, 0);
|
|
/**
|
|
* the summing node
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._sum = this.input[0] = this.input[1] = this.output = this.context.createGain();
|
|
/**
|
|
* @private
|
|
* @type {Tone.Signal}
|
|
*/
|
|
this._value = this.input[1] = new Tone.Signal(value);
|
|
this._value.connect(this._sum);
|
|
};
|
|
Tone.extend(Tone.Add, Tone.Signal);
|
|
/**
|
|
* dispose method
|
|
* @returns {Tone.Add} this
|
|
*/
|
|
Tone.Add.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._sum.disconnect();
|
|
this._sum = null;
|
|
this._value.dispose();
|
|
this._value = null;
|
|
return this;
|
|
};
|
|
return Tone.Add;
|
|
});
|
|
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 () {
|
|
/**
|
|
* 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.prototype.dispose.call(this);
|
|
this._multiply.dispose();
|
|
this._multiply = null;
|
|
return this;
|
|
};
|
|
return Tone.Negate;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Subtract a signal and a number or two signals.
|
|
* input 0 : minuend.
|
|
* input 1 : subtrahend
|
|
*
|
|
* @extends {Tone.Signal}
|
|
* @constructor
|
|
* @param {number=} value 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.
|
|
*/
|
|
Tone.Subtract = function (value) {
|
|
Tone.call(this, 2, 0);
|
|
/**
|
|
* the summing node
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._sum = this.input[0] = this.output = this.context.createGain();
|
|
/**
|
|
* 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._value = this.input[1] = new Tone.Signal(value);
|
|
this._value.chain(this._neg, this._sum);
|
|
};
|
|
Tone.extend(Tone.Subtract, Tone.Signal);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.SignalBase} this
|
|
*/
|
|
Tone.Subtract.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._neg.dispose();
|
|
this._neg = null;
|
|
this._sum.disconnect();
|
|
this._sum = null;
|
|
this._value.dispose();
|
|
this._value = null;
|
|
return this;
|
|
};
|
|
return Tone.Subtract;
|
|
});
|
|
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 () {
|
|
/**
|
|
* @type {Tone.WaveShaper}
|
|
* @private
|
|
*/
|
|
this._thresh = this.output = new Tone.WaveShaper(function (val) {
|
|
if (val <= 0) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
});
|
|
/**
|
|
* 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.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 EqualZero outputs 1 when the input is strictly greater than zero
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.SignalBase}
|
|
* @example
|
|
* var eq0 = new Tone.EqualZero();
|
|
* var sig = new Tone.Signal(0).connect(eq0);
|
|
* //the output of eq0 is 1.
|
|
*/
|
|
Tone.EqualZero = function () {
|
|
/**
|
|
* scale the incoming signal by a large factor
|
|
* @private
|
|
* @type {Tone.Multiply}
|
|
*/
|
|
this._scale = this.input = new Tone.Multiply(10000);
|
|
/**
|
|
* @type {Tone.WaveShaper}
|
|
* @private
|
|
*/
|
|
this._thresh = new Tone.WaveShaper(function (val) {
|
|
if (val === 0) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}, 128);
|
|
/**
|
|
* threshold the output so that it's 0 or 1
|
|
* @type {Tone.GreaterThanZero}
|
|
* @private
|
|
*/
|
|
this._gtz = this.output = new Tone.GreaterThanZero();
|
|
//connections
|
|
this._scale.chain(this._thresh, this._gtz);
|
|
};
|
|
Tone.extend(Tone.EqualZero, Tone.SignalBase);
|
|
/**
|
|
* dispose method
|
|
* @returns {Tone.EqualZero} this
|
|
*/
|
|
Tone.EqualZero.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._gtz.dispose();
|
|
this._gtz = null;
|
|
this._scale.dispose();
|
|
this._scale = null;
|
|
this._thresh.dispose();
|
|
this._thresh = null;
|
|
return this;
|
|
};
|
|
return Tone.EqualZero;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Output 1 if the signal is equal to the value, otherwise outputs 0.
|
|
* Can accept two signals if connected to inputs 0 and 1.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.SignalBase}
|
|
* @param {number} value the number to compare the incoming signal to
|
|
* @example
|
|
* var eq = new Tone.Equal(3);
|
|
* var sig = new Tone.Signal(3).connect(eq);
|
|
* //the output of eq is 1.
|
|
*/
|
|
Tone.Equal = function (value) {
|
|
Tone.call(this, 2, 0);
|
|
/**
|
|
* subtract the value from the incoming signal
|
|
*
|
|
* @type {Tone.Add}
|
|
* @private
|
|
*/
|
|
this._sub = this.input[0] = new Tone.Subtract(value);
|
|
/**
|
|
* @type {Tone.EqualZero}
|
|
* @private
|
|
*/
|
|
this._equals = this.output = new Tone.EqualZero();
|
|
this._sub.connect(this._equals);
|
|
this.input[1] = this._sub.input[1];
|
|
};
|
|
Tone.extend(Tone.Equal, Tone.SignalBase);
|
|
/**
|
|
* The value to compare to the incoming signal.
|
|
* @memberOf Tone.Equal#
|
|
* @type {number}
|
|
* @name value
|
|
*/
|
|
Object.defineProperty(Tone.Equal.prototype, 'value', {
|
|
get: function () {
|
|
return this._sub.value;
|
|
},
|
|
set: function (value) {
|
|
this._sub.value = value;
|
|
}
|
|
});
|
|
/**
|
|
* dispose method
|
|
* @returns {Tone.Equal} this
|
|
*/
|
|
Tone.Equal.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._equals.dispose();
|
|
this._equals = null;
|
|
this._sub.dispose();
|
|
this._sub = null;
|
|
return this;
|
|
};
|
|
return Tone.Equal;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Select between any number of inputs, sending the one
|
|
* selected by the gate signal to the output
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.SignalBase}
|
|
* @param {number} [sourceCount=2] the number of inputs the switch accepts
|
|
* @example
|
|
* var sel = new Tone.Select(2);
|
|
* var sigA = new Tone.Signal(10).connect(sel, 0, 0);
|
|
* var sigB = new Tone.Signal(20).connect(sel, 0, 1);
|
|
* sel.gate.value = 0;
|
|
* //sel outputs 10 (the value of sigA);
|
|
* sel.gate.value = 1;
|
|
* //sel outputs 20 (the value of sigB);
|
|
*/
|
|
Tone.Select = function (sourceCount) {
|
|
sourceCount = this.defaultArg(sourceCount, 2);
|
|
Tone.call(this, sourceCount, 1);
|
|
/**
|
|
* the control signal
|
|
* @type {Number}
|
|
* @signal
|
|
*/
|
|
this.gate = new Tone.Signal(0);
|
|
this._readOnly('gate');
|
|
//make all the inputs and connect them
|
|
for (var i = 0; i < sourceCount; i++) {
|
|
var switchGate = new SelectGate(i);
|
|
this.input[i] = switchGate;
|
|
this.gate.connect(switchGate.selecter);
|
|
switchGate.connect(this.output);
|
|
}
|
|
};
|
|
Tone.extend(Tone.Select, Tone.SignalBase);
|
|
/**
|
|
* open one of the inputs and close the other
|
|
* @param {number} which open one of the gates (closes the other)
|
|
* @param {Time=} time the time when the switch will open
|
|
* @returns {Tone.Select} this
|
|
* @example
|
|
* //open input 1 in a half second from now
|
|
* sel.select(1, "+0.5");
|
|
*/
|
|
Tone.Select.prototype.select = function (which, time) {
|
|
//make sure it's an integer
|
|
which = Math.floor(which);
|
|
this.gate.setValueAtTime(which, this.toSeconds(time));
|
|
return this;
|
|
};
|
|
/**
|
|
* dispose method
|
|
* @returns {Tone.Select} this
|
|
*/
|
|
Tone.Select.prototype.dispose = function () {
|
|
this._writable('gate');
|
|
this.gate.dispose();
|
|
this.gate = null;
|
|
for (var i = 0; i < this.input.length; i++) {
|
|
this.input[i].dispose();
|
|
this.input[i] = null;
|
|
}
|
|
Tone.prototype.dispose.call(this);
|
|
return this;
|
|
};
|
|
////////////START HELPER////////////
|
|
/**
|
|
* helper class for Tone.Select representing a single gate
|
|
* @constructor
|
|
* @extends {Tone}
|
|
* @private
|
|
*/
|
|
var SelectGate = function (num) {
|
|
/**
|
|
* the selector
|
|
* @type {Tone.Equal}
|
|
*/
|
|
this.selecter = new Tone.Equal(num);
|
|
/**
|
|
* the gate
|
|
* @type {GainNode}
|
|
*/
|
|
this.gate = this.input = this.output = this.context.createGain();
|
|
//connect the selecter to the gate gain
|
|
this.selecter.connect(this.gate.gain);
|
|
};
|
|
Tone.extend(SelectGate);
|
|
/**
|
|
* clean up
|
|
* @private
|
|
*/
|
|
SelectGate.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this.selecter.dispose();
|
|
this.gate.disconnect();
|
|
this.selecter = null;
|
|
this.gate = null;
|
|
};
|
|
////////////END HELPER////////////
|
|
//return Tone.Select
|
|
return Tone.Select;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class IfThenElse has three inputs. When the first input (if) is true (i.e. === 1),
|
|
* then it will pass the second input (then) through to the output, otherwise,
|
|
* if it's not true (i.e. === 0) then it will pass the third input (else)
|
|
* through to the output.
|
|
*
|
|
* @extends {Tone.SignalBase}
|
|
* @constructor
|
|
* @example
|
|
* var ifThenElse = new Tone.IfThenElse();
|
|
* var ifSignal = new Tone.Signal(1).connect(ifThenElse, 0, 0);
|
|
* var thenSignal = new Tone.PWMOscillator().connect(ifThenElse, 0, 1);
|
|
* var elseSignal = new Tone.PulseOscillator().connect(ifThenElse, 0, 2);
|
|
* //ifThenElse outputs thenSignal
|
|
* signal.value = 0;
|
|
* //now ifThenElse outputs elseSignal
|
|
*/
|
|
Tone.IfThenElse = function () {
|
|
Tone.call(this, 3, 0);
|
|
/**
|
|
* the selector node which is responsible for the routing
|
|
* @type {Tone.Select}
|
|
* @private
|
|
*/
|
|
this._selector = this.output = new Tone.Select(2);
|
|
//the input mapping
|
|
this.if = this.input[0] = this._selector.gate;
|
|
this.then = this.input[1] = this._selector.input[1];
|
|
this.else = this.input[2] = this._selector.input[0];
|
|
};
|
|
Tone.extend(Tone.IfThenElse, Tone.SignalBase);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.IfThenElse} this
|
|
*/
|
|
Tone.IfThenElse.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._selector.dispose();
|
|
this._selector = null;
|
|
this.if = null;
|
|
this.then = null;
|
|
this.else = null;
|
|
return this;
|
|
};
|
|
return Tone.IfThenElse;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class OR the inputs together. True if at least one of the inputs is true.
|
|
*
|
|
* @extends {Tone.SignalBase}
|
|
* @constructor
|
|
* @param {number} inputCount the input count
|
|
* @example
|
|
* var or = new Tone.OR(2);
|
|
* var sigA = new Tone.Signal(0)connect(or, 0, 0);
|
|
* var sigB = new Tone.Signal(1)connect(or, 0, 1);
|
|
* //output of or is 1 because at least
|
|
* //one of the inputs is equal to 1.
|
|
*/
|
|
Tone.OR = function (inputCount) {
|
|
inputCount = this.defaultArg(inputCount, 2);
|
|
Tone.call(this, inputCount, 0);
|
|
/**
|
|
* a private summing node
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._sum = this.context.createGain();
|
|
/**
|
|
* @type {Tone.Equal}
|
|
* @private
|
|
*/
|
|
this._gtz = this.output = new Tone.GreaterThanZero();
|
|
//make each of the inputs an alias
|
|
for (var i = 0; i < inputCount; i++) {
|
|
this.input[i] = this._sum;
|
|
}
|
|
this._sum.connect(this._gtz);
|
|
};
|
|
Tone.extend(Tone.OR, Tone.SignalBase);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.OR} this
|
|
*/
|
|
Tone.OR.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._gtz.dispose();
|
|
this._gtz = null;
|
|
this._sum.disconnect();
|
|
this._sum = null;
|
|
return this;
|
|
};
|
|
return Tone.OR;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class and returns 1 when all the inputs are equal to 1
|
|
*
|
|
* @extends {Tone.SignalBase}
|
|
* @constructor
|
|
* @param {number} [inputCount=2] the number of inputs. NOTE: all inputs are
|
|
* connected to the single AND input node
|
|
* @example
|
|
* var and = new Tone.AND(2);
|
|
* var sigA = new Tone.Signal(0).connect(and, 0, 0);
|
|
* var sigB = new Tone.Signal(1).connect(and, 0, 1);
|
|
* //the output of and is 0.
|
|
*/
|
|
Tone.AND = function (inputCount) {
|
|
inputCount = this.defaultArg(inputCount, 2);
|
|
Tone.call(this, inputCount, 0);
|
|
/**
|
|
* @type {Tone.Equal}
|
|
* @private
|
|
*/
|
|
this._equals = this.output = new Tone.Equal(inputCount);
|
|
//make each of the inputs an alias
|
|
for (var i = 0; i < inputCount; i++) {
|
|
this.input[i] = this._equals;
|
|
}
|
|
};
|
|
Tone.extend(Tone.AND, Tone.SignalBase);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.AND} this
|
|
*/
|
|
Tone.AND.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._equals.dispose();
|
|
this._equals = null;
|
|
return this;
|
|
};
|
|
return Tone.AND;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Just an alias for EqualZero. but has the same effect as a NOT operator.
|
|
* Outputs 1 when input equals 0.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.SignalBase}
|
|
* @example
|
|
* var not = new Tone.NOT();
|
|
* var sig = new Tone.Signal(1).connect(not);
|
|
* //output of not equals 0.
|
|
* sig.value = 0;
|
|
* //output of not equals 1.
|
|
*/
|
|
Tone.NOT = Tone.EqualZero;
|
|
return Tone.NOT;
|
|
});
|
|
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.call(this, 2, 0);
|
|
/**
|
|
* subtract the amount from the incoming signal
|
|
* @type {Tone.Subtract}
|
|
* @private
|
|
*/
|
|
this._value = this.input[0] = new Tone.Subtract(value);
|
|
this.input[1] = this._value.input[1];
|
|
/**
|
|
* compare that amount to zero
|
|
* @type {Tone.GreaterThanZero}
|
|
* @private
|
|
*/
|
|
this._gtz = this.output = new Tone.GreaterThanZero();
|
|
//connect
|
|
this._value.connect(this._gtz);
|
|
};
|
|
Tone.extend(Tone.GreaterThan, Tone.Signal);
|
|
/**
|
|
* dispose method
|
|
* @returns {Tone.GreaterThan} this
|
|
*/
|
|
Tone.GreaterThan.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._value.dispose();
|
|
this._value = null;
|
|
this._gtz.dispose();
|
|
this._gtz = null;
|
|
return this;
|
|
};
|
|
return Tone.GreaterThan;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Output 1 if the signal is less than the value, otherwise outputs 0.
|
|
* Can compare two signals or a signal and a number. <br><br>
|
|
* input 0: left hand side of comparison.<br><br>
|
|
* input 1: right hand side of comparison.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Signal}
|
|
* @param {number} [value=0] the value to compare to the incoming signal
|
|
* @example
|
|
* var lt = new Tone.LessThan(2);
|
|
* var sig = new Tone.Signal(-1).connect(lt);
|
|
* //lt outputs 1 because sig < 2
|
|
*/
|
|
Tone.LessThan = function (value) {
|
|
Tone.call(this, 2, 0);
|
|
/**
|
|
* negate the incoming signal
|
|
* @type {Tone.Negate}
|
|
* @private
|
|
*/
|
|
this._neg = this.input[0] = new Tone.Negate();
|
|
/**
|
|
* input < value === -input > -value
|
|
* @type {Tone.GreaterThan}
|
|
* @private
|
|
*/
|
|
this._gt = this.output = new Tone.GreaterThan();
|
|
/**
|
|
* negate the signal coming from the second input
|
|
* @private
|
|
* @type {Tone.Negate}
|
|
*/
|
|
this._rhNeg = new Tone.Negate();
|
|
/**
|
|
* the node where the value is set
|
|
* @private
|
|
* @type {Tone.Signal}
|
|
*/
|
|
this._value = this.input[1] = new Tone.Signal(value);
|
|
//connect
|
|
this._neg.connect(this._gt);
|
|
this._value.connect(this._rhNeg);
|
|
this._rhNeg.connect(this._gt, 0, 1);
|
|
};
|
|
Tone.extend(Tone.LessThan, Tone.Signal);
|
|
/**
|
|
* dispose method
|
|
* @returns {Tone.LessThan} this
|
|
*/
|
|
Tone.LessThan.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._neg.dispose();
|
|
this._neg = null;
|
|
this._gt.dispose();
|
|
this._gt = null;
|
|
this._rhNeg.dispose();
|
|
this._rhNeg = null;
|
|
this._value.dispose();
|
|
this._value = null;
|
|
return this;
|
|
};
|
|
return Tone.LessThan;
|
|
});
|
|
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.call(this, 1, 0);
|
|
/**
|
|
* @type {Tone.LessThan}
|
|
* @private
|
|
*/
|
|
this._ltz = new Tone.LessThan(0);
|
|
/**
|
|
* @type {Tone.Select}
|
|
* @private
|
|
*/
|
|
this._switch = this.output = new Tone.Select(2);
|
|
/**
|
|
* @type {Tone.Negate}
|
|
* @private
|
|
*/
|
|
this._negate = new Tone.Negate();
|
|
//two signal paths, positive and negative
|
|
this.input.connect(this._switch, 0, 0);
|
|
this.input.connect(this._negate);
|
|
this._negate.connect(this._switch, 0, 1);
|
|
//the control signal
|
|
this.input.chain(this._ltz, this._switch.gate);
|
|
};
|
|
Tone.extend(Tone.Abs, Tone.SignalBase);
|
|
/**
|
|
* dispose method
|
|
* @returns {Tone.Abs} this
|
|
*/
|
|
Tone.Abs.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._switch.dispose();
|
|
this._switch = null;
|
|
this._ltz.dispose();
|
|
this._ltz = null;
|
|
this._negate.dispose();
|
|
this._negate = null;
|
|
return this;
|
|
};
|
|
return Tone.Abs;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class outputs the greater of two signals. If a number is provided in the constructor
|
|
* it will use that instead of the signal.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Signal}
|
|
* @param {number=} max max value if provided. if not provided, it will use the
|
|
* signal value from input 1.
|
|
* @example
|
|
* var max = new Tone.Max(2);
|
|
* var sig = new Tone.Signal(3).connect(max);
|
|
* //max outputs 3
|
|
* sig.value = 1;
|
|
* //max outputs 2
|
|
*/
|
|
Tone.Max = function (max) {
|
|
Tone.call(this, 2, 0);
|
|
this.input[0] = this.context.createGain();
|
|
/**
|
|
* the max signal
|
|
* @type {Tone.Signal}
|
|
* @private
|
|
*/
|
|
this._value = this.input[1] = new Tone.Signal(max);
|
|
/**
|
|
* @type {Tone.Select}
|
|
* @private
|
|
*/
|
|
this._ifThenElse = this.output = new Tone.IfThenElse();
|
|
/**
|
|
* @type {Tone.Select}
|
|
* @private
|
|
*/
|
|
this._gt = new Tone.GreaterThan();
|
|
//connections
|
|
this.input[0].chain(this._gt, this._ifThenElse.if);
|
|
this.input[0].connect(this._ifThenElse.then);
|
|
this._value.connect(this._ifThenElse.else);
|
|
this._value.connect(this._gt, 0, 1);
|
|
};
|
|
Tone.extend(Tone.Max, Tone.Signal);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Max} this
|
|
*/
|
|
Tone.Max.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._value.dispose();
|
|
this._ifThenElse.dispose();
|
|
this._gt.dispose();
|
|
this._value = null;
|
|
this._ifThenElse = null;
|
|
this._gt = null;
|
|
return this;
|
|
};
|
|
return Tone.Max;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Outputs the lesser of two signals. If a number is given
|
|
* in the constructor, it will use a signal and a number.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Signal}
|
|
* @param {number} min the minimum to compare to the incoming signal
|
|
* @example
|
|
* var min = new Tone.Min(2);
|
|
* var sig = new Tone.Signal(3).connect(min);
|
|
* //min outputs 2
|
|
* sig.value = 1;
|
|
* //min outputs 1
|
|
*/
|
|
Tone.Min = function (min) {
|
|
Tone.call(this, 2, 0);
|
|
this.input[0] = this.context.createGain();
|
|
/**
|
|
* @type {Tone.Select}
|
|
* @private
|
|
*/
|
|
this._ifThenElse = this.output = new Tone.IfThenElse();
|
|
/**
|
|
* @type {Tone.Select}
|
|
* @private
|
|
*/
|
|
this._lt = new Tone.LessThan();
|
|
/**
|
|
* the min signal
|
|
* @type {Tone.Signal}
|
|
* @private
|
|
*/
|
|
this._value = this.input[1] = new Tone.Signal(min);
|
|
//connections
|
|
this.input[0].chain(this._lt, this._ifThenElse.if);
|
|
this.input[0].connect(this._ifThenElse.then);
|
|
this._value.connect(this._ifThenElse.else);
|
|
this._value.connect(this._lt, 0, 1);
|
|
};
|
|
Tone.extend(Tone.Min, Tone.Signal);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Min} this
|
|
*/
|
|
Tone.Min.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._value.dispose();
|
|
this._ifThenElse.dispose();
|
|
this._lt.dispose();
|
|
this._value = null;
|
|
this._ifThenElse = null;
|
|
this._lt = null;
|
|
return this;
|
|
};
|
|
return Tone.Min;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Signal-rate modulo operator. Only works in audio range [-1, 1] and for modulus
|
|
* values less than 1.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.SignalBase}
|
|
* @param {number} modulus the modulus to apply
|
|
* @example
|
|
* var mod = new Tone.Modulo(0.2)
|
|
* var sig = new Tone.Signal(0.5).connect(mod);
|
|
* //mod outputs 0.1
|
|
*/
|
|
Tone.Modulo = function (modulus) {
|
|
Tone.call(this, 1, 1);
|
|
/**
|
|
* A waveshaper gets the integer multiple of
|
|
* the input signal and the modulus.
|
|
* @private
|
|
* @type {Tone.WaveShaper}
|
|
*/
|
|
this._shaper = new Tone.WaveShaper(Math.pow(2, 16));
|
|
/**
|
|
* the integer multiple is multiplied by the modulus
|
|
* @type {Tone.Multiply}
|
|
* @private
|
|
*/
|
|
this._multiply = new Tone.Multiply();
|
|
/**
|
|
* and subtracted from the input signal
|
|
* @type {Tone.Subtract}
|
|
* @private
|
|
*/
|
|
this._subtract = this.output = new Tone.Subtract();
|
|
/**
|
|
* the modulus signal
|
|
* @type {Tone.Signal}
|
|
* @private
|
|
*/
|
|
this._modSignal = new Tone.Signal(modulus);
|
|
//connections
|
|
this.input.fan(this._shaper, this._subtract);
|
|
this._modSignal.connect(this._multiply, 0, 0);
|
|
this._shaper.connect(this._multiply, 0, 1);
|
|
this._multiply.connect(this._subtract, 0, 1);
|
|
this._setWaveShaper(modulus);
|
|
};
|
|
Tone.extend(Tone.Modulo, Tone.SignalBase);
|
|
/**
|
|
* @param {number} mod the modulus to apply
|
|
* @private
|
|
*/
|
|
Tone.Modulo.prototype._setWaveShaper = function (mod) {
|
|
this._shaper.setMap(function (val) {
|
|
var multiple = Math.floor((val + 0.0001) / mod);
|
|
return multiple;
|
|
});
|
|
};
|
|
/**
|
|
* The modulus value.
|
|
* @memberOf Tone.Modulo#
|
|
* @type {number}
|
|
* @name value
|
|
*/
|
|
Object.defineProperty(Tone.Modulo.prototype, 'value', {
|
|
get: function () {
|
|
return this._modSignal.value;
|
|
},
|
|
set: function (mod) {
|
|
this._modSignal.value = mod;
|
|
this._setWaveShaper(mod);
|
|
}
|
|
});
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Modulo} this
|
|
*/
|
|
Tone.Modulo.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._shaper.dispose();
|
|
this._shaper = null;
|
|
this._multiply.dispose();
|
|
this._multiply = null;
|
|
this._subtract.dispose();
|
|
this._subtract = null;
|
|
this._modSignal.dispose();
|
|
this._modSignal = null;
|
|
return this;
|
|
};
|
|
return Tone.Modulo;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class evaluate an expression at audio rate.
|
|
* parsing code modified from https://code.google.com/p/tapdigit/
|
|
* Copyright 2011 2012 Ariya Hidayat, New BSD License
|
|
*
|
|
* @extends {Tone.SignalBase}
|
|
* @constructor
|
|
* @param {string} expr the expression to generate
|
|
* @example
|
|
* //adds the signals from input 0 and input 1.
|
|
* var expr = new Tone.Expr("$0 + $1");
|
|
*/
|
|
Tone.Expr = function () {
|
|
var expr = this._replacements(Array.prototype.slice.call(arguments));
|
|
var inputCount = this._parseInputs(expr);
|
|
/**
|
|
* hold onto all of the nodes for disposal
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this._nodes = [];
|
|
/**
|
|
* The inputs. The length is determined by the expression.
|
|
* @type {Array}
|
|
*/
|
|
this.input = new Array(inputCount);
|
|
//create a gain for each input
|
|
for (var i = 0; i < inputCount; i++) {
|
|
this.input[i] = this.context.createGain();
|
|
}
|
|
//parse the syntax tree
|
|
var tree = this._parseTree(expr);
|
|
//evaluate the results
|
|
var result;
|
|
try {
|
|
result = this._eval(tree);
|
|
} catch (e) {
|
|
this._disposeNodes();
|
|
throw new Error('Could evaluate expression: ' + expr);
|
|
}
|
|
/**
|
|
* The output node is the result of the expression
|
|
* @type {Tone}
|
|
*/
|
|
this.output = result;
|
|
};
|
|
Tone.extend(Tone.Expr, Tone.SignalBase);
|
|
//some helpers to cut down the amount of code
|
|
function applyBinary(Constructor, args, self) {
|
|
var op = new Constructor();
|
|
self._eval(args[0]).connect(op, 0, 0);
|
|
self._eval(args[1]).connect(op, 0, 1);
|
|
return op;
|
|
}
|
|
function applyUnary(Constructor, args, self) {
|
|
var op = new Constructor();
|
|
self._eval(args[0]).connect(op, 0, 0);
|
|
return op;
|
|
}
|
|
function getNumber(arg) {
|
|
return arg ? parseFloat(arg) : undefined;
|
|
}
|
|
function literalNumber(arg) {
|
|
return arg && arg.args ? parseFloat(arg.args) : undefined;
|
|
}
|
|
/*
|
|
* the Expressions that Tone.Expr can parse.
|
|
*
|
|
* each expression belongs to a group and contains a regexp
|
|
* for selecting the operator as well as that operators method
|
|
*
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
Tone.Expr._Expressions = {
|
|
//values
|
|
'value': {
|
|
'signal': {
|
|
regexp: /^\d+\.\d+|^\d+/,
|
|
method: function (arg) {
|
|
var sig = new Tone.Signal(getNumber(arg));
|
|
return sig;
|
|
}
|
|
},
|
|
'input': {
|
|
regexp: /^\$\d/,
|
|
method: function (arg, self) {
|
|
return self.input[getNumber(arg.substr(1))];
|
|
}
|
|
}
|
|
},
|
|
//syntactic glue
|
|
'glue': {
|
|
'(': { regexp: /^\(/ },
|
|
')': { regexp: /^\)/ },
|
|
',': { regexp: /^,/ }
|
|
},
|
|
//functions
|
|
'func': {
|
|
'abs': {
|
|
regexp: /^abs/,
|
|
method: applyUnary.bind(this, Tone.Abs)
|
|
},
|
|
'min': {
|
|
regexp: /^min/,
|
|
method: applyBinary.bind(this, Tone.Min)
|
|
},
|
|
'max': {
|
|
regexp: /^max/,
|
|
method: applyBinary.bind(this, Tone.Max)
|
|
},
|
|
'if': {
|
|
regexp: /^if/,
|
|
method: function (args, self) {
|
|
var op = new Tone.IfThenElse();
|
|
self._eval(args[0]).connect(op.if);
|
|
self._eval(args[1]).connect(op.then);
|
|
self._eval(args[2]).connect(op.else);
|
|
return op;
|
|
}
|
|
},
|
|
'gt0': {
|
|
regexp: /^gt0/,
|
|
method: applyUnary.bind(this, Tone.GreaterThanZero)
|
|
},
|
|
'eq0': {
|
|
regexp: /^eq0/,
|
|
method: applyUnary.bind(this, Tone.EqualZero)
|
|
},
|
|
'mod': {
|
|
regexp: /^mod/,
|
|
method: function (args, self) {
|
|
var modulus = literalNumber(args[1]);
|
|
var op = new Tone.Modulo(modulus);
|
|
self._eval(args[0]).connect(op);
|
|
return op;
|
|
}
|
|
},
|
|
'pow': {
|
|
regexp: /^pow/,
|
|
method: function (args, self) {
|
|
var exp = literalNumber(args[1]);
|
|
var op = new Tone.Pow(exp);
|
|
self._eval(args[0]).connect(op);
|
|
return op;
|
|
}
|
|
}
|
|
},
|
|
//binary expressions
|
|
'binary': {
|
|
'+': {
|
|
regexp: /^\+/,
|
|
precedence: 1,
|
|
method: applyBinary.bind(this, Tone.Add)
|
|
},
|
|
'-': {
|
|
regexp: /^\-/,
|
|
precedence: 1,
|
|
method: function (args, self) {
|
|
//both unary and binary op
|
|
if (args.length === 1) {
|
|
return applyUnary(Tone.Negate, args, self);
|
|
} else {
|
|
return applyBinary(Tone.Subtract, args, self);
|
|
}
|
|
}
|
|
},
|
|
'*': {
|
|
regexp: /^\*/,
|
|
precedence: 0,
|
|
method: applyBinary.bind(this, Tone.Multiply)
|
|
},
|
|
'>': {
|
|
regexp: /^\>/,
|
|
precedence: 2,
|
|
method: applyBinary.bind(this, Tone.GreaterThan)
|
|
},
|
|
'<': {
|
|
regexp: /^</,
|
|
precedence: 2,
|
|
method: applyBinary.bind(this, Tone.LessThan)
|
|
},
|
|
'==': {
|
|
regexp: /^==/,
|
|
precedence: 3,
|
|
method: applyBinary.bind(this, Tone.Equal)
|
|
},
|
|
'&&': {
|
|
regexp: /^&&/,
|
|
precedence: 4,
|
|
method: applyBinary.bind(this, Tone.AND)
|
|
},
|
|
'||': {
|
|
regexp: /^\|\|/,
|
|
precedence: 5,
|
|
method: applyBinary.bind(this, Tone.OR)
|
|
}
|
|
},
|
|
//unary expressions
|
|
'unary': {
|
|
'-': {
|
|
regexp: /^\-/,
|
|
method: applyUnary.bind(this, Tone.Negate)
|
|
},
|
|
'!': {
|
|
regexp: /^\!/,
|
|
method: applyUnary.bind(this, Tone.NOT)
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* @param {string} expr the expression string
|
|
* @return {number} the input count
|
|
* @private
|
|
*/
|
|
Tone.Expr.prototype._parseInputs = function (expr) {
|
|
var inputArray = expr.match(/\$\d/g);
|
|
var inputMax = 0;
|
|
if (inputArray !== null) {
|
|
for (var i = 0; i < inputArray.length; i++) {
|
|
var inputNum = parseInt(inputArray[i].substr(1)) + 1;
|
|
inputMax = Math.max(inputMax, inputNum);
|
|
}
|
|
}
|
|
return inputMax;
|
|
};
|
|
/**
|
|
* @param {Array} args an array of arguments
|
|
* @return {string} the results of the replacements being replaced
|
|
* @private
|
|
*/
|
|
Tone.Expr.prototype._replacements = function (args) {
|
|
var expr = args.shift();
|
|
for (var i = 0; i < args.length; i++) {
|
|
expr = expr.replace(/\%/i, args[i]);
|
|
}
|
|
return expr;
|
|
};
|
|
/**
|
|
* tokenize the expression based on the Expressions object
|
|
* @param {string} expr
|
|
* @return {Object} returns two methods on the tokenized list, next and peek
|
|
* @private
|
|
*/
|
|
Tone.Expr.prototype._tokenize = function (expr) {
|
|
var position = -1;
|
|
var tokens = [];
|
|
while (expr.length > 0) {
|
|
expr = expr.trim();
|
|
var token = getNextToken(expr);
|
|
tokens.push(token);
|
|
expr = expr.substr(token.value.length);
|
|
}
|
|
function getNextToken(expr) {
|
|
for (var type in Tone.Expr._Expressions) {
|
|
var group = Tone.Expr._Expressions[type];
|
|
for (var opName in group) {
|
|
var op = group[opName];
|
|
var reg = op.regexp;
|
|
var match = expr.match(reg);
|
|
if (match !== null) {
|
|
return {
|
|
type: type,
|
|
value: match[0],
|
|
method: op.method
|
|
};
|
|
}
|
|
}
|
|
}
|
|
throw new SyntaxError('Unexpected token ' + expr);
|
|
}
|
|
return {
|
|
next: function () {
|
|
return tokens[++position];
|
|
},
|
|
peek: function () {
|
|
return tokens[position + 1];
|
|
}
|
|
};
|
|
};
|
|
/**
|
|
* recursively parse the string expression into a syntax tree
|
|
*
|
|
* @param {string} expr
|
|
* @return {Object}
|
|
* @private
|
|
*/
|
|
Tone.Expr.prototype._parseTree = function (expr) {
|
|
var lexer = this._tokenize(expr);
|
|
var isUndef = this.isUndef.bind(this);
|
|
function matchSyntax(token, syn) {
|
|
return !isUndef(token) && token.type === 'glue' && token.value === syn;
|
|
}
|
|
function matchGroup(token, groupName, prec) {
|
|
var ret = false;
|
|
var group = Tone.Expr._Expressions[groupName];
|
|
if (!isUndef(token)) {
|
|
for (var opName in group) {
|
|
var op = group[opName];
|
|
if (op.regexp.test(token.value)) {
|
|
if (!isUndef(prec)) {
|
|
if (op.precedence === prec) {
|
|
return true;
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
function parseExpression(precedence) {
|
|
if (isUndef(precedence)) {
|
|
precedence = 5;
|
|
}
|
|
var expr;
|
|
if (precedence < 0) {
|
|
expr = parseUnary();
|
|
} else {
|
|
expr = parseExpression(precedence - 1);
|
|
}
|
|
var token = lexer.peek();
|
|
while (matchGroup(token, 'binary', precedence)) {
|
|
token = lexer.next();
|
|
expr = {
|
|
operator: token.value,
|
|
method: token.method,
|
|
args: [
|
|
expr,
|
|
parseExpression(precedence)
|
|
]
|
|
};
|
|
token = lexer.peek();
|
|
}
|
|
return expr;
|
|
}
|
|
function parseUnary() {
|
|
var token, expr;
|
|
token = lexer.peek();
|
|
if (matchGroup(token, 'unary')) {
|
|
token = lexer.next();
|
|
expr = parseUnary();
|
|
return {
|
|
operator: token.value,
|
|
method: token.method,
|
|
args: [expr]
|
|
};
|
|
}
|
|
return parsePrimary();
|
|
}
|
|
function parsePrimary() {
|
|
var token, expr;
|
|
token = lexer.peek();
|
|
if (isUndef(token)) {
|
|
throw new SyntaxError('Unexpected termination of expression');
|
|
}
|
|
if (token.type === 'func') {
|
|
token = lexer.next();
|
|
return parseFunctionCall(token);
|
|
}
|
|
if (token.type === 'value') {
|
|
token = lexer.next();
|
|
return {
|
|
method: token.method,
|
|
args: token.value
|
|
};
|
|
}
|
|
if (matchSyntax(token, '(')) {
|
|
lexer.next();
|
|
expr = parseExpression();
|
|
token = lexer.next();
|
|
if (!matchSyntax(token, ')')) {
|
|
throw new SyntaxError('Expected )');
|
|
}
|
|
return expr;
|
|
}
|
|
throw new SyntaxError('Parse error, cannot process token ' + token.value);
|
|
}
|
|
function parseFunctionCall(func) {
|
|
var token, args = [];
|
|
token = lexer.next();
|
|
if (!matchSyntax(token, '(')) {
|
|
throw new SyntaxError('Expected ( in a function call "' + func.value + '"');
|
|
}
|
|
token = lexer.peek();
|
|
if (!matchSyntax(token, ')')) {
|
|
args = parseArgumentList();
|
|
}
|
|
token = lexer.next();
|
|
if (!matchSyntax(token, ')')) {
|
|
throw new SyntaxError('Expected ) in a function call "' + func.value + '"');
|
|
}
|
|
return {
|
|
method: func.method,
|
|
args: args,
|
|
name: name
|
|
};
|
|
}
|
|
function parseArgumentList() {
|
|
var token, expr, args = [];
|
|
while (true) {
|
|
expr = parseExpression();
|
|
if (isUndef(expr)) {
|
|
// TODO maybe throw exception?
|
|
break;
|
|
}
|
|
args.push(expr);
|
|
token = lexer.peek();
|
|
if (!matchSyntax(token, ',')) {
|
|
break;
|
|
}
|
|
lexer.next();
|
|
}
|
|
return args;
|
|
}
|
|
return parseExpression();
|
|
};
|
|
/**
|
|
* recursively evaluate the expression tree
|
|
* @param {Object} tree
|
|
* @return {AudioNode} the resulting audio node from the expression
|
|
* @private
|
|
*/
|
|
Tone.Expr.prototype._eval = function (tree) {
|
|
if (!this.isUndef(tree)) {
|
|
var node = tree.method(tree.args, this);
|
|
this._nodes.push(node);
|
|
return node;
|
|
}
|
|
};
|
|
/**
|
|
* dispose all the nodes
|
|
* @private
|
|
*/
|
|
Tone.Expr.prototype._disposeNodes = function () {
|
|
for (var i = 0; i < this._nodes.length; i++) {
|
|
var node = this._nodes[i];
|
|
if (this.isFunction(node.dispose)) {
|
|
node.dispose();
|
|
} else if (this.isFunction(node.disconnect)) {
|
|
node.disconnect();
|
|
}
|
|
node = null;
|
|
this._nodes[i] = null;
|
|
}
|
|
this._nodes = null;
|
|
};
|
|
/**
|
|
* clean up
|
|
*/
|
|
Tone.Expr.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._disposeNodes();
|
|
};
|
|
return Tone.Expr;
|
|
});
|
|
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 () {
|
|
/**
|
|
* @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 this.equalPowerScale(val);
|
|
}
|
|
}.bind(this), 4096);
|
|
};
|
|
Tone.extend(Tone.EqualPowerGain, Tone.SignalBase);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.EqualPowerGain} this
|
|
*/
|
|
Tone.EqualPowerGain.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._eqPower.dispose();
|
|
this._eqPower = null;
|
|
return this;
|
|
};
|
|
return Tone.EqualPowerGain;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Equal power fading control values:<br>
|
|
* 0 = 100% input 0<br>
|
|
* 1 = 100% input 1<br>
|
|
*
|
|
* @constructor
|
|
* @extends {Tone}
|
|
* @param {number} [initialFade=0.5]
|
|
* @example
|
|
* var crossFade = new Tone.CrossFade(0.5);
|
|
* effectA.connect(crossFade, 0, 0);
|
|
* 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.call(this, 2, 1);
|
|
/**
|
|
* the first input. input "a".
|
|
* @type {GainNode}
|
|
*/
|
|
this.a = this.input[0] = this.context.createGain();
|
|
/**
|
|
* the second input. input "b"
|
|
* @type {GainNode}
|
|
*/
|
|
this.b = this.input[1] = this.context.createGain();
|
|
/**
|
|
* 0 is 100% signal `a` (input 0) and 1 is 100% signal `b` (input 1).
|
|
* Values between 0-1.
|
|
*
|
|
* @type {NormalRange}
|
|
* @signal
|
|
*/
|
|
this.fade = new Tone.Signal(this.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._invert = new Tone.Expr('1 - $0');
|
|
//connections
|
|
this.a.connect(this.output);
|
|
this.b.connect(this.output);
|
|
this.fade.chain(this._equalPowerB, this.b.gain);
|
|
this.fade.chain(this._invert, this._equalPowerA, this.a.gain);
|
|
this._readOnly('fade');
|
|
};
|
|
Tone.extend(Tone.CrossFade);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.CrossFade} this
|
|
*/
|
|
Tone.CrossFade.prototype.dispose = function () {
|
|
Tone.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.a.disconnect();
|
|
this.a = null;
|
|
this.b.disconnect();
|
|
this.b = null;
|
|
return this;
|
|
};
|
|
return Tone.CrossFade;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Filter object which allows for all of the same native methods
|
|
* as the BiquadFilter (with AudioParams implemented as Tone.Signals)
|
|
* but adds the ability to set the filter rolloff at -12 (default),
|
|
* -24 and -48.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone}
|
|
* @param {number|Object} [freq=350] the frequency
|
|
* @param {string} [type=lowpass] the type of filter
|
|
* @param {number} [rolloff=-12] the rolloff which is the drop per octave.
|
|
* 3 choices: -12, -24, and -48
|
|
* @example
|
|
* var filter = new Tone.Filter(200, "highpass");
|
|
*/
|
|
Tone.Filter = function () {
|
|
Tone.call(this);
|
|
var options = this.optionsObject(arguments, [
|
|
'frequency',
|
|
'type',
|
|
'rolloff'
|
|
], Tone.Filter.defaults);
|
|
/**
|
|
* the filter(s)
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this._filters = [];
|
|
/**
|
|
* the 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 {Gain}
|
|
* @signal
|
|
*/
|
|
this.gain = new Tone.Signal({
|
|
'value': options.gain,
|
|
'units': Tone.Type.Decibels,
|
|
'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);
|
|
/**
|
|
* 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 does not have filter 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, and -48.
|
|
* @memberOf Tone.Filter#
|
|
* @type {number}
|
|
* @name rolloff
|
|
*/
|
|
Object.defineProperty(Tone.Filter.prototype, 'rolloff', {
|
|
get: function () {
|
|
return this._rolloff;
|
|
},
|
|
set: function (rolloff) {
|
|
var possibilities = [
|
|
-12,
|
|
-24,
|
|
-48
|
|
];
|
|
var cascadingCount = possibilities.indexOf(rolloff);
|
|
//check the rolloff is valid
|
|
if (cascadingCount === -1) {
|
|
throw new RangeError('Filter rolloff can only be -12, -24, or -48');
|
|
}
|
|
cascadingCount++;
|
|
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]);
|
|
this.connectSeries.apply(this, connectionChain);
|
|
}
|
|
});
|
|
/**
|
|
* clean up
|
|
* @return {Tone.Filter} this
|
|
*/
|
|
Tone.Filter.prototype.dispose = function () {
|
|
Tone.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}
|
|
* @constructor
|
|
* @param {number} lowFrequency the low/mid crossover frequency
|
|
* @param {number} highFrequency the mid/high crossover frequency
|
|
*/
|
|
Tone.MultibandSplit = function () {
|
|
var options = this.optionsObject(arguments, [
|
|
'lowFrequency',
|
|
'highFrequency'
|
|
], Tone.MultibandSplit.defaults);
|
|
/**
|
|
* the input
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this.input = this.context.createGain();
|
|
/**
|
|
* the outputs
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this.output = new Array(3);
|
|
/**
|
|
* the low band
|
|
* @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
|
|
* @type {Tone.Filter}
|
|
*/
|
|
this.mid = this.output[1] = new Tone.Filter(0, 'lowpass');
|
|
/**
|
|
* the high band
|
|
* @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 fitlers
|
|
* @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);
|
|
/**
|
|
* @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.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 A 3 band EQ with control over low, mid, and high gain as
|
|
* well as the low and high crossover frequencies.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone}
|
|
*
|
|
* @param {number|object} [lowLevel=0] the gain applied to the lows (in db)
|
|
* @param {number} [midLevel=0] the gain applied to the mid (in db)
|
|
* @param {number} [highLevel=0] the gain applied to the high (in db)
|
|
* @example
|
|
* var eq = new Tone.EQ3(-10, 3, -20);
|
|
*/
|
|
Tone.EQ3 = function () {
|
|
var options = this.optionsObject(arguments, [
|
|
'low',
|
|
'mid',
|
|
'high'
|
|
], Tone.EQ3.defaults);
|
|
/**
|
|
* the output node
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this.output = this.context.createGain();
|
|
/**
|
|
* the multiband split
|
|
* @type {Tone.MultibandSplit}
|
|
* @private
|
|
*/
|
|
this._multibandSplit = this.input = new Tone.MultibandSplit({
|
|
'lowFrequency': options.lowFrequency,
|
|
'highFrequency': options.highFrequency
|
|
});
|
|
/**
|
|
* the low gain
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._lowGain = this.context.createGain();
|
|
/**
|
|
* the mid gain
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._midGain = this.context.createGain();
|
|
/**
|
|
* the high gain
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._highGain = this.context.createGain();
|
|
/**
|
|
* The gain in decibels of the low part
|
|
* @type {Decibels}
|
|
* @signal
|
|
*/
|
|
this.low = new Tone.Signal(this._lowGain.gain, Tone.Type.Decibels);
|
|
/**
|
|
* The gain in decibels of the mid part
|
|
* @type {Decibels}
|
|
* @signal
|
|
*/
|
|
this.mid = new Tone.Signal(this._midGain.gain, Tone.Type.Decibels);
|
|
/**
|
|
* The gain in decibels of the high part
|
|
* @type {Decibels}
|
|
* @signal
|
|
*/
|
|
this.high = new Tone.Signal(this._highGain.gain, Tone.Type.Decibels);
|
|
/**
|
|
* the Q value
|
|
* @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);
|
|
//set the gains
|
|
this.low.value = options.low;
|
|
this.mid.value = options.mid;
|
|
this.high.value = options.high;
|
|
this._readOnly([
|
|
'low',
|
|
'mid',
|
|
'high',
|
|
'lowFrequency',
|
|
'highFrequency'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.EQ3);
|
|
/**
|
|
* 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.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.disconnect();
|
|
this._lowGain = null;
|
|
this._midGain.disconnect();
|
|
this._midGain = null;
|
|
this._highGain.disconnect();
|
|
this._highGain = null;
|
|
this.low.dispose();
|
|
this.low = null;
|
|
this.mid.dispose();
|
|
this.mid = null;
|
|
this.high.dispose();
|
|
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 normal gain input range [0,1] to between
|
|
* outputMin and outputMax
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.SignalBase}
|
|
* @param {number} [outputMin=0]
|
|
* @param {number} [outputMax=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) {
|
|
/**
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
this._outputMin = this.defaultArg(outputMin, 0);
|
|
/**
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
this._outputMax = this.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.
|
|
* @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.
|
|
* @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.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 normal gain range [0,1] exponentially
|
|
* to the output range of outputMin to outputMax.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.SignalBase}
|
|
* @param {number} [outputMin=0]
|
|
* @param {number} [outputMax=1]
|
|
* @param {number} [exponent=2] the exponent which scales the incoming signal
|
|
*/
|
|
Tone.ScaleExp = function (outputMin, outputMax, exponent) {
|
|
/**
|
|
* 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(this.defaultArg(exponent, 2));
|
|
this._exp.connect(this._scale);
|
|
};
|
|
Tone.extend(Tone.ScaleExp, Tone.SignalBase);
|
|
/**
|
|
* The minimum output value.
|
|
* @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.
|
|
* @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.
|
|
* @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.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 A comb filter with feedback.
|
|
*
|
|
* @extends {Tone}
|
|
* @constructor
|
|
* @param {number} [delayTime=0.1] the minimum delay time which the filter can have
|
|
* @param {number} [resonance=0.5] the maximum delay time which the filter can have
|
|
*/
|
|
Tone.FeedbackCombFilter = function () {
|
|
Tone.call(this);
|
|
var options = this.optionsObject(arguments, [
|
|
'delayTime',
|
|
'resonance'
|
|
], Tone.FeedbackCombFilter.defaults);
|
|
/**
|
|
* the resonance control
|
|
* @type {NormalRange}
|
|
* @signal
|
|
*/
|
|
this.resonance = new Tone.Signal(options.resonance, Tone.Type.NormalRange);
|
|
/**
|
|
* the delay node
|
|
* @type {DelayNode}
|
|
* @private
|
|
*/
|
|
this._delay = this.input = this.output = this.context.createDelay(1);
|
|
/**
|
|
* the delayTime
|
|
* @type {Time}
|
|
* @signal
|
|
*/
|
|
this.delayTime = new Tone.Signal(options.delayTime, Tone.Type.Time);
|
|
/**
|
|
* the feedback node
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._feedback = this.context.createGain();
|
|
this._delay.chain(this._feedback, this._delay);
|
|
this.resonance.connect(this._feedback.gain);
|
|
this.delayTime.connect(this._delay.delayTime);
|
|
this._readOnly([
|
|
'resonance',
|
|
'delayTime'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.FeedbackCombFilter);
|
|
/**
|
|
* 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.prototype.dispose.call(this);
|
|
this._writable([
|
|
'resonance',
|
|
'delayTime'
|
|
]);
|
|
this._delay.disconnect();
|
|
this._delay = null;
|
|
this.delayTime.dispose();
|
|
this.delayTime = null;
|
|
this.resonance.dispose();
|
|
this.resonance = null;
|
|
this._feedback.disconnect();
|
|
this._feedback = null;
|
|
return this;
|
|
};
|
|
return Tone.FeedbackCombFilter;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Follow the envelope of the incoming signal.
|
|
* Careful with small (< 0.02) attack or decay values.
|
|
* The follower has some ripple which gets exaggerated
|
|
* by small values.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone}
|
|
* @param {Time=} attack
|
|
* @param {Time=} release
|
|
* @example
|
|
* var follower = new Tone.Follower(0.2, 0.4);
|
|
*/
|
|
Tone.Follower = function () {
|
|
Tone.call(this);
|
|
var options = this.optionsObject(arguments, [
|
|
'attack',
|
|
'release'
|
|
], Tone.Follower.defaults);
|
|
/**
|
|
* @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 {DelayNode}
|
|
* @private
|
|
*/
|
|
this._delay = this.context.createDelay();
|
|
this._delay.delayTime.value = this.bufferTime;
|
|
/**
|
|
* 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);
|
|
/**
|
|
* @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.bufferTime;
|
|
attack = this.secondsToFrequency(this.toSeconds(attack));
|
|
release = this.secondsToFrequency(this.toSeconds(release));
|
|
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 control signal {@link Tone.Signal}
|
|
* @function
|
|
*/
|
|
Tone.Follower.prototype.connect = Tone.Signal.prototype.connect;
|
|
/**
|
|
* dispose
|
|
* @returns {Tone.Follower} this
|
|
*/
|
|
Tone.Follower.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._filter.disconnect();
|
|
this._filter = null;
|
|
this._frequencyValues.disconnect();
|
|
this._frequencyValues = null;
|
|
this._delay.disconnect();
|
|
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 Only pass signal through when it's signal exceeds the
|
|
* specified threshold.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone}
|
|
* @param {number} [threshold = -40] the threshold in Decibels
|
|
* @param {Time} [attack = 0.1] the follower's attack time
|
|
* @param {Time} [release = 0.1] the follower's release time
|
|
* @example
|
|
* var gate = new Tone.Gate(-30, 0.2, 0.3);
|
|
*/
|
|
Tone.Gate = function () {
|
|
Tone.call(this);
|
|
var options = this.optionsObject(arguments, [
|
|
'threshold',
|
|
'attack',
|
|
'release'
|
|
], Tone.Gate.defaults);
|
|
/**
|
|
* @type {Tone.Follower}
|
|
* @private
|
|
*/
|
|
this._follower = new Tone.Follower(options.attack, options.release);
|
|
/**
|
|
* @type {Tone.GreaterThan}
|
|
* @private
|
|
*/
|
|
this._gt = new Tone.GreaterThan(this.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);
|
|
/**
|
|
* @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 this.gainToDb(this._gt.value);
|
|
},
|
|
set: function (thresh) {
|
|
this._gt.value = this.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;
|
|
}
|
|
});
|
|
/**
|
|
* dispose
|
|
* @returns {Tone.Gate} this
|
|
*/
|
|
Tone.Gate.prototype.dispose = function () {
|
|
Tone.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 Base class for sources. Sources have start/stop methods
|
|
* and the ability to be synced to the
|
|
* start/stop of Tone.Transport.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone}
|
|
*/
|
|
Tone.Source = function (options) {
|
|
//unlike most ToneNodes, Sources only have an output and no input
|
|
Tone.call(this, 0, 1);
|
|
options = this.defaultArg(options, Tone.Source.defaults);
|
|
/**
|
|
* Callback is invoked when the source is done playing.
|
|
* @type {function}
|
|
* @example
|
|
* source.onended = function(){
|
|
* console.log("the source is done playing");
|
|
* }
|
|
*/
|
|
this.onended = options.onended;
|
|
/**
|
|
* the next time the source is started
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._nextStart = Infinity;
|
|
/**
|
|
* the next time the source is stopped
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._nextStop = Infinity;
|
|
/**
|
|
* The volume of the output in decibels.
|
|
* @type {Decibels}
|
|
* @signal
|
|
* @example
|
|
* source.volume.value = -6;
|
|
*/
|
|
this.volume = new Tone.Signal({
|
|
'param': this.output.gain,
|
|
'value': options.volume,
|
|
'units': Tone.Type.Decibels
|
|
});
|
|
this._readOnly('volume');
|
|
/**
|
|
* keeps track of the timeout for chaning the state
|
|
* and calling the onended
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._timeout = -1;
|
|
//make the output explicitly stereo
|
|
this.output.channelCount = 2;
|
|
this.output.channelCountMode = 'explicit';
|
|
};
|
|
Tone.extend(Tone.Source);
|
|
/**
|
|
* The default parameters
|
|
* @static
|
|
* @const
|
|
* @type {Object}
|
|
*/
|
|
Tone.Source.defaults = {
|
|
'onended': Tone.noOp,
|
|
'volume': 0
|
|
};
|
|
/**
|
|
* Returns the playback state of the source, either "started" or "stopped".
|
|
* @type {Tone.State}
|
|
* @readOnly
|
|
* @memberOf Tone.Source#
|
|
* @name state
|
|
*/
|
|
Object.defineProperty(Tone.Source.prototype, 'state', {
|
|
get: function () {
|
|
return this._stateAtTime(this.now());
|
|
}
|
|
});
|
|
/**
|
|
* Get the state of the source at the specified time.
|
|
* @param {Time} time
|
|
* @return {Tone.State}
|
|
* @private
|
|
*/
|
|
Tone.Source.prototype._stateAtTime = function (time) {
|
|
time = this.toSeconds(time);
|
|
if (this._nextStart <= time && this._nextStop > time) {
|
|
return Tone.State.Started;
|
|
} else if (this._nextStop <= time) {
|
|
return Tone.State.Stopped;
|
|
} else {
|
|
return Tone.State.Stopped;
|
|
}
|
|
};
|
|
/**
|
|
* Start the source.
|
|
* @param {Time} [time=now]
|
|
* @returns {Tone.Source} this
|
|
* @example
|
|
* source.start("+0.5"); //starts the source 0.5 seconds from now
|
|
*/
|
|
Tone.Source.prototype.start = function (time) {
|
|
time = this.toSeconds(time);
|
|
if (this._stateAtTime(time) !== Tone.State.Started || this.retrigger) {
|
|
this._nextStart = time;
|
|
this._nextStop = Infinity;
|
|
this._start.apply(this, arguments);
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* Stop the source.
|
|
* @param {Time} [time=now]
|
|
* @returns {Tone.Source} this
|
|
* @example
|
|
* source.stop(); // stops the source immediately
|
|
*/
|
|
Tone.Source.prototype.stop = function (time) {
|
|
var now = this.now();
|
|
time = this.toSeconds(time, now);
|
|
if (this._stateAtTime(time) === Tone.State.Started) {
|
|
this._nextStop = this.toSeconds(time);
|
|
clearTimeout(this._timeout);
|
|
var diff = time - now;
|
|
if (diff > 0) {
|
|
//add a small buffer before invoking the callback
|
|
this._timeout = setTimeout(this.onended, diff * 1000 + 20);
|
|
} else {
|
|
this.onended();
|
|
}
|
|
this._stop.apply(this, arguments);
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* Not ready yet.
|
|
* @private
|
|
* @abstract
|
|
* @param {Time} time
|
|
* @returns {Tone.Source} this
|
|
*/
|
|
Tone.Source.prototype.pause = function (time) {
|
|
//if there is no pause, just stop it
|
|
this.stop(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* Sync the source to the Transport so that when the transport
|
|
* is started, this source is started and when the transport is stopped
|
|
* or paused, so is the source.
|
|
*
|
|
* @param {Time} [delay=0] Delay time before starting the source after the
|
|
* Transport has started.
|
|
* @returns {Tone.Source} this
|
|
* @example
|
|
* //sync the source to start 1 measure after the transport starts
|
|
* source.sync("1m");
|
|
* //start the transport. the source will start 1 measure later.
|
|
* Tone.Transport.start();
|
|
*/
|
|
Tone.Source.prototype.sync = function (delay) {
|
|
Tone.Transport.syncSource(this, delay);
|
|
return this;
|
|
};
|
|
/**
|
|
* Unsync the source to the Transport. See Tone.Source.sync
|
|
* @returns {Tone.Source} this
|
|
*/
|
|
Tone.Source.prototype.unsync = function () {
|
|
Tone.Transport.unsyncSource(this);
|
|
return this;
|
|
};
|
|
/**
|
|
* Clean up.
|
|
* @return {Tone.Source} this
|
|
*/
|
|
Tone.Source.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this.stop();
|
|
clearTimeout(this._timeout);
|
|
this.onended = Tone.noOp;
|
|
this._writable('volume');
|
|
this.volume.dispose();
|
|
this.volume = null;
|
|
};
|
|
return Tone.Source;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Oscilator with start, stop and sync to Transport methods
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Source}
|
|
* @param {Frequency} [frequency] starting frequency
|
|
* @param {string} [type] The oscillator type. Read more about type below.
|
|
* @example
|
|
* var osc = new Tone.Oscillator(440, "sine");
|
|
*/
|
|
Tone.Oscillator = function () {
|
|
var options = this.optionsObject(arguments, [
|
|
'frequency',
|
|
'type'
|
|
], Tone.Oscillator.defaults);
|
|
Tone.Source.call(this, options);
|
|
/**
|
|
* the main oscillator
|
|
* @type {OscillatorNode}
|
|
* @private
|
|
*/
|
|
this._oscillator = null;
|
|
/**
|
|
* The frequency control signal in hertz.
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency);
|
|
/**
|
|
* The detune control signal in cents.
|
|
* @type {Cents}
|
|
* @signal
|
|
*/
|
|
this.detune = new Tone.Signal(options.detune, Tone.Type.Cents);
|
|
/**
|
|
* the periodic wave
|
|
* @type {PeriodicWave}
|
|
* @private
|
|
*/
|
|
this._wave = null;
|
|
/**
|
|
* the phase of the oscillator
|
|
* between 0 - 360
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._phase = options.phase;
|
|
/**
|
|
* the type of the oscillator
|
|
* @type {string}
|
|
* @private
|
|
*/
|
|
this._type = null;
|
|
//setup
|
|
this.type = options.type;
|
|
this.phase = this._phase;
|
|
this._readOnly([
|
|
'frequency',
|
|
'detune'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.Oscillator, Tone.Source);
|
|
/**
|
|
* the default parameters
|
|
* @type {Object}
|
|
*/
|
|
Tone.Oscillator.defaults = {
|
|
'type': 'sine',
|
|
'frequency': 440,
|
|
'detune': 0,
|
|
'phase': 0
|
|
};
|
|
/**
|
|
* start the oscillator
|
|
* @param {Time} [time=now]
|
|
* @private
|
|
*/
|
|
Tone.Oscillator.prototype._start = function (time) {
|
|
//new oscillator with previous values
|
|
this._oscillator = this.context.createOscillator();
|
|
this._oscillator.setPeriodicWave(this._wave);
|
|
//connect the control signal to the oscillator frequency & detune
|
|
this._oscillator.connect(this.output);
|
|
this.frequency.connect(this._oscillator.frequency);
|
|
this.detune.connect(this._oscillator.detune);
|
|
//start the oscillator
|
|
this._oscillator.start(this.toSeconds(time));
|
|
};
|
|
/**
|
|
* stop the oscillator
|
|
* @private
|
|
* @param {Time} [time=now] (optional) timing parameter
|
|
* @returns {Tone.Oscillator} this
|
|
*/
|
|
Tone.Oscillator.prototype._stop = function (time) {
|
|
if (this._oscillator) {
|
|
this._oscillator.stop(this.toSeconds(time));
|
|
this._oscillator = null;
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* Sync the signal to the Transport's bpm. Any changes to the transports bpm,
|
|
* will also affect the oscillators frequency.
|
|
* @returns {Tone.Oscillator} this
|
|
* @example
|
|
* Tone.Transport.bpm.value = 120;
|
|
* osc.frequency.value = 440;
|
|
* osc.syncFrequency();
|
|
* Tone.Transport.bpm.value = 240;
|
|
* // the frequency of the oscillator is doubled to 880
|
|
*/
|
|
Tone.Oscillator.prototype.syncFrequency = function () {
|
|
Tone.Transport.syncSignal(this.frequency);
|
|
return this;
|
|
};
|
|
/**
|
|
* Unsync the oscillator's frequency from the Transport.
|
|
* See Tone.Oscillator.syncFrequency
|
|
* @returns {Tone.Oscillator} this
|
|
*/
|
|
Tone.Oscillator.prototype.unsyncFrequency = function () {
|
|
Tone.Transport.unsyncSignal(this.frequency);
|
|
return this;
|
|
};
|
|
/**
|
|
* The type of the oscillator: either sine, square, triangle, or sawtooth. Also capable of
|
|
* setting the first x number of partials of the oscillator. For example: "sine4" would
|
|
* would set be the first 4 partials of the sine wave and "triangle8" would set the first
|
|
* 8 partials of the triangle wave.
|
|
* <br><br>
|
|
* Uses PeriodicWave internally even for native types so that it can set the phase.
|
|
* PeriodicWave equations are from the
|
|
* <a href="https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/modules/webaudio/PeriodicWave.cpp&sq=package:chromium">Webkit Web Audio implementation</a>.
|
|
*
|
|
* @memberOf Tone.Oscillator#
|
|
* @type {string}
|
|
* @name type
|
|
* @example
|
|
* //set it to a square wave
|
|
* osc.type = "square";
|
|
* @example
|
|
* //set the first 6 partials of a sawtooth wave
|
|
* osc.type = "sawtooth6";
|
|
*/
|
|
Object.defineProperty(Tone.Oscillator.prototype, 'type', {
|
|
get: function () {
|
|
return this._type;
|
|
},
|
|
set: function (type) {
|
|
var originalType = type;
|
|
var fftSize = 4096;
|
|
var periodicWaveSize = fftSize / 2;
|
|
var real = new Float32Array(periodicWaveSize);
|
|
var imag = new Float32Array(periodicWaveSize);
|
|
var partialCount = 1;
|
|
var partial = /(sine|triangle|square|sawtooth)(\d+)$/.exec(type);
|
|
if (partial) {
|
|
partialCount = parseInt(partial[2]);
|
|
type = partial[1];
|
|
partialCount = Math.max(partialCount, 2);
|
|
periodicWaveSize = partialCount;
|
|
}
|
|
var shift = this._phase;
|
|
for (var n = 1; n < periodicWaveSize; ++n) {
|
|
var piFactor = 2 / (n * Math.PI);
|
|
var b;
|
|
switch (type) {
|
|
case 'sine':
|
|
b = n <= partialCount ? 1 : 0;
|
|
break;
|
|
case 'square':
|
|
b = n & 1 ? 2 * piFactor : 0;
|
|
break;
|
|
case 'sawtooth':
|
|
b = piFactor * (n & 1 ? 1 : -1);
|
|
break;
|
|
case 'triangle':
|
|
if (n & 1) {
|
|
b = 2 * (piFactor * piFactor) * (n - 1 >> 1 & 1 ? -1 : 1);
|
|
} else {
|
|
b = 0;
|
|
}
|
|
break;
|
|
default:
|
|
throw new TypeError('invalid oscillator type: ' + type);
|
|
}
|
|
if (b !== 0) {
|
|
real[n] = -b * Math.sin(shift * n);
|
|
imag[n] = b * Math.cos(shift * n);
|
|
} else {
|
|
real[n] = 0;
|
|
imag[n] = 0;
|
|
}
|
|
}
|
|
var periodicWave = this.context.createPeriodicWave(real, imag);
|
|
this._wave = periodicWave;
|
|
if (this._oscillator !== null) {
|
|
this._oscillator.setPeriodicWave(this._wave);
|
|
}
|
|
this._type = originalType;
|
|
}
|
|
});
|
|
/**
|
|
* The phase of the oscillator in degrees.
|
|
* @memberOf Tone.Oscillator#
|
|
* @type {Degrees}
|
|
* @name phase
|
|
* @example
|
|
* osc.phase = 180; //flips the phase of the oscillator
|
|
*/
|
|
Object.defineProperty(Tone.Oscillator.prototype, 'phase', {
|
|
get: function () {
|
|
return this._phase * (180 / Math.PI);
|
|
},
|
|
set: function (phase) {
|
|
this._phase = phase * Math.PI / 180;
|
|
//reset the type
|
|
this.type = this._type;
|
|
}
|
|
});
|
|
/**
|
|
* Dispose and disconnect.
|
|
* @return {Tone.Oscillator} this
|
|
*/
|
|
Tone.Oscillator.prototype.dispose = function () {
|
|
Tone.Source.prototype.dispose.call(this);
|
|
if (this._oscillator !== null) {
|
|
this._oscillator.disconnect();
|
|
this._oscillator = null;
|
|
}
|
|
this._wave = null;
|
|
this._writable([
|
|
'frequency',
|
|
'detune'
|
|
]);
|
|
this.frequency.dispose();
|
|
this.frequency = null;
|
|
this.detune.dispose();
|
|
this.detune = null;
|
|
return this;
|
|
};
|
|
return Tone.Oscillator;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class AudioToGain converts an input range of -1,1 to 0,1
|
|
*
|
|
* @extends {Tone.SignalBase}
|
|
* @constructor
|
|
* @example
|
|
* var a2g = new Tone.AudioToGain();
|
|
*/
|
|
Tone.AudioToGain = function () {
|
|
/**
|
|
* @type {WaveShaperNode}
|
|
* @private
|
|
*/
|
|
this._norm = this.input = this.output = new Tone.WaveShaper(function (x) {
|
|
return (x + 1) / 2;
|
|
});
|
|
};
|
|
Tone.extend(Tone.AudioToGain, Tone.SignalBase);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.AudioToGain} this
|
|
*/
|
|
Tone.AudioToGain.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._norm.dispose();
|
|
this._norm = null;
|
|
return this;
|
|
};
|
|
return Tone.AudioToGain;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class The Low Frequency Oscillator produces an output signal
|
|
* which can be attached to an AudioParam or Tone.Signal
|
|
* for constant control over that parameter. the LFO can
|
|
* also be synced to the transport to start/stop/pause
|
|
* and change when the tempo changes. The LFO starts at
|
|
* it's minimal value.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Oscillator}
|
|
* @param {Time} [frequency="4n"]
|
|
* @param {number} [outputMin=0]
|
|
* @param {number} [outputMax=1]
|
|
* @example
|
|
* var lfo = new Tone.LFO("4n", 400, 4000);
|
|
* lfo.connect(filter.frequency);
|
|
*/
|
|
Tone.LFO = function () {
|
|
var options = this.optionsObject(arguments, [
|
|
'frequency',
|
|
'min',
|
|
'max'
|
|
], Tone.LFO.defaults);
|
|
/**
|
|
* the oscillator
|
|
* @type {Tone.Oscillator}
|
|
*/
|
|
this.oscillator = new Tone.Oscillator({
|
|
'frequency': options.frequency,
|
|
'type': options.type,
|
|
'phase': options.phase + 90
|
|
});
|
|
/**
|
|
* the lfo's frequency
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.frequency = this.oscillator.frequency;
|
|
/**
|
|
* The amplitude of the LFO, which controls the output range between
|
|
* the min and max output. For example if the min is -10 and the max
|
|
* is 10, setting the amplitude to 0.5 would make the LFO modulate
|
|
* between -5 and 5.
|
|
* @type {Number}
|
|
* @signal
|
|
*/
|
|
this.amplitude = this.oscillator.volume;
|
|
this.amplitude.units = Tone.Type.NormalRange;
|
|
this.amplitude.value = options.amplitude;
|
|
/**
|
|
* @type {Tone.AudioToGain}
|
|
* @private
|
|
*/
|
|
this._a2g = new Tone.AudioToGain();
|
|
/**
|
|
* @type {Tone.Scale}
|
|
* @private
|
|
*/
|
|
this._scaler = this.output = new Tone.Scale(options.min, options.max);
|
|
/**
|
|
* the units of the LFO (used for converting)
|
|
* @type {string}
|
|
* @private
|
|
*/
|
|
this._units = Tone.Type.Default;
|
|
//connect it up
|
|
this.oscillator.chain(this._a2g, this._scaler);
|
|
this._readOnly([
|
|
'amplitude',
|
|
'frequency',
|
|
'oscillator'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.LFO, Tone.Oscillator);
|
|
/**
|
|
* the default parameters
|
|
*
|
|
* @static
|
|
* @const
|
|
* @type {Object}
|
|
*/
|
|
Tone.LFO.defaults = {
|
|
'type': 'sine',
|
|
'min': 0,
|
|
'max': 1,
|
|
'phase': 0,
|
|
'frequency': '4n',
|
|
'amplitude': 1
|
|
};
|
|
/**
|
|
* Start the LFO.
|
|
* @param {Time} [time=now] the time the LFO will start
|
|
* @returns {Tone.LFO} this
|
|
*/
|
|
Tone.LFO.prototype.start = function (time) {
|
|
this.oscillator.start(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* Stop the LFO.
|
|
* @param {Time} [time=now] the time the LFO will stop
|
|
* @returns {Tone.LFO} this
|
|
*/
|
|
Tone.LFO.prototype.stop = function (time) {
|
|
this.oscillator.stop(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* Sync the start/stop/pause to the transport
|
|
* and the frequency to the bpm of the transport
|
|
*
|
|
* @param {Time} [delay=0] the time to delay the start of the
|
|
* LFO from the start of the transport
|
|
* @returns {Tone.LFO} this
|
|
* @example
|
|
* lfo.frequency.value = "8n";
|
|
* lfo.sync();
|
|
* // the rate of the LFO will always be an eighth note,
|
|
* // even as the tempo changes
|
|
*/
|
|
Tone.LFO.prototype.sync = function (delay) {
|
|
this.oscillator.sync(delay);
|
|
this.oscillator.syncFrequency();
|
|
return this;
|
|
};
|
|
/**
|
|
* unsync the LFO from transport control
|
|
* @returns {Tone.LFO} this
|
|
*/
|
|
Tone.LFO.prototype.unsync = function () {
|
|
this.oscillator.unsync();
|
|
this.oscillator.unsyncFrequency();
|
|
return this;
|
|
};
|
|
/**
|
|
* The miniumum output of the LFO.
|
|
* @memberOf Tone.LFO#
|
|
* @type {number}
|
|
* @name min
|
|
*/
|
|
Object.defineProperty(Tone.LFO.prototype, 'min', {
|
|
get: function () {
|
|
return this._toUnits(this._scaler.min);
|
|
},
|
|
set: function (min) {
|
|
min = this._fromUnits(min);
|
|
this._scaler.min = min;
|
|
}
|
|
});
|
|
/**
|
|
* The maximum output of the LFO.
|
|
* @memberOf Tone.LFO#
|
|
* @type {number}
|
|
* @name max
|
|
*/
|
|
Object.defineProperty(Tone.LFO.prototype, 'max', {
|
|
get: function () {
|
|
return this._toUnits(this._scaler.max);
|
|
},
|
|
set: function (max) {
|
|
max = this._fromUnits(max);
|
|
this._scaler.max = max;
|
|
}
|
|
});
|
|
/**
|
|
* The type of the oscillator: sine, square, sawtooth, triangle.
|
|
* @memberOf Tone.LFO#
|
|
* @type {string}
|
|
* @name type
|
|
*/
|
|
Object.defineProperty(Tone.LFO.prototype, 'type', {
|
|
get: function () {
|
|
return this.oscillator.type;
|
|
},
|
|
set: function (type) {
|
|
this.oscillator.type = type;
|
|
}
|
|
});
|
|
/**
|
|
* The phase of the LFO
|
|
* @memberOf Tone.LFO#
|
|
* @type {number}
|
|
* @name phase
|
|
*/
|
|
Object.defineProperty(Tone.LFO.prototype, 'phase', {
|
|
get: function () {
|
|
return this.oscillator.phase - 90;
|
|
},
|
|
set: function (phase) {
|
|
this.oscillator.phase = phase + 90;
|
|
}
|
|
});
|
|
/**
|
|
* The output units of the LFO
|
|
* @memberOf Tone.LFO#
|
|
* @type {Tone.Type}
|
|
* @name units
|
|
*/
|
|
Object.defineProperty(Tone.LFO.prototype, 'units', {
|
|
get: function () {
|
|
return this._units;
|
|
},
|
|
set: function (val) {
|
|
var currentMin = this.min;
|
|
var currentMax = this.max;
|
|
//convert the min and the max
|
|
this._units = val;
|
|
this.min = currentMin;
|
|
this.max = currentMax;
|
|
}
|
|
});
|
|
/**
|
|
* Connect the output of a ToneNode to an AudioParam, AudioNode, or Tone Node.
|
|
* will get the units from the connected node.
|
|
* @param {Tone | AudioParam | AudioNode} node
|
|
* @param {number} [outputNum=0] optionally which output to connect from
|
|
* @param {number} [inputNum=0] optionally which input to connect to
|
|
* @returns {Tone.LFO} this
|
|
*/
|
|
Tone.LFO.prototype.connect = function (node) {
|
|
if (node.constructor === Tone.Signal) {
|
|
this.convert = node.convert;
|
|
this.units = node.units;
|
|
}
|
|
Tone.Signal.prototype.connect.apply(this, arguments);
|
|
return this;
|
|
};
|
|
/**
|
|
* private method borroed from Signal converts
|
|
* units from their destination value
|
|
* @function
|
|
* @private
|
|
*/
|
|
Tone.LFO.prototype._fromUnits = Tone.Signal.prototype._fromUnits;
|
|
/**
|
|
* private method borroed from Signal converts
|
|
* units to their destination value
|
|
* @function
|
|
* @private
|
|
*/
|
|
Tone.LFO.prototype._toUnits = Tone.Signal.prototype._toUnits;
|
|
/**
|
|
* disconnect and dispose
|
|
* @returns {Tone.LFO} this
|
|
*/
|
|
Tone.LFO.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._writable([
|
|
'amplitude',
|
|
'frequency',
|
|
'oscillator'
|
|
]);
|
|
this.oscillator.dispose();
|
|
this.oscillator = null;
|
|
this._scaler.dispose();
|
|
this._scaler = null;
|
|
this._a2g.dispose();
|
|
this._a2g = null;
|
|
this.frequency = null;
|
|
this.amplitude = null;
|
|
return this;
|
|
};
|
|
return Tone.LFO;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class A limiter on the incoming signal. Composed of a Tone.Compressor
|
|
* with a fast attack and decay value.
|
|
*
|
|
* @extends {Tone}
|
|
* @constructor
|
|
* @param {number} threshold the threshold in decibels
|
|
* @example
|
|
* var limiter = new Tone.Limiter(-6);
|
|
*/
|
|
Tone.Limiter = function (threshold) {
|
|
/**
|
|
* the compressor
|
|
* @private
|
|
* @type {Tone.Compressor}
|
|
*/
|
|
this._compressor = this.input = this.output = new Tone.Compressor({
|
|
'attack': 0.0001,
|
|
'decay': 0.0001,
|
|
'threshold': threshold
|
|
});
|
|
/**
|
|
* The threshold of of the limiter
|
|
* @type {AudioParam}
|
|
*/
|
|
this.threshold = this._compressor.threshold;
|
|
this._readOnly('threshold');
|
|
};
|
|
Tone.extend(Tone.Limiter);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Limiter} this
|
|
*/
|
|
Tone.Limiter.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._compressor.dispose();
|
|
this._compressor = null;
|
|
this._writable('threshold');
|
|
this.threshold = null;
|
|
return this;
|
|
};
|
|
return Tone.Limiter;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class A lowpass feedback comb filter.
|
|
* DelayNode -> Lowpass Filter -> feedback
|
|
*
|
|
* @extends {Tone}
|
|
* @constructor
|
|
* @param {Time} [delayTime=0.1] The delay time of the comb filter
|
|
* @param {NormalRange} [resonance=0.5] The resonance (feedback) of the comb filter
|
|
* @param {Frequency} [dampening=3000] The dampending cutoff of the lowpass filter
|
|
*/
|
|
Tone.LowpassCombFilter = function () {
|
|
Tone.call(this);
|
|
var options = this.optionsObject(arguments, [
|
|
'delayTime',
|
|
'resonance',
|
|
'dampening'
|
|
], Tone.LowpassCombFilter.defaults);
|
|
/**
|
|
* the delay node
|
|
* @type {DelayNode}
|
|
* @private
|
|
*/
|
|
this._delay = this.input = this.context.createDelay(1);
|
|
/**
|
|
* the delayTime
|
|
* @type {Time}
|
|
* @signal
|
|
*/
|
|
this.delayTime = new Tone.Signal(options.delayTime, Tone.Type.Time);
|
|
/**
|
|
* the lowpass filter
|
|
* @type {BiquadFilterNode}
|
|
* @private
|
|
*/
|
|
this._lowpass = this.output = this.context.createBiquadFilter();
|
|
this._lowpass.Q.value = 0;
|
|
this._lowpass.type = 'lowpass';
|
|
/**
|
|
* the dampening control
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.dampening = new Tone.Signal(this._lowpass.frequency, Tone.Type.Frequency);
|
|
this.dampening.value = options.dampening;
|
|
/**
|
|
* the feedback gain
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._feedback = this.context.createGain();
|
|
/**
|
|
* the resonance control
|
|
* @type {NormalRange}
|
|
* @signal
|
|
*/
|
|
this.resonance = new Tone.Signal(options.resonance, Tone.Type.NormalRange);
|
|
//connections
|
|
this._delay.chain(this._lowpass, this._feedback, this._delay);
|
|
this.delayTime.connect(this._delay.delayTime);
|
|
this.resonance.connect(this._feedback.gain);
|
|
this.dampening.connect(this._lowpass.frequency);
|
|
this._readOnly([
|
|
'dampening',
|
|
'resonance',
|
|
'delayTime'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.LowpassCombFilter);
|
|
/**
|
|
* the default parameters
|
|
* @static
|
|
* @const
|
|
* @type {Object}
|
|
*/
|
|
Tone.LowpassCombFilter.defaults = {
|
|
'delayTime': 0.1,
|
|
'resonance': 0.5,
|
|
'dampening': 3000
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.LowpassCombFilter} this
|
|
*/
|
|
Tone.LowpassCombFilter.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._writable([
|
|
'dampening',
|
|
'resonance',
|
|
'delayTime'
|
|
]);
|
|
this.dampening.dispose();
|
|
this.dampening = null;
|
|
this.resonance.dispose();
|
|
this.resonance = null;
|
|
this._delay.disconnect();
|
|
this._delay = null;
|
|
this._lowpass.disconnect();
|
|
this._lowpass = null;
|
|
this._feedback.disconnect();
|
|
this._feedback = null;
|
|
this.delayTime.dispose();
|
|
this.delayTime = null;
|
|
return this;
|
|
};
|
|
return Tone.LowpassCombFilter;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Merge a left and a right channel into a single stereo channel.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone}
|
|
* @example
|
|
* var merge = new Tone.Merge();
|
|
* sigLeft.connect(merge.left);
|
|
* sigRight.connect(merge.right);
|
|
*/
|
|
Tone.Merge = function () {
|
|
Tone.call(this, 2, 0);
|
|
/**
|
|
* The left input channel.
|
|
* Alias for input 0
|
|
* @type {GainNode}
|
|
*/
|
|
this.left = this.input[0] = this.context.createGain();
|
|
/**
|
|
* The right input channel.
|
|
* Alias for input 1.
|
|
* @type {GainNode}
|
|
*/
|
|
this.right = this.input[1] = this.context.createGain();
|
|
/**
|
|
* the merger node for the two channels
|
|
* @type {ChannelMergerNode}
|
|
* @private
|
|
*/
|
|
this._merger = this.output = this.context.createChannelMerger(2);
|
|
//connections
|
|
this.left.connect(this._merger, 0, 0);
|
|
this.right.connect(this._merger, 0, 1);
|
|
};
|
|
Tone.extend(Tone.Merge);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Merge} this
|
|
*/
|
|
Tone.Merge.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this.left.disconnect();
|
|
this.left = null;
|
|
this.right.disconnect();
|
|
this.right = null;
|
|
this._merger.disconnect();
|
|
this._merger = null;
|
|
return this;
|
|
};
|
|
return Tone.Merge;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Get the rms of the input signal with some averaging.
|
|
* Can also just get the value of the signal
|
|
* or the value in dB. inspired by https://github.com/cwilso/volume-meter/blob/master/volume-meter.js<br><br>
|
|
* Note that for signal processing, it's better to use Tone.Follower which will produce
|
|
* an audio-rate envelope follower instead of needing to poll the Meter to get the output.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone}
|
|
* @param {number} [channels=1] number of channels being metered
|
|
* @param {number} [smoothing=0.8] amount of smoothing applied to the volume
|
|
* @param {number} [clipMemory=0.5] number in seconds that a "clip" should be remembered
|
|
*/
|
|
Tone.Meter = function (channels, smoothing, clipMemory) {
|
|
//extends Unit
|
|
Tone.call(this);
|
|
/**
|
|
* The channel count
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._channels = this.defaultArg(channels, 1);
|
|
/**
|
|
* the smoothing value
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._smoothing = this.defaultArg(smoothing, 0.8);
|
|
/**
|
|
* the amount of time a clip is remember for.
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._clipMemory = this.defaultArg(clipMemory, 0.5) * 1000;
|
|
/**
|
|
* the rms for each of the channels
|
|
* @private
|
|
* @type {Array}
|
|
*/
|
|
this._volume = new Array(this._channels);
|
|
/**
|
|
* the raw values for each of the channels
|
|
* @private
|
|
* @type {Array}
|
|
*/
|
|
this._values = new Array(this._channels);
|
|
//zero out the volume array
|
|
for (var i = 0; i < this._channels; i++) {
|
|
this._volume[i] = 0;
|
|
this._values[i] = 0;
|
|
}
|
|
/**
|
|
* last time the values clipped
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
this._lastClip = 0;
|
|
/**
|
|
* @private
|
|
* @type {ScriptProcessorNode}
|
|
*/
|
|
this._jsNode = this.context.createScriptProcessor(this.bufferSize, this._channels, 1);
|
|
this._jsNode.onaudioprocess = this._onprocess.bind(this);
|
|
//so it doesn't get garbage collected
|
|
this._jsNode.noGC();
|
|
//signal just passes
|
|
this.input.connect(this.output);
|
|
this.input.connect(this._jsNode);
|
|
};
|
|
Tone.extend(Tone.Meter);
|
|
/**
|
|
* called on each processing frame
|
|
* @private
|
|
* @param {AudioProcessingEvent} event
|
|
*/
|
|
Tone.Meter.prototype._onprocess = function (event) {
|
|
var bufferSize = this._jsNode.bufferSize;
|
|
var smoothing = this._smoothing;
|
|
for (var channel = 0; channel < this._channels; channel++) {
|
|
var input = event.inputBuffer.getChannelData(channel);
|
|
var sum = 0;
|
|
var total = 0;
|
|
var x;
|
|
var clipped = false;
|
|
for (var i = 0; i < bufferSize; i++) {
|
|
x = input[i];
|
|
if (!clipped && x > 0.95) {
|
|
clipped = true;
|
|
this._lastClip = Date.now();
|
|
}
|
|
total += x;
|
|
sum += x * x;
|
|
}
|
|
var average = total / bufferSize;
|
|
var rms = Math.sqrt(sum / bufferSize);
|
|
this._volume[channel] = Math.max(rms, this._volume[channel] * smoothing);
|
|
this._values[channel] = average;
|
|
}
|
|
};
|
|
/**
|
|
* get the rms of the signal
|
|
*
|
|
* @param {number} [channel=0] which channel
|
|
* @return {number} the value
|
|
*/
|
|
Tone.Meter.prototype.getLevel = function (channel) {
|
|
channel = this.defaultArg(channel, 0);
|
|
var vol = this._volume[channel];
|
|
if (vol < 0.00001) {
|
|
return 0;
|
|
} else {
|
|
return vol;
|
|
}
|
|
};
|
|
/**
|
|
* get the value of the signal
|
|
* @param {number=} channel
|
|
* @return {number}
|
|
*/
|
|
Tone.Meter.prototype.getValue = function (channel) {
|
|
channel = this.defaultArg(channel, 0);
|
|
return this._values[channel];
|
|
};
|
|
/**
|
|
* get the volume of the signal in dB
|
|
* @param {number=} channel
|
|
* @return {number}
|
|
*/
|
|
Tone.Meter.prototype.getDb = function (channel) {
|
|
return this.gainToDb(this.getLevel(channel));
|
|
};
|
|
/**
|
|
* @returns {boolean} if the audio has clipped in the last 500ms
|
|
*/
|
|
Tone.Meter.prototype.isClipped = function () {
|
|
return Date.now() - this._lastClip < this._clipMemory;
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Meter} this
|
|
*/
|
|
Tone.Meter.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._jsNode.disconnect();
|
|
this._jsNode.onaudioprocess = null;
|
|
this._volume = null;
|
|
this._values = null;
|
|
return this;
|
|
};
|
|
return Tone.Meter;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Split the incoming signal into left and right channels
|
|
*
|
|
* @constructor
|
|
* @extends {Tone}
|
|
* @example
|
|
* var split = new Tone.Split();
|
|
* stereoSignal.connect(split);
|
|
*/
|
|
Tone.Split = function () {
|
|
Tone.call(this, 0, 2);
|
|
/**
|
|
* @type {ChannelSplitterNode}
|
|
* @private
|
|
*/
|
|
this._splitter = this.input = this.context.createChannelSplitter(2);
|
|
/**
|
|
* left channel output
|
|
* alais for the first output
|
|
* @type {GainNode}
|
|
*/
|
|
this.left = this.output[0] = this.context.createGain();
|
|
/**
|
|
* the right channel output
|
|
* alais for the second output
|
|
* @type {GainNode}
|
|
*/
|
|
this.right = this.output[1] = this.context.createGain();
|
|
//connections
|
|
this._splitter.connect(this.left, 0, 0);
|
|
this._splitter.connect(this.right, 1, 0);
|
|
};
|
|
Tone.extend(Tone.Split);
|
|
/**
|
|
* dispose method
|
|
* @returns {Tone.Split} this
|
|
*/
|
|
Tone.Split.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._splitter.disconnect();
|
|
this.left.disconnect();
|
|
this.right.disconnect();
|
|
this.left = null;
|
|
this.right = null;
|
|
this._splitter = null;
|
|
return this;
|
|
};
|
|
return Tone.Split;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Seperates the mid channel from the side channel. Has two outputs labeled
|
|
* `mid` and `side` or `output[0]` and `output[1]`. <br>
|
|
* M = (L+R)/sqrt(2); // obtain mid-signal from left and right<br>
|
|
* S = (L-R)/sqrt(2); // obtain side-signal from left and righ<br>
|
|
*
|
|
* @extends {Tone}
|
|
* @constructor
|
|
*/
|
|
Tone.MidSideSplit = function () {
|
|
Tone.call(this, 0, 2);
|
|
/**
|
|
* split the incoming signal into left and right channels
|
|
* @type {Tone.Split}
|
|
* @private
|
|
*/
|
|
this._split = this.input = new Tone.Split();
|
|
/**
|
|
* The mid send. Connect to mid processing.
|
|
* @type {Tone.Expr}
|
|
*/
|
|
this.mid = this.output[0] = new Tone.Expr('($0 + $1) * $2');
|
|
/**
|
|
* The side output. Connect to side processing.
|
|
* @type {Tone.Expr}
|
|
*/
|
|
this.side = this.output[1] = new Tone.Expr('($0 - $1) * $2');
|
|
this._split.connect(this.mid, 0, 0);
|
|
this._split.connect(this.mid, 1, 1);
|
|
this._split.connect(this.side, 0, 0);
|
|
this._split.connect(this.side, 1, 1);
|
|
sqrtTwo.connect(this.mid, 0, 2);
|
|
sqrtTwo.connect(this.side, 0, 2);
|
|
};
|
|
Tone.extend(Tone.MidSideSplit);
|
|
/**
|
|
* a constant signal equal to 1 / sqrt(2)
|
|
* @type {Number}
|
|
* @signal
|
|
* @private
|
|
* @static
|
|
*/
|
|
var sqrtTwo = null;
|
|
Tone._initAudioContext(function () {
|
|
sqrtTwo = new Tone.Signal(1 / Math.sqrt(2));
|
|
});
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.MidSideSplit} this
|
|
*/
|
|
Tone.MidSideSplit.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this.mid.dispose();
|
|
this.mid = null;
|
|
this.side.dispose();
|
|
this.side = null;
|
|
this._split.dispose();
|
|
this._split = null;
|
|
return this;
|
|
};
|
|
return Tone.MidSideSplit;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Mid/Side processing separates the the 'mid' signal
|
|
* (which comes out of both the left and the right channel)
|
|
* and the 'side' (which only comes out of the the side channels).
|
|
* MidSideMerge merges the mid and side signal after they've been seperated
|
|
* by Tone.MidSideSplit.<br>
|
|
* M/S send/return<br>
|
|
* L = (M+S)/sqrt(2); // obtain left signal from mid and side<br>
|
|
* R = (M-S)/sqrt(2); // obtain right signal from mid and side<br>
|
|
*
|
|
* @extends {Tone.StereoEffect}
|
|
* @constructor
|
|
*/
|
|
Tone.MidSideMerge = function () {
|
|
Tone.call(this, 2, 0);
|
|
/**
|
|
* The mid signal input.
|
|
* @type {GainNode}
|
|
*/
|
|
this.mid = this.input[0] = this.context.createGain();
|
|
/**
|
|
* recombine the mid/side into Left
|
|
* @type {Tone.Expr}
|
|
* @private
|
|
*/
|
|
this._left = new Tone.Expr('($0 + $1) * $2');
|
|
/**
|
|
* The side signal input.
|
|
* @type {GainNode}
|
|
*/
|
|
this.side = this.input[1] = this.context.createGain();
|
|
/**
|
|
* recombine the mid/side into Right
|
|
* @type {Tone.Expr}
|
|
* @private
|
|
*/
|
|
this._right = new Tone.Expr('($0 - $1) * $2');
|
|
/**
|
|
* Merge the left/right signal back into a stereo signal.
|
|
* @type {Tone.Merge}
|
|
* @private
|
|
*/
|
|
this._merge = this.output = new Tone.Merge();
|
|
this.mid.connect(this._left, 0, 0);
|
|
this.side.connect(this._left, 0, 1);
|
|
this.mid.connect(this._right, 0, 0);
|
|
this.side.connect(this._right, 0, 1);
|
|
this._left.connect(this._merge, 0, 0);
|
|
this._right.connect(this._merge, 0, 1);
|
|
sqrtTwo.connect(this._left, 0, 2);
|
|
sqrtTwo.connect(this._right, 0, 2);
|
|
};
|
|
Tone.extend(Tone.MidSideMerge);
|
|
/**
|
|
* A constant signal equal to 1 / sqrt(2).
|
|
* @type {Number}
|
|
* @signal
|
|
* @private
|
|
* @static
|
|
*/
|
|
var sqrtTwo = null;
|
|
Tone._initAudioContext(function () {
|
|
sqrtTwo = new Tone.Signal(1 / Math.sqrt(2));
|
|
});
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.MidSideMerge} this
|
|
*/
|
|
Tone.MidSideMerge.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this.mid.disconnect();
|
|
this.mid = null;
|
|
this.side.disconnect();
|
|
this.side = null;
|
|
this._left.dispose();
|
|
this._left = null;
|
|
this._right.dispose();
|
|
this._right = null;
|
|
this._merge.dispose();
|
|
this._merge = null;
|
|
return this;
|
|
};
|
|
return Tone.MidSideMerge;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class MidSideCompressor applies two different compressors to the mid
|
|
* and side signal components.
|
|
*
|
|
* @extends {Tone.MidSideEffect}
|
|
* @constructor
|
|
*/
|
|
Tone.MidSideCompressor = function (options) {
|
|
options = this.defaultArg(options, Tone.MidSideCompressor.defaults);
|
|
/**
|
|
* the mid/side split
|
|
* @type {Tone.MidSideSplit}
|
|
* @private
|
|
*/
|
|
this._midSideSplit = this.input = new Tone.MidSideSplit();
|
|
/**
|
|
* the mid/side recombination
|
|
* @type {Tone.MidSideMerge}
|
|
* @private
|
|
*/
|
|
this._midSideMerge = this.output = new Tone.MidSideMerge();
|
|
/**
|
|
* The compressor applied to the mid signal
|
|
* @type {Tone.Compressor}
|
|
*/
|
|
this.mid = new Tone.Compressor(options.mid);
|
|
/**
|
|
* The compressor applied to the side signal
|
|
* @type {Tone.Compressor}
|
|
*/
|
|
this.side = new Tone.Compressor(options.side);
|
|
this._midSideSplit.mid.chain(this.mid, this._midSideMerge.mid);
|
|
this._midSideSplit.side.chain(this.side, this._midSideMerge.side);
|
|
this._readOnly([
|
|
'mid',
|
|
'side'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.MidSideCompressor);
|
|
/**
|
|
* @const
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.MidSideCompressor.defaults = {
|
|
'mid': {
|
|
'ratio': 3,
|
|
'threshold': -24,
|
|
'release': 0.03,
|
|
'attack': 0.02,
|
|
'knee': 16
|
|
},
|
|
'side': {
|
|
'ratio': 6,
|
|
'threshold': -30,
|
|
'release': 0.25,
|
|
'attack': 0.03,
|
|
'knee': 10
|
|
}
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.MidSideCompressor} this
|
|
*/
|
|
Tone.MidSideCompressor.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._writable([
|
|
'mid',
|
|
'side'
|
|
]);
|
|
this.mid.dispose();
|
|
this.mid = null;
|
|
this.side.dispose();
|
|
this.side = null;
|
|
this._midSideSplit.dispose();
|
|
this._midSideSplit = null;
|
|
this._midSideMerge.dispose();
|
|
this._midSideMerge = null;
|
|
return this;
|
|
};
|
|
return Tone.MidSideCompressor;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Coerces the incoming mono or stereo signal into a mono signal
|
|
* where both left and right channels have the same value.
|
|
*
|
|
* @extends {Tone}
|
|
* @constructor
|
|
*/
|
|
Tone.Mono = function () {
|
|
Tone.call(this, 1, 0);
|
|
/**
|
|
* merge the signal
|
|
* @type {Tone.Merge}
|
|
* @private
|
|
*/
|
|
this._merge = this.output = new Tone.Merge();
|
|
this.input.connect(this._merge, 0, 0);
|
|
this.input.connect(this._merge, 0, 1);
|
|
this.input.gain.value = this.dbToGain(-10);
|
|
};
|
|
Tone.extend(Tone.Mono);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Mono} this
|
|
*/
|
|
Tone.Mono.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._merge.dispose();
|
|
this._merge = null;
|
|
return this;
|
|
};
|
|
return Tone.Mono;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class A compressor with seperate controls over low/mid/high dynamics
|
|
*
|
|
* @extends {Tone}
|
|
* @constructor
|
|
* @param {Object} options the low/mid/high compressor settings in a single object
|
|
* @example
|
|
* var multiband = new Tone.MultibandCompressor({
|
|
* "lowFrequency" : 200,
|
|
* "highFrequency" : 1300
|
|
* "low" : {
|
|
* "threshold" : -12
|
|
* }
|
|
* })
|
|
*/
|
|
Tone.MultibandCompressor = function (options) {
|
|
options = this.defaultArg(arguments, Tone.MultibandCompressor.defaults);
|
|
/**
|
|
* split the incoming signal into high/mid/low
|
|
* @type {Tone.MultibandSplit}
|
|
* @private
|
|
*/
|
|
this._splitter = this.input = new Tone.MultibandSplit({
|
|
'lowFrequency': options.lowFrequency,
|
|
'highFrequency': options.highFrequency
|
|
});
|
|
/**
|
|
* low/mid crossover frequency
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.lowFrequency = this._splitter.lowFrequency;
|
|
/**
|
|
* mid/high crossover frequency
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.highFrequency = this._splitter.highFrequency;
|
|
/**
|
|
* the output
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this.output = this.context.createGain();
|
|
/**
|
|
* the low compressor
|
|
* @type {Tone.Compressor}
|
|
*/
|
|
this.low = new Tone.Compressor(options.low);
|
|
/**
|
|
* the mid compressor
|
|
* @type {Tone.Compressor}
|
|
*/
|
|
this.mid = new Tone.Compressor(options.mid);
|
|
/**
|
|
* the high compressor
|
|
* @type {Tone.Compressor}
|
|
*/
|
|
this.high = new Tone.Compressor(options.high);
|
|
//connect the compressor
|
|
this._splitter.low.chain(this.low, this.output);
|
|
this._splitter.mid.chain(this.mid, this.output);
|
|
this._splitter.high.chain(this.high, this.output);
|
|
this._readOnly([
|
|
'high',
|
|
'mid',
|
|
'low',
|
|
'highFrequency',
|
|
'lowFrequency'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.MultibandCompressor);
|
|
/**
|
|
* @const
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.MultibandCompressor.defaults = {
|
|
'low': Tone.Compressor.defaults,
|
|
'mid': Tone.Compressor.defaults,
|
|
'high': Tone.Compressor.defaults,
|
|
'lowFrequency': 250,
|
|
'highFrequency': 2000
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.MultibandCompressor} this
|
|
*/
|
|
Tone.MultibandCompressor.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._splitter.dispose();
|
|
this._writable([
|
|
'high',
|
|
'mid',
|
|
'low',
|
|
'highFrequency',
|
|
'lowFrequency'
|
|
]);
|
|
this.low.dispose();
|
|
this.mid.dispose();
|
|
this.high.dispose();
|
|
this._splitter = null;
|
|
this.low = null;
|
|
this.mid = null;
|
|
this.high = null;
|
|
this.lowFrequency = null;
|
|
this.highFrequency = null;
|
|
return this;
|
|
};
|
|
return Tone.MultibandCompressor;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Maps a gain value [0, 1] to an audio value [-1, 1]
|
|
*
|
|
* @extends {Tone.SignalBase}
|
|
* @constructor
|
|
* @example
|
|
* var g2a = new Tone.GainToAudio();
|
|
*/
|
|
Tone.GainToAudio = function () {
|
|
/**
|
|
* @type {WaveShaperNode}
|
|
* @private
|
|
*/
|
|
this._norm = this.input = this.output = new Tone.WaveShaper(function (x) {
|
|
return Math.abs(x) * 2 - 1;
|
|
});
|
|
};
|
|
Tone.extend(Tone.GainToAudio, Tone.SignalBase);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.GainToAudio} this
|
|
*/
|
|
Tone.GainToAudio.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._norm.dispose();
|
|
this._norm = null;
|
|
return this;
|
|
};
|
|
return Tone.GainToAudio;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* Panner.
|
|
*
|
|
* @class Equal Power Gain L/R Panner. Not 3D.
|
|
* 0 = 100% Left
|
|
* 1 = 100% Right
|
|
*
|
|
* @constructor
|
|
* @extends {Tone}
|
|
* @param {number} [initialPan=0.5] the initail panner value (defaults to 0.5 = center)
|
|
* @example
|
|
* var panner = new Tone.Panner(1);
|
|
* // ^ pan the input signal hard right.
|
|
*/
|
|
Tone.Panner = function (initialPan) {
|
|
Tone.call(this);
|
|
/**
|
|
* indicates if the panner is using the new StereoPannerNode internally
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
this._hasStereoPanner = this.isFunction(this.context.createStereoPanner);
|
|
if (this._hasStereoPanner) {
|
|
/**
|
|
* the panner node
|
|
* @type {StereoPannerNode}
|
|
* @private
|
|
*/
|
|
this._panner = this.input = this.output = this.context.createStereoPanner();
|
|
/**
|
|
* the pan control
|
|
* @type {NormalRange}
|
|
* @signal
|
|
*/
|
|
this.pan = new Tone.Signal(0, Tone.Type.NormalRange);
|
|
/**
|
|
* scale the pan signal to between -1 and 1
|
|
* @type {Tone.WaveShaper}
|
|
* @private
|
|
*/
|
|
this._scalePan = new Tone.GainToAudio();
|
|
//connections
|
|
this.pan.chain(this._scalePan, this._panner.pan);
|
|
} else {
|
|
/**
|
|
* the dry/wet knob
|
|
* @type {Tone.CrossFade}
|
|
* @private
|
|
*/
|
|
this._crossFade = new Tone.CrossFade();
|
|
/**
|
|
* @type {Tone.Merge}
|
|
* @private
|
|
*/
|
|
this._merger = this.output = new Tone.Merge();
|
|
/**
|
|
* @type {Tone.Split}
|
|
* @private
|
|
*/
|
|
this._splitter = this.input = new Tone.Split();
|
|
/**
|
|
* the pan control
|
|
* @type {NormalRange}
|
|
* @signal
|
|
*/
|
|
this.pan = this._crossFade.fade;
|
|
//CONNECTIONS:
|
|
//left channel is a, right channel is b
|
|
this._splitter.connect(this._crossFade, 0, 0);
|
|
this._splitter.connect(this._crossFade, 1, 1);
|
|
//merge it back together
|
|
this._crossFade.a.connect(this._merger, 0, 0);
|
|
this._crossFade.b.connect(this._merger, 0, 1);
|
|
}
|
|
//initial value
|
|
this.pan.value = this.defaultArg(initialPan, 0.5);
|
|
this._readOnly('pan');
|
|
};
|
|
Tone.extend(Tone.Panner);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Panner} this
|
|
*/
|
|
Tone.Panner.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._writable('pan');
|
|
if (this._hasStereoPanner) {
|
|
this._panner.disconnect();
|
|
this._panner = null;
|
|
this.pan.dispose();
|
|
this.pan = null;
|
|
this._scalePan.dispose();
|
|
this._scalePan = null;
|
|
} else {
|
|
this._crossFade.dispose();
|
|
this._crossFade = null;
|
|
this._splitter.dispose();
|
|
this._splitter = null;
|
|
this._merger.dispose();
|
|
this._merger = null;
|
|
this.pan = null;
|
|
}
|
|
return this;
|
|
};
|
|
return Tone.Panner;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class A simple volume node. Volume value in decibels.
|
|
*
|
|
* @extends {Tone}
|
|
* @constructor
|
|
* @param {number} [volume=0] the initial volume
|
|
* @example
|
|
* var vol = new Tone.Volume(-12);
|
|
* instrument.chain(vol, Tone.Master);
|
|
*/
|
|
Tone.Volume = function (volume) {
|
|
/**
|
|
* the output node
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this.output = this.input = this.context.createGain();
|
|
/**
|
|
* The volume control in decibels.
|
|
* @type {Tone.Signal}
|
|
*/
|
|
this.volume = new Tone.Signal(this.output.gain, Tone.Type.Decibels);
|
|
this.volume.value = this.defaultArg(volume, 0);
|
|
this._readOnly('volume');
|
|
};
|
|
Tone.extend(Tone.Volume);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Volume} this
|
|
*/
|
|
Tone.Volume.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._writable('volume');
|
|
this.volume.dispose();
|
|
this.volume = null;
|
|
return this;
|
|
};
|
|
return Tone.Volume;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class A Panner and volume in one.
|
|
*
|
|
* @extends {Tone}
|
|
* @constructor
|
|
* @param {number} pan the initial pan
|
|
* @param {number} volume the volume
|
|
* @example
|
|
* var panVol = new Tone.PanVol(0.25, -12);
|
|
*/
|
|
Tone.PanVol = function (pan, volume) {
|
|
/**
|
|
* the panning node
|
|
* @type {Tone.Panner}
|
|
* @private
|
|
*/
|
|
this._panner = this.input = new Tone.Panner(pan);
|
|
/**
|
|
* the panning control
|
|
* @type {Tone.Panner}
|
|
* @private
|
|
*/
|
|
this.pan = this._panner.pan;
|
|
/**
|
|
* the volume control
|
|
* @type {Tone.Volume}
|
|
* @private
|
|
*/
|
|
this._volume = this.output = new Tone.Volume(volume);
|
|
/**
|
|
* The volume control in decibels.
|
|
* @type {Decibels}
|
|
* @signal
|
|
*/
|
|
this.volume = this._volume.volume;
|
|
//connections
|
|
this._panner.connect(this._volume);
|
|
this._readOnly([
|
|
'pan',
|
|
'volume'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.PanVol);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.PanVol} this
|
|
*/
|
|
Tone.PanVol.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._writable([
|
|
'pan',
|
|
'volume'
|
|
]);
|
|
this._panner.dispose();
|
|
this._panner = null;
|
|
this._volume.dispose();
|
|
this._volume = null;
|
|
this.pan = null;
|
|
this.volume = null;
|
|
return this;
|
|
};
|
|
return Tone.PanVol;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class An envelope which can be scaled to any range.
|
|
* Useful for applying an envelope to a filter
|
|
*
|
|
* @extends {Tone.Envelope}
|
|
* @constructor
|
|
* @param {Time|Object} [attack=0.01] the attack time in seconds
|
|
* @param {Time} [decay=0.1] the decay time in seconds
|
|
* @param {number} [sustain=0.5] a percentage (0-1) of the full amplitude
|
|
* @param {Time} [release=1] 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 = this.optionsObject(arguments, [
|
|
'attack',
|
|
'decay',
|
|
'sustain',
|
|
'release'
|
|
], Tone.Envelope.defaults);
|
|
Tone.Envelope.call(this, options);
|
|
options = this.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 Pulse Oscillator with control over width.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Oscillator}
|
|
* @param {Frequency} [frequency=440] the frequency of the oscillator
|
|
* @param {NormalRange} [width = 0.2] the width of the pulse
|
|
* @example
|
|
* var pulse = new Tone.PulseOscillator("E5", 0.4);
|
|
*/
|
|
Tone.PulseOscillator = function () {
|
|
var options = this.optionsObject(arguments, [
|
|
'frequency',
|
|
'width'
|
|
], Tone.Oscillator.defaults);
|
|
Tone.Source.call(this, options);
|
|
/**
|
|
* The width of the pulse.
|
|
* @type {NormalRange}
|
|
* @signal
|
|
*/
|
|
this.width = new Tone.Signal(options.width, Tone.Type.NormalRange);
|
|
/**
|
|
* gate the width amount
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._widthGate = this.context.createGain();
|
|
/**
|
|
* the sawtooth oscillator
|
|
* @type {Tone.Oscillator}
|
|
* @private
|
|
*/
|
|
this._sawtooth = new Tone.Oscillator({
|
|
frequency: options.frequency,
|
|
detune: options.detune,
|
|
type: 'sawtooth',
|
|
phase: options.phase
|
|
});
|
|
/**
|
|
* The frequency in of the oscillator.
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.frequency = this._sawtooth.frequency;
|
|
/**
|
|
* The detune in cents.
|
|
* @type {Cents}
|
|
* @signal
|
|
*/
|
|
this.detune = this._sawtooth.detune;
|
|
/**
|
|
* Threshold the signal to turn it into a square
|
|
* @type {Tone.WaveShaper}
|
|
* @private
|
|
*/
|
|
this._thresh = new Tone.WaveShaper(function (val) {
|
|
if (val < 0) {
|
|
return -1;
|
|
} else {
|
|
return 1;
|
|
}
|
|
});
|
|
//connections
|
|
this._sawtooth.chain(this._thresh, this.output);
|
|
this.width.chain(this._widthGate, this._thresh);
|
|
this._readOnly([
|
|
'width',
|
|
'frequency',
|
|
'detune'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.PulseOscillator, Tone.Oscillator);
|
|
/**
|
|
* The default parameters.
|
|
* @static
|
|
* @const
|
|
* @type {Object}
|
|
*/
|
|
Tone.PulseOscillator.defaults = {
|
|
'frequency': 440,
|
|
'detune': 0,
|
|
'phase': 0,
|
|
'width': 0.2
|
|
};
|
|
/**
|
|
* start the oscillator
|
|
* @param {Time} time
|
|
* @private
|
|
*/
|
|
Tone.PulseOscillator.prototype._start = function (time) {
|
|
time = this.toSeconds(time);
|
|
this._sawtooth.start(time);
|
|
this._widthGate.gain.setValueAtTime(1, time);
|
|
};
|
|
/**
|
|
* stop the oscillator
|
|
* @param {Time} time
|
|
* @private
|
|
*/
|
|
Tone.PulseOscillator.prototype._stop = function (time) {
|
|
time = this.toSeconds(time);
|
|
this._sawtooth.stop(time);
|
|
//the width is still connected to the output.
|
|
//that needs to be stopped also
|
|
this._widthGate.gain.setValueAtTime(0, time);
|
|
};
|
|
/**
|
|
* The phase of the oscillator in degrees.
|
|
* @memberOf Tone.PulseOscillator#
|
|
* @type {Degrees}
|
|
* @name phase
|
|
*/
|
|
Object.defineProperty(Tone.PulseOscillator.prototype, 'phase', {
|
|
get: function () {
|
|
return this._sawtooth.phase;
|
|
},
|
|
set: function (phase) {
|
|
this._sawtooth.phase = phase;
|
|
}
|
|
});
|
|
/**
|
|
* The type of the oscillator. Always returns "pulse".
|
|
* @readOnly
|
|
* @memberOf Tone.PulseOscillator#
|
|
* @type {string}
|
|
* @name type
|
|
*/
|
|
Object.defineProperty(Tone.PulseOscillator.prototype, 'type', {
|
|
get: function () {
|
|
return 'pulse';
|
|
}
|
|
});
|
|
/**
|
|
* Clean up method.
|
|
* @return {Tone.PulseOscillator} this
|
|
*/
|
|
Tone.PulseOscillator.prototype.dispose = function () {
|
|
Tone.Source.prototype.dispose.call(this);
|
|
this._sawtooth.dispose();
|
|
this._sawtooth = null;
|
|
this._writable([
|
|
'width',
|
|
'frequency',
|
|
'detune'
|
|
]);
|
|
this.width.dispose();
|
|
this.width = null;
|
|
this._widthGate.disconnect();
|
|
this._widthGate = null;
|
|
this._widthGate = null;
|
|
this._thresh.disconnect();
|
|
this._thresh = null;
|
|
this.frequency = null;
|
|
this.detune = null;
|
|
return this;
|
|
};
|
|
return Tone.PulseOscillator;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class PWM oscillator modulates the width of the Tone.PulseOscillator at the modulationFrequency.
|
|
*
|
|
* @extends {Tone.Oscillator}
|
|
* @constructor
|
|
* @param {Frequency} frequency The starting frequency of the oscillator.
|
|
* @param {Frequency} modulationFrequency The modulation frequency of the width of the pulse.
|
|
* @example
|
|
* var pwm = new Tone.PWMOscillator("Ab3", 0.3);
|
|
*/
|
|
Tone.PWMOscillator = function () {
|
|
var options = this.optionsObject(arguments, [
|
|
'frequency',
|
|
'modulationFrequency'
|
|
], Tone.PWMOscillator.defaults);
|
|
Tone.Source.call(this, options);
|
|
/**
|
|
* the pulse oscillator
|
|
* @type {Tone.PulseOscillator}
|
|
* @private
|
|
*/
|
|
this._pulse = new Tone.PulseOscillator(options.modulationFrequency);
|
|
//change the pulse oscillator type
|
|
this._pulse._sawtooth.type = 'sine';
|
|
/**
|
|
* the modulator
|
|
* @type {Tone.Oscillator}
|
|
* @private
|
|
*/
|
|
this._modulator = new Tone.Oscillator({
|
|
'frequency': options.frequency,
|
|
'detune': options.detune
|
|
});
|
|
/**
|
|
* Scale the oscillator so it doesn't go silent
|
|
* at the extreme values.
|
|
* @type {Tone.Multiply}
|
|
* @private
|
|
*/
|
|
this._scale = new Tone.Multiply(1.01);
|
|
/**
|
|
* The frequency control.
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.frequency = this._modulator.frequency;
|
|
/**
|
|
* The detune of the oscillator.
|
|
* @type {Cents}
|
|
* @signal
|
|
*/
|
|
this.detune = this._modulator.detune;
|
|
/**
|
|
* The modulation rate of the oscillator.
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.modulationFrequency = this._pulse.frequency;
|
|
//connections
|
|
this._modulator.chain(this._scale, this._pulse.width);
|
|
this._pulse.connect(this.output);
|
|
this._readOnly([
|
|
'modulationFrequency',
|
|
'frequency',
|
|
'detune'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.PWMOscillator, Tone.Oscillator);
|
|
/**
|
|
* default values
|
|
* @static
|
|
* @type {Object}
|
|
* @const
|
|
*/
|
|
Tone.PWMOscillator.defaults = {
|
|
'frequency': 440,
|
|
'detune': 0,
|
|
'modulationFrequency': 0.4
|
|
};
|
|
/**
|
|
* start the oscillator
|
|
* @param {Time} [time=now]
|
|
* @private
|
|
*/
|
|
Tone.PWMOscillator.prototype._start = function (time) {
|
|
time = this.toSeconds(time);
|
|
this._modulator.start(time);
|
|
this._pulse.start(time);
|
|
};
|
|
/**
|
|
* stop the oscillator
|
|
* @param {Time} time (optional) timing parameter
|
|
* @private
|
|
*/
|
|
Tone.PWMOscillator.prototype._stop = function (time) {
|
|
time = this.toSeconds(time);
|
|
this._modulator.stop(time);
|
|
this._pulse.stop(time);
|
|
};
|
|
/**
|
|
* The type of the oscillator. Always returns "pwm".
|
|
* @readOnly
|
|
* @memberOf Tone.PWMOscillator#
|
|
* @type {string}
|
|
* @name type
|
|
*/
|
|
Object.defineProperty(Tone.PWMOscillator.prototype, 'type', {
|
|
get: function () {
|
|
return 'pwm';
|
|
}
|
|
});
|
|
/**
|
|
* The phase of the oscillator in degrees.
|
|
* @memberOf Tone.PWMOscillator#
|
|
* @type {number}
|
|
* @name phase
|
|
*/
|
|
Object.defineProperty(Tone.PWMOscillator.prototype, 'phase', {
|
|
get: function () {
|
|
return this._modulator.phase;
|
|
},
|
|
set: function (phase) {
|
|
this._modulator.phase = phase;
|
|
}
|
|
});
|
|
/**
|
|
* Clean up.
|
|
* @return {Tone.PWMOscillator} this
|
|
*/
|
|
Tone.PWMOscillator.prototype.dispose = function () {
|
|
Tone.Source.prototype.dispose.call(this);
|
|
this._pulse.dispose();
|
|
this._pulse = null;
|
|
this._scale.dispose();
|
|
this._scale = null;
|
|
this._modulator.dispose();
|
|
this._modulator = null;
|
|
this._writable([
|
|
'modulationFrequency',
|
|
'frequency',
|
|
'detune'
|
|
]);
|
|
this.frequency = null;
|
|
this.detune = null;
|
|
this.modulationFrequency = null;
|
|
return this;
|
|
};
|
|
return Tone.PWMOscillator;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class OmniOscillator aggregates Tone.Oscillator, Tone.PulseOscillator,
|
|
* and Tone.PWMOscillator which allows it to have the types:
|
|
* sine, square, triangle, sawtooth, pulse or pwm.
|
|
*
|
|
* @extends {Tone.Oscillator}
|
|
* @constructor
|
|
* @param {Frequency} frequency of the oscillator (meaningless for noise types)
|
|
* @param {string} type the type of the oscillator
|
|
* @example
|
|
* var omniOsc = new Tone.OmniOscillator("C#4", "pwm");
|
|
*/
|
|
Tone.OmniOscillator = function () {
|
|
var options = this.optionsObject(arguments, [
|
|
'frequency',
|
|
'type'
|
|
], Tone.OmniOscillator.defaults);
|
|
Tone.Source.call(this, options);
|
|
/**
|
|
* the frequency control
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency);
|
|
/**
|
|
* the detune control
|
|
* @type {Cents}
|
|
* @signal
|
|
*/
|
|
this.detune = new Tone.Signal(options.detune, Tone.Type.Cents);
|
|
/**
|
|
* the type of the oscillator source
|
|
* @type {string}
|
|
* @private
|
|
*/
|
|
this._sourceType = undefined;
|
|
/**
|
|
* the oscillator
|
|
* @type {Tone.Oscillator|Tone.PWMOscillator|Tone.PulseOscillator}
|
|
* @private
|
|
*/
|
|
this._oscillator = null;
|
|
//set the oscillator
|
|
this.type = options.type;
|
|
this._readOnly([
|
|
'frequency',
|
|
'detune'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.OmniOscillator, Tone.Oscillator);
|
|
/**
|
|
* default values
|
|
* @static
|
|
* @type {Object}
|
|
* @const
|
|
*/
|
|
Tone.OmniOscillator.defaults = {
|
|
'frequency': 440,
|
|
'detune': 0,
|
|
'type': 'sine',
|
|
'width': 0.4,
|
|
//only applies if the oscillator is set to "pulse",
|
|
'modulationFrequency': 0.4
|
|
};
|
|
/**
|
|
* @enum {string}
|
|
* @private
|
|
*/
|
|
var OmniOscType = {
|
|
PulseOscillator: 'PulseOscillator',
|
|
PWMOscillator: 'PWMOscillator',
|
|
Oscillator: 'Oscillator'
|
|
};
|
|
/**
|
|
* start the oscillator
|
|
* @param {Time} [time=now] the time to start the oscillator
|
|
* @private
|
|
*/
|
|
Tone.OmniOscillator.prototype._start = function (time) {
|
|
this._oscillator.start(time);
|
|
};
|
|
/**
|
|
* start the oscillator
|
|
* @param {Time} [time=now] the time to start the oscillator
|
|
* @private
|
|
*/
|
|
Tone.OmniOscillator.prototype._stop = function (time) {
|
|
this._oscillator.stop(time);
|
|
};
|
|
/**
|
|
* The type of the oscillator. sine, square, triangle, sawtooth, pwm, or pulse.
|
|
* @memberOf Tone.OmniOscillator#
|
|
* @type {string}
|
|
* @name type
|
|
*/
|
|
Object.defineProperty(Tone.OmniOscillator.prototype, 'type', {
|
|
get: function () {
|
|
return this._oscillator.type;
|
|
},
|
|
set: function (type) {
|
|
if (type.indexOf('sine') === 0 || type.indexOf('square') === 0 || type.indexOf('triangle') === 0 || type.indexOf('sawtooth') === 0) {
|
|
if (this._sourceType !== OmniOscType.Oscillator) {
|
|
this._sourceType = OmniOscType.Oscillator;
|
|
this._createNewOscillator(Tone.Oscillator);
|
|
}
|
|
this._oscillator.type = type;
|
|
} else if (type === 'pwm') {
|
|
if (this._sourceType !== OmniOscType.PWMOscillator) {
|
|
this._sourceType = OmniOscType.PWMOscillator;
|
|
this._createNewOscillator(Tone.PWMOscillator);
|
|
}
|
|
} else if (type === 'pulse') {
|
|
if (this._sourceType !== OmniOscType.PulseOscillator) {
|
|
this._sourceType = OmniOscType.PulseOscillator;
|
|
this._createNewOscillator(Tone.PulseOscillator);
|
|
}
|
|
} else {
|
|
throw new TypeError('Tone.OmniOscillator does not support type ' + type);
|
|
}
|
|
}
|
|
});
|
|
/**
|
|
* connect the oscillator to the frequency and detune signals
|
|
* @private
|
|
*/
|
|
Tone.OmniOscillator.prototype._createNewOscillator = function (OscillatorConstructor) {
|
|
//short delay to avoid clicks on the change
|
|
var now = this.now() + this.bufferTime;
|
|
if (this._oscillator !== null) {
|
|
var oldOsc = this._oscillator;
|
|
oldOsc.stop(now);
|
|
oldOsc.onended = function () {
|
|
oldOsc.dispose();
|
|
oldOsc = null;
|
|
};
|
|
}
|
|
this._oscillator = new OscillatorConstructor();
|
|
this.frequency.connect(this._oscillator.frequency);
|
|
this.detune.connect(this._oscillator.detune);
|
|
this._oscillator.connect(this.output);
|
|
if (this.state === Tone.State.Started) {
|
|
this._oscillator.start(now);
|
|
}
|
|
};
|
|
/**
|
|
* The phase of the oscillator in degrees.
|
|
* @memberOf Tone.OmniOscillator#
|
|
* @type {Degrees}
|
|
* @name phase
|
|
*/
|
|
Object.defineProperty(Tone.OmniOscillator.prototype, 'phase', {
|
|
get: function () {
|
|
return this._oscillator.phase;
|
|
},
|
|
set: function (phase) {
|
|
this._oscillator.phase = phase;
|
|
}
|
|
});
|
|
/**
|
|
* The width of the oscillator (only if the oscillator is set to pulse)
|
|
* @memberOf Tone.OmniOscillator#
|
|
* @type {NormalRange}
|
|
* @signal
|
|
* @name width
|
|
* @example
|
|
* var omniOsc = new Tone.OmniOscillator(440, "pulse");
|
|
* //can access the width attribute only if type === "pulse"
|
|
* omniOsc.width.value = 0.2;
|
|
*/
|
|
Object.defineProperty(Tone.OmniOscillator.prototype, 'width', {
|
|
get: function () {
|
|
if (this._sourceType === OmniOscType.PulseOscillator) {
|
|
return this._oscillator.width;
|
|
}
|
|
}
|
|
});
|
|
/**
|
|
* The modulationFrequency Signal of the oscillator
|
|
* (only if the oscillator type is set to pwm).
|
|
* @memberOf Tone.OmniOscillator#
|
|
* @type {Frequency}
|
|
* @signal
|
|
* @name modulationFrequency
|
|
* @example
|
|
* var omniOsc = new Tone.OmniOscillator(440, "pwm");
|
|
* //can access the modulationFrequency attribute only if type === "pwm"
|
|
* omniOsc.modulationFrequency.value = 0.2;
|
|
*/
|
|
Object.defineProperty(Tone.OmniOscillator.prototype, 'modulationFrequency', {
|
|
get: function () {
|
|
if (this._sourceType === OmniOscType.PWMOscillator) {
|
|
return this._oscillator.modulationFrequency;
|
|
}
|
|
}
|
|
});
|
|
/**
|
|
* clean up
|
|
* @return {Tone.OmniOscillator} this
|
|
*/
|
|
Tone.OmniOscillator.prototype.dispose = function () {
|
|
Tone.Source.prototype.dispose.call(this);
|
|
this._writable([
|
|
'frequency',
|
|
'detune'
|
|
]);
|
|
this.detune.dispose();
|
|
this.detune = null;
|
|
this.frequency.dispose();
|
|
this.frequency = null;
|
|
this._oscillator.dispose();
|
|
this._oscillator = null;
|
|
this._sourceType = null;
|
|
return this;
|
|
};
|
|
return Tone.OmniOscillator;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Base-class for all instruments
|
|
*
|
|
* @constructor
|
|
* @extends {Tone}
|
|
*/
|
|
Tone.Instrument = function () {
|
|
/**
|
|
* the output
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this.output = this.context.createGain();
|
|
/**
|
|
* the volume of the output in decibels
|
|
* @type {Decibels}
|
|
* @signal
|
|
*/
|
|
this.volume = new Tone.Signal(this.output.gain, Tone.Type.Decibels);
|
|
this._readOnly(['volume']);
|
|
};
|
|
Tone.extend(Tone.Instrument);
|
|
/**
|
|
* the default attributes
|
|
* @type {object}
|
|
*/
|
|
Tone.Instrument.defaults = {
|
|
/** the volume of the output in decibels */
|
|
'volume': 0
|
|
};
|
|
/**
|
|
* @abstract
|
|
* @param {string|number} note the note to trigger
|
|
* @param {Time} [time=now] the time to trigger the ntoe
|
|
* @param {number} [velocity=1] the velocity to trigger the note
|
|
*/
|
|
Tone.Instrument.prototype.triggerAttack = Tone.noOp;
|
|
/**
|
|
* @abstract
|
|
* @param {Time} [time=now] when to trigger the release
|
|
*/
|
|
Tone.Instrument.prototype.triggerRelease = Tone.noOp;
|
|
/**
|
|
* Trigger the attack and then the release after the duration.
|
|
* @param {string|number} note the note to trigger
|
|
* @param {Time} duration the duration of the note
|
|
* @param {Time} [time=now] the time of the attack
|
|
* @param {NormalRange} [velocity=1] the velocity
|
|
* @returns {Tone.Instrument} this
|
|
* @example
|
|
* //trigger "C4" for the duration of an 8th note
|
|
* synth.triggerAttackRelease("C4", "8n");
|
|
*/
|
|
Tone.Instrument.prototype.triggerAttackRelease = function (note, duration, time, velocity) {
|
|
time = this.toSeconds(time);
|
|
duration = this.toSeconds(duration);
|
|
this.triggerAttack(note, time, velocity);
|
|
this.triggerRelease(time + duration);
|
|
return this;
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Instrument} this
|
|
*/
|
|
Tone.Instrument.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._writable(['volume']);
|
|
this.volume.dispose();
|
|
this.volume = null;
|
|
return this;
|
|
};
|
|
return Tone.Instrument;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class this is a base class for monophonic instruments.
|
|
* it defines their interfaces
|
|
*
|
|
* @constructor
|
|
* @abstract
|
|
* @extends {Tone.Instrument}
|
|
*/
|
|
Tone.Monophonic = function (options) {
|
|
Tone.Instrument.call(this);
|
|
//get the defaults
|
|
options = this.defaultArg(options, Tone.Monophonic.defaults);
|
|
/**
|
|
* The glide time between notes.
|
|
* @type {Time}
|
|
*/
|
|
this.portamento = options.portamento;
|
|
};
|
|
Tone.extend(Tone.Monophonic, Tone.Instrument);
|
|
/**
|
|
* @static
|
|
* @const
|
|
* @type {Object}
|
|
*/
|
|
Tone.Monophonic.defaults = { 'portamento': 0 };
|
|
/**
|
|
* Trigger the attack. Start the note, at the time with the velocity
|
|
*
|
|
* @param {Frequency} note the note
|
|
* @param {Time} [time=now] the time, if not given is now
|
|
* @param {number} [velocity=1] velocity defaults to 1
|
|
* @returns {Tone.Monophonic} this
|
|
* @example
|
|
* synth.triggerAttack("C4");
|
|
*/
|
|
Tone.Monophonic.prototype.triggerAttack = function (note, time, velocity) {
|
|
time = this.toSeconds(time);
|
|
this._triggerEnvelopeAttack(time, velocity);
|
|
this.setNote(note, time);
|
|
return this;
|
|
};
|
|
/**
|
|
* Trigger the release portion of the envelope
|
|
* @param {Time} [time=now] if no time is given, the release happens immediatly
|
|
* @returns {Tone.Monophonic} this
|
|
* @example
|
|
* synth.triggerRelease();
|
|
*/
|
|
Tone.Monophonic.prototype.triggerRelease = function (time) {
|
|
this._triggerEnvelopeRelease(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* override this method with the actual method
|
|
* @abstract
|
|
* @private
|
|
*/
|
|
Tone.Monophonic.prototype._triggerEnvelopeAttack = function () {
|
|
};
|
|
/**
|
|
* override this method with the actual method
|
|
* @abstract
|
|
* @private
|
|
*/
|
|
Tone.Monophonic.prototype._triggerEnvelopeRelease = function () {
|
|
};
|
|
/**
|
|
* set the note to happen at a specific time
|
|
* @param {Frequency} note if the note is a string, it will be
|
|
* parsed as (NoteName)(Octave) i.e. A4, C#3, etc
|
|
* otherwise it will be considered as the frequency
|
|
* @param {Time} [time=now] The time when the note should be set.
|
|
* @returns {Tone.Monophonic} this
|
|
*/
|
|
Tone.Monophonic.prototype.setNote = function (note, time) {
|
|
time = this.toSeconds(time);
|
|
if (this.portamento > 0) {
|
|
var currentNote = this.frequency.value;
|
|
this.frequency.setValueAtTime(currentNote, time);
|
|
var portTime = this.toSeconds(this.portamento);
|
|
this.frequency.exponentialRampToValueAtTime(note, time + portTime);
|
|
} else {
|
|
this.frequency.setValueAtTime(note, time);
|
|
}
|
|
return this;
|
|
};
|
|
return Tone.Monophonic;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Tone.MonoSynth is composed of one oscillator, one filter, and two envelopes.
|
|
* The amplitude of the Tone.Oscillator and the cutoff frequency of the
|
|
* Tone.Filter are controlled by Tone.Envelopes.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Monophonic}
|
|
* @param {Object} [options] the options available for the synth
|
|
* see defaults below
|
|
* @example
|
|
* var synth = new Tone.MonoSynth().toMaster();
|
|
* synth.triggerAttackRelease("C4", "8n");
|
|
*/
|
|
Tone.MonoSynth = function (options) {
|
|
//get the defaults
|
|
options = this.defaultArg(options, Tone.MonoSynth.defaults);
|
|
Tone.Monophonic.call(this, options);
|
|
/**
|
|
* The oscillator.
|
|
* @type {Tone.OmniOscillator}
|
|
*/
|
|
this.oscillator = new Tone.OmniOscillator(options.oscillator);
|
|
/**
|
|
* The frequency control.
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.frequency = this.oscillator.frequency;
|
|
/**
|
|
* The detune control.
|
|
* @type {Cents}
|
|
* @signal
|
|
*/
|
|
this.detune = this.oscillator.detune;
|
|
/**
|
|
* The filter.
|
|
* @type {Tone.Filter}
|
|
*/
|
|
this.filter = new Tone.Filter(options.filter);
|
|
/**
|
|
* The filter envelope.
|
|
* @type {Tone.ScaledEnvelope}
|
|
*/
|
|
this.filterEnvelope = new Tone.ScaledEnvelope(options.filterEnvelope);
|
|
/**
|
|
* The amplitude envelope.
|
|
* @type {Tone.AmplitudeEnvelope}
|
|
*/
|
|
this.envelope = new Tone.AmplitudeEnvelope(options.envelope);
|
|
//connect the oscillators to the output
|
|
this.oscillator.chain(this.filter, this.envelope, this.output);
|
|
//start the oscillators
|
|
this.oscillator.start();
|
|
//connect the filter envelope
|
|
this.filterEnvelope.connect(this.filter.frequency);
|
|
this._readOnly([
|
|
'oscillator',
|
|
'frequency',
|
|
'detune',
|
|
'filter',
|
|
'filterEnvelope',
|
|
'envelope'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.MonoSynth, Tone.Monophonic);
|
|
/**
|
|
* @const
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.MonoSynth.defaults = {
|
|
'frequency': 'C4',
|
|
'detune': 0,
|
|
'oscillator': { 'type': 'square' },
|
|
'filter': {
|
|
'Q': 6,
|
|
'type': 'lowpass',
|
|
'rolloff': -24
|
|
},
|
|
'envelope': {
|
|
'attack': 0.005,
|
|
'decay': 0.1,
|
|
'sustain': 0.9,
|
|
'release': 1
|
|
},
|
|
'filterEnvelope': {
|
|
'attack': 0.06,
|
|
'decay': 0.2,
|
|
'sustain': 0.5,
|
|
'release': 2,
|
|
'min': 20,
|
|
'max': 4000,
|
|
'exponent': 2
|
|
}
|
|
};
|
|
/**
|
|
* start the attack portion of the envelope
|
|
* @param {Time} [time=now] the time the attack should start
|
|
* @param {NormalRange} [velocity=1] the velocity of the note (0-1)
|
|
* @returns {Tone.MonoSynth} this
|
|
* @private
|
|
*/
|
|
Tone.MonoSynth.prototype._triggerEnvelopeAttack = function (time, velocity) {
|
|
//the envelopes
|
|
this.envelope.triggerAttack(time, velocity);
|
|
this.filterEnvelope.triggerAttack(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* start the release portion of the envelope
|
|
* @param {Time} [time=now] the time the release should start
|
|
* @returns {Tone.MonoSynth} this
|
|
* @private
|
|
*/
|
|
Tone.MonoSynth.prototype._triggerEnvelopeRelease = function (time) {
|
|
this.envelope.triggerRelease(time);
|
|
this.filterEnvelope.triggerRelease(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.MonoSynth} this
|
|
*/
|
|
Tone.MonoSynth.prototype.dispose = function () {
|
|
Tone.Monophonic.prototype.dispose.call(this);
|
|
this._writable([
|
|
'oscillator',
|
|
'frequency',
|
|
'detune',
|
|
'filter',
|
|
'filterEnvelope',
|
|
'envelope'
|
|
]);
|
|
this.oscillator.dispose();
|
|
this.oscillator = null;
|
|
this.envelope.dispose();
|
|
this.envelope = null;
|
|
this.filterEnvelope.dispose();
|
|
this.filterEnvelope = null;
|
|
this.filter.dispose();
|
|
this.filter = null;
|
|
this.frequency = null;
|
|
this.detune = null;
|
|
return this;
|
|
};
|
|
return Tone.MonoSynth;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class AMSynth uses the output of one Tone.MonoSynth to modulate the
|
|
* amplitude of another Tone.MonoSynth. The harmonicity (the ratio between
|
|
* the two signals) affects the timbre of the output signal the most.
|
|
* Read more about Amplitude Modulation Synthesis on
|
|
* <a href="http://www.soundonsound.com/sos/mar00/articles/synthsecrets.htm" target="_blank">SoundOnSound</a>.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Monophonic}
|
|
* @param {Object} [options] the options available for the synth
|
|
* see defaults below
|
|
* @example
|
|
* var synth = new Tone.AMSynth().toMaster();
|
|
* synth.triggerAttackRelease("C4", "4n");
|
|
*/
|
|
Tone.AMSynth = function (options) {
|
|
options = this.defaultArg(options, Tone.AMSynth.defaults);
|
|
Tone.Monophonic.call(this, options);
|
|
/**
|
|
* The carrier voice.
|
|
* @type {Tone.MonoSynth}
|
|
*/
|
|
this.carrier = new Tone.MonoSynth(options.carrier);
|
|
this.carrier.volume.value = -10;
|
|
/**
|
|
* The modulator voice.
|
|
* @type {Tone.MonoSynth}
|
|
*/
|
|
this.modulator = new Tone.MonoSynth(options.modulator);
|
|
this.modulator.volume.value = -10;
|
|
/**
|
|
* The frequency.
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.frequency = new Tone.Signal(440, Tone.Type.Frequency);
|
|
/**
|
|
* The ratio between the two voices.
|
|
* @type {Positive}
|
|
* @signal
|
|
*/
|
|
this.harmonicity = new Tone.Multiply(options.harmonicity);
|
|
this.harmonicity.units = Tone.Type.Positive;
|
|
/**
|
|
* convert the -1,1 output to 0,1
|
|
* @type {Tone.AudioToGain}
|
|
* @private
|
|
*/
|
|
this._modulationScale = new Tone.AudioToGain();
|
|
/**
|
|
* the node where the modulation happens
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._modulationNode = this.context.createGain();
|
|
//control the two voices frequency
|
|
this.frequency.connect(this.carrier.frequency);
|
|
this.frequency.chain(this.harmonicity, this.modulator.frequency);
|
|
this.modulator.chain(this._modulationScale, this._modulationNode.gain);
|
|
this.carrier.chain(this._modulationNode, this.output);
|
|
this._readOnly([
|
|
'carrier',
|
|
'modulator',
|
|
'frequency',
|
|
'harmonicity'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.AMSynth, Tone.Monophonic);
|
|
/**
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.AMSynth.defaults = {
|
|
'harmonicity': 3,
|
|
'carrier': {
|
|
'volume': -10,
|
|
'oscillator': { 'type': 'sine' },
|
|
'envelope': {
|
|
'attack': 0.01,
|
|
'decay': 0.01,
|
|
'sustain': 1,
|
|
'release': 0.5
|
|
},
|
|
'filterEnvelope': {
|
|
'attack': 0.01,
|
|
'decay': 0,
|
|
'sustain': 1,
|
|
'release': 0.5,
|
|
'min': 20000,
|
|
'max': 20000
|
|
},
|
|
'filter': {
|
|
'Q': 6,
|
|
'type': 'lowpass',
|
|
'rolloff': -24
|
|
}
|
|
},
|
|
'modulator': {
|
|
'volume': -10,
|
|
'oscillator': { 'type': 'square' },
|
|
'envelope': {
|
|
'attack': 2,
|
|
'decay': 0,
|
|
'sustain': 1,
|
|
'release': 0.5
|
|
},
|
|
'filterEnvelope': {
|
|
'attack': 4,
|
|
'decay': 0.2,
|
|
'sustain': 0.5,
|
|
'release': 0.5,
|
|
'min': 20,
|
|
'max': 1500
|
|
},
|
|
'filter': {
|
|
'Q': 6,
|
|
'type': 'lowpass',
|
|
'rolloff': -24
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* trigger the attack portion of the note
|
|
*
|
|
* @param {Time} [time=now] the time the note will occur
|
|
* @param {NormalRange} [velocity=1] the velocity of the note
|
|
* @private
|
|
* @returns {Tone.AMSynth} this
|
|
*/
|
|
Tone.AMSynth.prototype._triggerEnvelopeAttack = function (time, velocity) {
|
|
//the port glide
|
|
time = this.toSeconds(time);
|
|
//the envelopes
|
|
this.carrier.envelope.triggerAttack(time, velocity);
|
|
this.modulator.envelope.triggerAttack(time);
|
|
this.carrier.filterEnvelope.triggerAttack(time);
|
|
this.modulator.filterEnvelope.triggerAttack(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* trigger the release portion of the note
|
|
*
|
|
* @param {Time} [time=now] the time the note will release
|
|
* @private
|
|
* @returns {Tone.AMSynth} this
|
|
*/
|
|
Tone.AMSynth.prototype._triggerEnvelopeRelease = function (time) {
|
|
this.carrier.triggerRelease(time);
|
|
this.modulator.triggerRelease(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.AMSynth} this
|
|
*/
|
|
Tone.AMSynth.prototype.dispose = function () {
|
|
Tone.Monophonic.prototype.dispose.call(this);
|
|
this._writable([
|
|
'carrier',
|
|
'modulator',
|
|
'frequency',
|
|
'harmonicity'
|
|
]);
|
|
this.carrier.dispose();
|
|
this.carrier = null;
|
|
this.modulator.dispose();
|
|
this.modulator = null;
|
|
this.frequency.dispose();
|
|
this.frequency = null;
|
|
this.harmonicity.dispose();
|
|
this.harmonicity = null;
|
|
this._modulationScale.dispose();
|
|
this._modulationScale = null;
|
|
this._modulationNode.disconnect();
|
|
this._modulationNode = null;
|
|
return this;
|
|
};
|
|
return Tone.AMSynth;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Tone.DrumSynth makes kick and tom sounds using a single oscillator
|
|
* with an amplitude envelope and frequency ramp.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Instrument}
|
|
* @param {Object} [options] the options available for the synth
|
|
* see defaults below
|
|
* @example
|
|
* var synth = new Tone.DrumSynth().toMaster();
|
|
* synth.triggerAttackRelease("C2", "8n");
|
|
*/
|
|
Tone.DrumSynth = function (options) {
|
|
options = this.defaultArg(options, Tone.DrumSynth.defaults);
|
|
Tone.Instrument.call(this, options);
|
|
/**
|
|
* The oscillator.
|
|
* @type {Tone.Oscillator}
|
|
*/
|
|
this.oscillator = new Tone.Oscillator(options.oscillator).start();
|
|
/**
|
|
* The envelope.
|
|
* @type {Tone.AmplitudeEnvelope}
|
|
*/
|
|
this.envelope = new Tone.AmplitudeEnvelope(options.envelope);
|
|
/**
|
|
* The number of octaves the pitch envelope ramps.
|
|
* @type {Positive}
|
|
*/
|
|
this.octaves = options.octaves;
|
|
/**
|
|
* The amount of time of the pitch decay.
|
|
* @type {Time}
|
|
*/
|
|
this.pitchDecay = options.pitchDecay;
|
|
this.oscillator.chain(this.envelope, this.output);
|
|
this._readOnly([
|
|
'oscillator',
|
|
'envelope'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.DrumSynth, Tone.Instrument);
|
|
/**
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.DrumSynth.defaults = {
|
|
'pitchDecay': 0.05,
|
|
'octaves': 10,
|
|
'oscillator': { 'type': 'sine' },
|
|
'envelope': {
|
|
'attack': 0.001,
|
|
'decay': 0.4,
|
|
'sustain': 0.01,
|
|
'release': 1.4,
|
|
'attackCurve': 'exponential'
|
|
}
|
|
};
|
|
/**
|
|
* trigger the attack. start the note, at the time with the velocity
|
|
*
|
|
* @param {string|string} note the note
|
|
* @param {Time} [time=now] the time, if not given is now
|
|
* @param {number} [velocity=1] velocity defaults to 1
|
|
* @returns {Tone.DrumSynth} this
|
|
* @example
|
|
* kick.triggerAttack(60);
|
|
*/
|
|
Tone.DrumSynth.prototype.triggerAttack = function (note, time, velocity) {
|
|
time = this.toSeconds(time);
|
|
note = this.toFrequency(note);
|
|
var maxNote = note * this.octaves;
|
|
this.oscillator.frequency.setValueAtTime(maxNote, time);
|
|
this.oscillator.frequency.exponentialRampToValueAtTime(note, time + this.toSeconds(this.pitchDecay));
|
|
this.envelope.triggerAttack(time, velocity);
|
|
return this;
|
|
};
|
|
/**
|
|
* trigger the release portion of the note
|
|
*
|
|
* @param {Time} [time=now] the time the note will release
|
|
* @returns {Tone.DrumSynth} this
|
|
*/
|
|
Tone.DrumSynth.prototype.triggerRelease = function (time) {
|
|
this.envelope.triggerRelease(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.DrumSynth} this
|
|
*/
|
|
Tone.DrumSynth.prototype.dispose = function () {
|
|
Tone.Instrument.prototype.dispose.call(this);
|
|
this._writable([
|
|
'oscillator',
|
|
'envelope'
|
|
]);
|
|
this.oscillator.dispose();
|
|
this.oscillator = null;
|
|
this.envelope.dispose();
|
|
this.envelope = null;
|
|
return this;
|
|
};
|
|
return Tone.DrumSynth;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class the DuoSynth is a monophonic synth composed of two
|
|
* MonoSynths run in parallel with control over the
|
|
* frequency ratio between the two voices and vibrato effect.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Monophonic}
|
|
* @param {Object} [options] the options available for the synth
|
|
* see defaults below
|
|
* @example
|
|
* var duoSynth = new Tone.DuoSynth().toMaster();
|
|
* duoSynth.triggerAttackRelease("C4", "2n");
|
|
*/
|
|
Tone.DuoSynth = function (options) {
|
|
options = this.defaultArg(options, Tone.DuoSynth.defaults);
|
|
Tone.Monophonic.call(this, options);
|
|
/**
|
|
* the first voice
|
|
* @type {Tone.MonoSynth}
|
|
*/
|
|
this.voice0 = new Tone.MonoSynth(options.voice0);
|
|
this.voice0.volume.value = -10;
|
|
/**
|
|
* the second voice
|
|
* @type {Tone.MonoSynth}
|
|
*/
|
|
this.voice1 = new Tone.MonoSynth(options.voice1);
|
|
this.voice1.volume.value = -10;
|
|
/**
|
|
* The vibrato LFO.
|
|
* @type {Tone.LFO}
|
|
* @private
|
|
*/
|
|
this._vibrato = new Tone.LFO(options.vibratoRate, -50, 50);
|
|
this._vibrato.start();
|
|
/**
|
|
* the vibrato frequency
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.vibratoRate = this._vibrato.frequency;
|
|
/**
|
|
* the vibrato gain
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._vibratoGain = this.context.createGain();
|
|
/**
|
|
* The amount of vibrato
|
|
* @type {Gain}
|
|
* @signal
|
|
*/
|
|
this.vibratoAmount = new Tone.Signal(this._vibratoGain.gain, Tone.Type.Gain);
|
|
this.vibratoAmount.value = options.vibratoAmount;
|
|
/**
|
|
* the delay before the vibrato starts
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._vibratoDelay = this.toSeconds(options.vibratoDelay);
|
|
/**
|
|
* the frequency control
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.frequency = new Tone.Signal(440, Tone.Type.Frequency);
|
|
/**
|
|
* the ratio between the two voices
|
|
* @type {Positive}
|
|
* @signal
|
|
*/
|
|
this.harmonicity = new Tone.Multiply(options.harmonicity);
|
|
this.harmonicity.units = Tone.Type.Positive;
|
|
//control the two voices frequency
|
|
this.frequency.connect(this.voice0.frequency);
|
|
this.frequency.chain(this.harmonicity, this.voice1.frequency);
|
|
this._vibrato.connect(this._vibratoGain);
|
|
this._vibratoGain.fan(this.voice0.detune, this.voice1.detune);
|
|
this.voice0.connect(this.output);
|
|
this.voice1.connect(this.output);
|
|
this._readOnly([
|
|
'voice0',
|
|
'voice1',
|
|
'frequency',
|
|
'vibratoAmount',
|
|
'vibratoRate'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.DuoSynth, Tone.Monophonic);
|
|
/**
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.DuoSynth.defaults = {
|
|
'vibratoAmount': 0.5,
|
|
'vibratoRate': 5,
|
|
'vibratoDelay': 1,
|
|
'harmonicity': 1.5,
|
|
'voice0': {
|
|
'volume': -10,
|
|
'portamento': 0,
|
|
'oscillator': { 'type': 'sine' },
|
|
'filterEnvelope': {
|
|
'attack': 0.01,
|
|
'decay': 0,
|
|
'sustain': 1,
|
|
'release': 0.5
|
|
},
|
|
'envelope': {
|
|
'attack': 0.01,
|
|
'decay': 0,
|
|
'sustain': 1,
|
|
'release': 0.5
|
|
}
|
|
},
|
|
'voice1': {
|
|
'volume': -10,
|
|
'portamento': 0,
|
|
'oscillator': { 'type': 'sine' },
|
|
'filterEnvelope': {
|
|
'attack': 0.01,
|
|
'decay': 0,
|
|
'sustain': 1,
|
|
'release': 0.5
|
|
},
|
|
'envelope': {
|
|
'attack': 0.01,
|
|
'decay': 0,
|
|
'sustain': 1,
|
|
'release': 0.5
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* start the attack portion of the envelopes
|
|
*
|
|
* @param {Time} [time=now] the time the attack should start
|
|
* @param {NormalRange} [velocity=1] the velocity of the note (0-1)
|
|
* @returns {Tone.DuoSynth} this
|
|
* @private
|
|
*/
|
|
Tone.DuoSynth.prototype._triggerEnvelopeAttack = function (time, velocity) {
|
|
time = this.toSeconds(time);
|
|
this.voice0.envelope.triggerAttack(time, velocity);
|
|
this.voice1.envelope.triggerAttack(time, velocity);
|
|
this.voice0.filterEnvelope.triggerAttack(time);
|
|
this.voice1.filterEnvelope.triggerAttack(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* start the release portion of the envelopes
|
|
*
|
|
* @param {Time} [time=now] the time the release should start
|
|
* @returns {Tone.DuoSynth} this
|
|
* @private
|
|
*/
|
|
Tone.DuoSynth.prototype._triggerEnvelopeRelease = function (time) {
|
|
this.voice0.triggerRelease(time);
|
|
this.voice1.triggerRelease(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.DuoSynth} this
|
|
*/
|
|
Tone.DuoSynth.prototype.dispose = function () {
|
|
Tone.Monophonic.prototype.dispose.call(this);
|
|
this._writable([
|
|
'voice0',
|
|
'voice1',
|
|
'frequency',
|
|
'vibratoAmount',
|
|
'vibratoRate'
|
|
]);
|
|
this.voice0.dispose();
|
|
this.voice0 = null;
|
|
this.voice1.dispose();
|
|
this.voice1 = null;
|
|
this.frequency.dispose();
|
|
this.frequency = null;
|
|
this._vibrato.dispose();
|
|
this._vibrato = null;
|
|
this._vibratoGain.disconnect();
|
|
this._vibratoGain = null;
|
|
this.harmonicity.dispose();
|
|
this.harmonicity = null;
|
|
this.vibratoAmount.dispose();
|
|
this.vibratoAmount = null;
|
|
this.vibratoRate = null;
|
|
return this;
|
|
};
|
|
return Tone.DuoSynth;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class FMSynth is composed of two Tone.MonoSynths where one Tone.MonoSynth modulates
|
|
* the frequency of a second Tone.MonoSynth. A lot of spectral content
|
|
* can be explored using the modulationIndex parameter. Read more about
|
|
* Frequency Modulation Synthesis on <a href="http://www.soundonsound.com/sos/apr00/articles/synthsecrets.htm">SoundOnSound</a>
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Monophonic}
|
|
* @param {Object} [options] the options available for the synth
|
|
* see defaults below
|
|
* @example
|
|
* var fmSynth = new Tone.FMSynth().toMaster();
|
|
* fmSynth.triggerAttackRelease("C5", "4n");
|
|
*/
|
|
Tone.FMSynth = function (options) {
|
|
options = this.defaultArg(options, Tone.FMSynth.defaults);
|
|
Tone.Monophonic.call(this, options);
|
|
/**
|
|
* The carrier voice.
|
|
* @type {Tone.MonoSynth}
|
|
*/
|
|
this.carrier = new Tone.MonoSynth(options.carrier);
|
|
this.carrier.volume.value = -10;
|
|
/**
|
|
* The modulator voice.
|
|
* @type {Tone.MonoSynth}
|
|
*/
|
|
this.modulator = new Tone.MonoSynth(options.modulator);
|
|
this.modulator.volume.value = -10;
|
|
/**
|
|
* the frequency control
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.frequency = new Tone.Signal(440, Tone.Type.Frequency);
|
|
/**
|
|
* The ratio between the two carrier and the modulator.
|
|
* @type {Positive}
|
|
* @signal
|
|
*/
|
|
this.harmonicity = new Tone.Multiply(options.harmonicity);
|
|
this.harmonicity.units = Tone.Type.Positive;
|
|
/**
|
|
* The modulation index which is in essence the depth or amount of the modulation. In other terms it is the
|
|
* ratio of the frequency of the modulating signal (mf) to the amplitude of the
|
|
* modulating signal (ma) -- as in ma/mf.
|
|
* @type {Positive}
|
|
* @signal
|
|
*/
|
|
this.modulationIndex = new Tone.Multiply(options.modulationIndex);
|
|
this.modulationIndex.units = Tone.Type.Positive;
|
|
/**
|
|
* the node where the modulation happens
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._modulationNode = this.context.createGain();
|
|
//control the two voices frequency
|
|
this.frequency.connect(this.carrier.frequency);
|
|
this.frequency.chain(this.harmonicity, this.modulator.frequency);
|
|
this.frequency.chain(this.modulationIndex, this._modulationNode);
|
|
this.modulator.connect(this._modulationNode.gain);
|
|
this._modulationNode.gain.value = 0;
|
|
this._modulationNode.connect(this.carrier.frequency);
|
|
this.carrier.connect(this.output);
|
|
this._readOnly([
|
|
'carrier',
|
|
'modulator',
|
|
'frequency',
|
|
'harmonicity',
|
|
'modulationIndex'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.FMSynth, Tone.Monophonic);
|
|
/**
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.FMSynth.defaults = {
|
|
'harmonicity': 3,
|
|
'modulationIndex': 10,
|
|
'carrier': {
|
|
'volume': -10,
|
|
'portamento': 0,
|
|
'oscillator': { 'type': 'sine' },
|
|
'envelope': {
|
|
'attack': 0.01,
|
|
'decay': 0,
|
|
'sustain': 1,
|
|
'release': 0.5
|
|
},
|
|
'filterEnvelope': {
|
|
'attack': 0.01,
|
|
'decay': 0,
|
|
'sustain': 1,
|
|
'release': 0.5,
|
|
'min': 20000,
|
|
'max': 20000
|
|
}
|
|
},
|
|
'modulator': {
|
|
'volume': -10,
|
|
'portamento': 0,
|
|
'oscillator': { 'type': 'triangle' },
|
|
'envelope': {
|
|
'attack': 0.01,
|
|
'decay': 0,
|
|
'sustain': 1,
|
|
'release': 0.5
|
|
},
|
|
'filterEnvelope': {
|
|
'attack': 0.01,
|
|
'decay': 0,
|
|
'sustain': 1,
|
|
'release': 0.5,
|
|
'min': 20000,
|
|
'max': 20000
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* trigger the attack portion of the note
|
|
*
|
|
* @param {Time} [time=now] the time the note will occur
|
|
* @param {number} [velocity=1] the velocity of the note
|
|
* @returns {Tone.FMSynth} this
|
|
* @private
|
|
*/
|
|
Tone.FMSynth.prototype._triggerEnvelopeAttack = function (time, velocity) {
|
|
//the port glide
|
|
time = this.toSeconds(time);
|
|
//the envelopes
|
|
this.carrier.envelope.triggerAttack(time, velocity);
|
|
this.modulator.envelope.triggerAttack(time);
|
|
this.carrier.filterEnvelope.triggerAttack(time);
|
|
this.modulator.filterEnvelope.triggerAttack(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* trigger the release portion of the note
|
|
*
|
|
* @param {Time} [time=now] the time the note will release
|
|
* @returns {Tone.FMSynth} this
|
|
* @private
|
|
*/
|
|
Tone.FMSynth.prototype._triggerEnvelopeRelease = function (time) {
|
|
this.carrier.triggerRelease(time);
|
|
this.modulator.triggerRelease(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.FMSynth} this
|
|
*/
|
|
Tone.FMSynth.prototype.dispose = function () {
|
|
Tone.Monophonic.prototype.dispose.call(this);
|
|
this._writable([
|
|
'carrier',
|
|
'modulator',
|
|
'frequency',
|
|
'harmonicity',
|
|
'modulationIndex'
|
|
]);
|
|
this.carrier.dispose();
|
|
this.carrier = null;
|
|
this.modulator.dispose();
|
|
this.modulator = null;
|
|
this.frequency.dispose();
|
|
this.frequency = null;
|
|
this.modulationIndex.dispose();
|
|
this.modulationIndex = null;
|
|
this.harmonicity.dispose();
|
|
this.harmonicity = null;
|
|
this._modulationNode.disconnect();
|
|
this._modulationNode = null;
|
|
return this;
|
|
};
|
|
return Tone.FMSynth;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Noise generator.
|
|
* Uses looped noise buffers to save on performance.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Source}
|
|
* @param {string} type the noise type (white|pink|brown)
|
|
* @example
|
|
* var noise = new Tone.Noise("pink");
|
|
*/
|
|
Tone.Noise = function () {
|
|
var options = this.optionsObject(arguments, ['type'], Tone.Noise.defaults);
|
|
Tone.Source.call(this, options);
|
|
/**
|
|
* @private
|
|
* @type {AudioBufferSourceNode}
|
|
*/
|
|
this._source = null;
|
|
/**
|
|
* the buffer
|
|
* @private
|
|
* @type {AudioBuffer}
|
|
*/
|
|
this._buffer = null;
|
|
this.type = options.type;
|
|
};
|
|
Tone.extend(Tone.Noise, Tone.Source);
|
|
/**
|
|
* the default parameters
|
|
*
|
|
* @static
|
|
* @const
|
|
* @type {Object}
|
|
*/
|
|
Tone.Noise.defaults = { 'type': 'white' };
|
|
/**
|
|
* The type of the noise. Can be "white", "brown", or "pink".
|
|
* @memberOf Tone.Noise#
|
|
* @type {string}
|
|
* @name type
|
|
* @example
|
|
* noise.type = "white";
|
|
*/
|
|
Object.defineProperty(Tone.Noise.prototype, 'type', {
|
|
get: function () {
|
|
if (this._buffer === _whiteNoise) {
|
|
return 'white';
|
|
} else if (this._buffer === _brownNoise) {
|
|
return 'brown';
|
|
} else if (this._buffer === _pinkNoise) {
|
|
return 'pink';
|
|
}
|
|
},
|
|
set: function (type) {
|
|
if (this.type !== type) {
|
|
switch (type) {
|
|
case 'white':
|
|
this._buffer = _whiteNoise;
|
|
break;
|
|
case 'pink':
|
|
this._buffer = _pinkNoise;
|
|
break;
|
|
case 'brown':
|
|
this._buffer = _brownNoise;
|
|
break;
|
|
default:
|
|
this._buffer = _whiteNoise;
|
|
}
|
|
//if it's playing, stop and restart it
|
|
if (this.state === Tone.State.Started) {
|
|
var now = this.now() + this.bufferTime;
|
|
//remove the listener
|
|
this._source.onended = undefined;
|
|
this._stop(now);
|
|
this._start(now);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
/**
|
|
* internal start method
|
|
*
|
|
* @param {Time} time
|
|
* @private
|
|
*/
|
|
Tone.Noise.prototype._start = function (time) {
|
|
this._source = this.context.createBufferSource();
|
|
this._source.buffer = this._buffer;
|
|
this._source.loop = true;
|
|
this.connectSeries(this._source, this.output);
|
|
this._source.start(this.toSeconds(time));
|
|
this._source.onended = this.onended;
|
|
};
|
|
/**
|
|
* internal stop method
|
|
*
|
|
* @param {Time} time
|
|
* @private
|
|
*/
|
|
Tone.Noise.prototype._stop = function (time) {
|
|
if (this._source) {
|
|
this._source.stop(this.toSeconds(time));
|
|
}
|
|
};
|
|
/**
|
|
* Dispose all the components.
|
|
* @returns {Tone.Noise} this
|
|
*/
|
|
Tone.Noise.prototype.dispose = function () {
|
|
Tone.Source.prototype.dispose.call(this);
|
|
if (this._source !== null) {
|
|
this._source.disconnect();
|
|
this._source = null;
|
|
}
|
|
this._buffer = null;
|
|
return this;
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// THE BUFFERS
|
|
// borrowed heavily from http://noisehack.com/generate-noise-web-audio-api/
|
|
///////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* static noise buffers
|
|
*
|
|
* @static
|
|
* @private
|
|
* @type {AudioBuffer}
|
|
*/
|
|
var _pinkNoise = null, _brownNoise = null, _whiteNoise = null;
|
|
Tone._initAudioContext(function (audioContext) {
|
|
var sampleRate = audioContext.sampleRate;
|
|
//four seconds per buffer
|
|
var bufferLength = sampleRate * 4;
|
|
//fill the buffers
|
|
_pinkNoise = function () {
|
|
var buffer = audioContext.createBuffer(2, bufferLength, sampleRate);
|
|
for (var channelNum = 0; channelNum < buffer.numberOfChannels; channelNum++) {
|
|
var channel = buffer.getChannelData(channelNum);
|
|
var b0, b1, b2, b3, b4, b5, b6;
|
|
b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0;
|
|
for (var i = 0; i < bufferLength; i++) {
|
|
var white = Math.random() * 2 - 1;
|
|
b0 = 0.99886 * b0 + white * 0.0555179;
|
|
b1 = 0.99332 * b1 + white * 0.0750759;
|
|
b2 = 0.969 * b2 + white * 0.153852;
|
|
b3 = 0.8665 * b3 + white * 0.3104856;
|
|
b4 = 0.55 * b4 + white * 0.5329522;
|
|
b5 = -0.7616 * b5 - white * 0.016898;
|
|
channel[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
|
|
channel[i] *= 0.11;
|
|
// (roughly) compensate for gain
|
|
b6 = white * 0.115926;
|
|
}
|
|
}
|
|
return buffer;
|
|
}();
|
|
_brownNoise = function () {
|
|
var buffer = audioContext.createBuffer(2, bufferLength, sampleRate);
|
|
for (var channelNum = 0; channelNum < buffer.numberOfChannels; channelNum++) {
|
|
var channel = buffer.getChannelData(channelNum);
|
|
var lastOut = 0;
|
|
for (var i = 0; i < bufferLength; i++) {
|
|
var white = Math.random() * 2 - 1;
|
|
channel[i] = (lastOut + 0.02 * white) / 1.02;
|
|
lastOut = channel[i];
|
|
channel[i] *= 3.5; // (roughly) compensate for gain
|
|
}
|
|
}
|
|
return buffer;
|
|
}();
|
|
_whiteNoise = function () {
|
|
var buffer = audioContext.createBuffer(2, bufferLength, sampleRate);
|
|
for (var channelNum = 0; channelNum < buffer.numberOfChannels; channelNum++) {
|
|
var channel = buffer.getChannelData(channelNum);
|
|
for (var i = 0; i < bufferLength; i++) {
|
|
channel[i] = Math.random() * 2 - 1;
|
|
}
|
|
}
|
|
return buffer;
|
|
}();
|
|
});
|
|
return Tone.Noise;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Tone.NoiseSynth is composed of a noise generator, one filter, and two envelopes.
|
|
* The amplitude of the Tone.Noise and the cutoff frequency of the
|
|
* Tone.Filter are controlled by Tone.Envelopes.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Instrument}
|
|
* @param {Object} [options] the options available for the synth
|
|
* see defaults below
|
|
* @example
|
|
* var noiseSynth = new Tone.NoiseSynth().toMaster();
|
|
* noiseSynth.triggerAttackRelease("8n");
|
|
*/
|
|
Tone.NoiseSynth = function (options) {
|
|
//get the defaults
|
|
options = this.defaultArg(options, Tone.NoiseSynth.defaults);
|
|
Tone.Instrument.call(this);
|
|
/**
|
|
* The noise source. Set the type by setting
|
|
* `noiseSynth.noise.type`.
|
|
* @type {Tone.Noise}
|
|
*/
|
|
this.noise = new Tone.Noise();
|
|
/**
|
|
* The filter .
|
|
* @type {Tone.Filter}
|
|
*/
|
|
this.filter = new Tone.Filter(options.filter);
|
|
/**
|
|
* The filter envelope.
|
|
* @type {Tone.ScaledEnvelope}
|
|
*/
|
|
this.filterEnvelope = new Tone.ScaledEnvelope(options.filterEnvelope);
|
|
/**
|
|
* The amplitude envelope.
|
|
* @type {Tone.AmplitudeEnvelope}
|
|
*/
|
|
this.envelope = new Tone.AmplitudeEnvelope(options.envelope);
|
|
//connect the noise to the output
|
|
this.noise.chain(this.filter, this.envelope, this.output);
|
|
//start the noise
|
|
this.noise.start();
|
|
//connect the filter envelope
|
|
this.filterEnvelope.connect(this.filter.frequency);
|
|
this._readOnly([
|
|
'noise',
|
|
'filter',
|
|
'filterEnvelope',
|
|
'envelope'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.NoiseSynth, Tone.Instrument);
|
|
/**
|
|
* @const
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.NoiseSynth.defaults = {
|
|
'noise': { 'type': 'white' },
|
|
'filter': {
|
|
'Q': 6,
|
|
'type': 'highpass',
|
|
'rolloff': -24
|
|
},
|
|
'envelope': {
|
|
'attack': 0.005,
|
|
'decay': 0.1,
|
|
'sustain': 0
|
|
},
|
|
'filterEnvelope': {
|
|
'attack': 0.06,
|
|
'decay': 0.2,
|
|
'sustain': 0,
|
|
'release': 2,
|
|
'min': 20,
|
|
'max': 4000,
|
|
'exponent': 2
|
|
}
|
|
};
|
|
/**
|
|
* start the attack portion of the envelope
|
|
* @param {Time} [time=now] the time the attack should start
|
|
* @param {number} [velocity=1] the velocity of the note (0-1)
|
|
* @returns {Tone.NoiseSynth} this
|
|
*/
|
|
Tone.NoiseSynth.prototype.triggerAttack = function (time, velocity) {
|
|
//the envelopes
|
|
this.envelope.triggerAttack(time, velocity);
|
|
this.filterEnvelope.triggerAttack(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* start the release portion of the envelope
|
|
* @param {Time} [time=now] the time the release should start
|
|
* @returns {Tone.NoiseSynth} this
|
|
*/
|
|
Tone.NoiseSynth.prototype.triggerRelease = function (time) {
|
|
this.envelope.triggerRelease(time);
|
|
this.filterEnvelope.triggerRelease(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* trigger the attack and then the release
|
|
* @param {Time} duration the duration of the note
|
|
* @param {Time} [time=now] the time of the attack
|
|
* @param {number} [velocity=1] the velocity
|
|
* @returns {Tone.NoiseSynth} this
|
|
*/
|
|
Tone.NoiseSynth.prototype.triggerAttackRelease = function (duration, time, velocity) {
|
|
time = this.toSeconds(time);
|
|
duration = this.toSeconds(duration);
|
|
this.triggerAttack(time, velocity);
|
|
this.triggerRelease(time + duration);
|
|
return this;
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.NoiseSynth} this
|
|
*/
|
|
Tone.NoiseSynth.prototype.dispose = function () {
|
|
Tone.Instrument.prototype.dispose.call(this);
|
|
this._writable([
|
|
'noise',
|
|
'filter',
|
|
'filterEnvelope',
|
|
'envelope'
|
|
]);
|
|
this.noise.dispose();
|
|
this.noise = null;
|
|
this.envelope.dispose();
|
|
this.envelope = null;
|
|
this.filterEnvelope.dispose();
|
|
this.filterEnvelope = null;
|
|
this.filter.dispose();
|
|
this.filter = null;
|
|
return this;
|
|
};
|
|
return Tone.NoiseSynth;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Karplus-String string synthesis. Often out of tune.
|
|
* Will change when the AudioWorkerNode is available across
|
|
* browsers.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Instrument}
|
|
* @param {Object} [options] see the defaults
|
|
* @example
|
|
* var plucky = new Tone.PluckSynth().toMaster();
|
|
* plucky.triggerAttackRelease("C4", "8n");
|
|
*/
|
|
Tone.PluckSynth = function (options) {
|
|
options = this.defaultArg(options, Tone.PluckSynth.defaults);
|
|
Tone.Instrument.call(this);
|
|
/**
|
|
* @type {Tone.Noise}
|
|
* @private
|
|
*/
|
|
this._noise = new Tone.Noise('pink');
|
|
/**
|
|
* The amount of noise at the attack.
|
|
* Nominal range of [0.1, 20]
|
|
* @type {number}
|
|
*/
|
|
this.attackNoise = 1;
|
|
/**
|
|
* the LFCF
|
|
* @type {Tone.LowpassCombFilter}
|
|
* @private
|
|
*/
|
|
this._lfcf = new Tone.LowpassCombFilter({
|
|
'resonance': options.resonance,
|
|
'dampening': options.dampening
|
|
});
|
|
/**
|
|
* the resonance control
|
|
* @type {NormalRange}
|
|
* @signal
|
|
*/
|
|
this.resonance = this._lfcf.resonance;
|
|
/**
|
|
* the dampening control. i.e. the lowpass filter frequency of the comb filter
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.dampening = this._lfcf.dampening;
|
|
//connections
|
|
this._noise.connect(this._lfcf);
|
|
this._lfcf.connect(this.output);
|
|
this._readOnly([
|
|
'resonance',
|
|
'dampening'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.PluckSynth, Tone.Instrument);
|
|
/**
|
|
* @static
|
|
* @const
|
|
* @type {Object}
|
|
*/
|
|
Tone.PluckSynth.defaults = {
|
|
'attackNoise': 1,
|
|
'dampening': 4000,
|
|
'resonance': 0.9
|
|
};
|
|
/**
|
|
* trigger the attack portion
|
|
* @param {string|number} note the note name or frequency
|
|
* @param {Time} [time=now] the time of the note
|
|
* @returns {Tone.PluckSynth} this
|
|
*/
|
|
Tone.PluckSynth.prototype.triggerAttack = function (note, time) {
|
|
note = this.toFrequency(note);
|
|
time = this.toSeconds(time);
|
|
var delayAmount = 1 / note;
|
|
this._lfcf.delayTime.setValueAtTime(delayAmount, time);
|
|
this._noise.start(time);
|
|
this._noise.stop(time + delayAmount * this.attackNoise);
|
|
return this;
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.PluckSynth} this
|
|
*/
|
|
Tone.PluckSynth.prototype.dispose = function () {
|
|
Tone.Instrument.prototype.dispose.call(this);
|
|
this._noise.dispose();
|
|
this._lfcf.dispose();
|
|
this._noise = null;
|
|
this._lfcf = null;
|
|
this._writable([
|
|
'resonance',
|
|
'dampening'
|
|
]);
|
|
this.dampening = null;
|
|
this.resonance = null;
|
|
return this;
|
|
};
|
|
return Tone.PluckSynth;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Tone.PolySynth handles voice creation and allocation for any
|
|
* instruments passed in as the second paramter.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Instrument}
|
|
* @param {number|Object} [polyphony=4] the number of voices to create
|
|
* @param {function} [voice=Tone.MonoSynth] the constructor of the voices
|
|
* uses Tone.MonoSynth by default
|
|
* @example
|
|
* //a polysynth composed of 6 Voices of MonoSynth
|
|
* var synth = new Tone.PolySynth(6, Tone.MonoSynth).toMaster();
|
|
* //set the attributes using the set interface
|
|
* synth.set("detune", -1200);
|
|
* //play a chord
|
|
* synth.triggerAttackRelease(["C4", "E4", "A4"], "4n");
|
|
*/
|
|
Tone.PolySynth = function () {
|
|
Tone.Instrument.call(this);
|
|
var options = this.optionsObject(arguments, [
|
|
'polyphony',
|
|
'voice'
|
|
], Tone.PolySynth.defaults);
|
|
/**
|
|
* the array of voices
|
|
* @type {Array}
|
|
*/
|
|
this.voices = new Array(options.polyphony);
|
|
/**
|
|
* the queue of free voices
|
|
* @private
|
|
* @type {Array}
|
|
*/
|
|
this._freeVoices = [];
|
|
/**
|
|
* keeps track of which notes are down
|
|
* @private
|
|
* @type {Object}
|
|
*/
|
|
this._activeVoices = {};
|
|
//create the voices
|
|
for (var i = 0; i < options.polyphony; i++) {
|
|
var v = new options.voice(arguments[2], arguments[3]);
|
|
this.voices[i] = v;
|
|
v.connect(this.output);
|
|
}
|
|
//make a copy of the voices
|
|
this._freeVoices = this.voices.slice(0); //get the prototypes and properties
|
|
};
|
|
Tone.extend(Tone.PolySynth, Tone.Instrument);
|
|
/**
|
|
* the defaults
|
|
* @const
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.PolySynth.defaults = {
|
|
'polyphony': 4,
|
|
'voice': Tone.MonoSynth
|
|
};
|
|
/**
|
|
* Pull properties from the
|
|
*/
|
|
/**
|
|
* trigger the attack
|
|
* @param {string|number|Object|Array} value the value of the note(s) to start.
|
|
* if the value is an array, it will iterate
|
|
* over the array to play each of the notes
|
|
* @param {Time} [time=now] the start time of the note
|
|
* @param {number} [velocity=1] the velocity of the note
|
|
* @returns {Tone.PolySynth} this
|
|
*/
|
|
Tone.PolySynth.prototype.triggerAttack = function (value, time, velocity) {
|
|
if (!Array.isArray(value)) {
|
|
value = [value];
|
|
}
|
|
for (var i = 0; i < value.length; i++) {
|
|
var val = value[i];
|
|
var stringified = JSON.stringify(val);
|
|
if (this._activeVoices[stringified]) {
|
|
this._activeVoices[stringified].triggerAttack(val, time, velocity);
|
|
} else if (this._freeVoices.length > 0) {
|
|
var voice = this._freeVoices.shift();
|
|
voice.triggerAttack(val, time, velocity);
|
|
this._activeVoices[stringified] = voice;
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* trigger the attack and release after the specified duration
|
|
*
|
|
* @param {string|number|Object|Array} value the note(s).
|
|
* if the value is an array, it will iterate
|
|
* over the array to play each of the notes
|
|
* @param {Time} duration the duration of the note
|
|
* @param {Time} [time=now] if no time is given, defaults to now
|
|
* @param {number} [velocity=1] the velocity of the attack (0-1)
|
|
* @returns {Tone.PolySynth} this
|
|
*/
|
|
Tone.PolySynth.prototype.triggerAttackRelease = function (value, duration, time, velocity) {
|
|
time = this.toSeconds(time);
|
|
this.triggerAttack(value, time, velocity);
|
|
this.triggerRelease(value, time + this.toSeconds(duration));
|
|
return this;
|
|
};
|
|
/**
|
|
* trigger the release of a note
|
|
* @param {string|number|Object|Array} value the value of the note(s) to release.
|
|
* if the value is an array, it will iterate
|
|
* over the array to play each of the notes
|
|
* @param {Time} [time=now] the release time of the note
|
|
* @returns {Tone.PolySynth} this
|
|
*/
|
|
Tone.PolySynth.prototype.triggerRelease = function (value, time) {
|
|
if (!Array.isArray(value)) {
|
|
value = [value];
|
|
}
|
|
for (var i = 0; i < value.length; i++) {
|
|
//get the voice
|
|
var stringified = JSON.stringify(value[i]);
|
|
var voice = this._activeVoices[stringified];
|
|
if (voice) {
|
|
voice.triggerRelease(time);
|
|
this._freeVoices.push(voice);
|
|
delete this._activeVoices[stringified];
|
|
voice = null;
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* set the options on all of the voices
|
|
* @param {Object|string} params
|
|
* @param {number=} value
|
|
* @param {Time=} rampTime
|
|
* @returns {Tone.PolySynth} this
|
|
*/
|
|
Tone.PolySynth.prototype.set = function (params, value, rampTime) {
|
|
for (var i = 0; i < this.voices.length; i++) {
|
|
this.voices[i].set(params, value, rampTime);
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* get a group of parameters
|
|
* @param {Array=} params the parameters to get, otherwise will return
|
|
* all available.
|
|
*/
|
|
Tone.PolySynth.prototype.get = function (params) {
|
|
return this.voices[0].get(params);
|
|
};
|
|
/**
|
|
* @param {string} presetName the preset name
|
|
* @returns {Tone.PolySynth} this
|
|
*/
|
|
Tone.PolySynth.prototype.setPreset = function (presetName) {
|
|
for (var i = 0; i < this.voices.length; i++) {
|
|
this.voices[i].setPreset(presetName);
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.PolySynth} this
|
|
*/
|
|
Tone.PolySynth.prototype.dispose = function () {
|
|
Tone.Instrument.prototype.dispose.call(this);
|
|
for (var i = 0; i < this.voices.length; i++) {
|
|
this.voices[i].dispose();
|
|
this.voices[i] = null;
|
|
}
|
|
this.voices = null;
|
|
this._activeVoices = null;
|
|
this._freeVoices = null;
|
|
return this;
|
|
};
|
|
return Tone.PolySynth;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Audio file player with start, loop, stop.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Source}
|
|
* @param {string|AudioBuffer} url Either the AudioBuffer or the url from
|
|
* which to load the AudioBuffer
|
|
* @param {function=} onload The function to invoke when the buffer is loaded.
|
|
* Recommended to use Tone.Buffer.onload instead.
|
|
* @example
|
|
* var player = new Tone.Player("./path/to/sample.mp3");
|
|
*/
|
|
Tone.Player = function () {
|
|
var options = this.optionsObject(arguments, [
|
|
'url',
|
|
'onload'
|
|
], Tone.Player.defaults);
|
|
Tone.Source.call(this, options);
|
|
/**
|
|
* @private
|
|
* @type {AudioBufferSourceNode}
|
|
*/
|
|
this._source = null;
|
|
/**
|
|
* If the file should play as soon
|
|
* as the buffer is loaded.
|
|
* @type {boolean}
|
|
*/
|
|
this.autostart = options.autostart;
|
|
/**
|
|
* the buffer
|
|
* @private
|
|
* @type {Tone.Buffer}
|
|
*/
|
|
this._buffer = new Tone.Buffer({
|
|
'url': options.url,
|
|
'onload': this._onload.bind(this, options.onload),
|
|
'reverse': options.reverse
|
|
});
|
|
/**
|
|
* if the buffer should loop once it's over
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
this._loop = options.loop;
|
|
/**
|
|
* if 'loop' is true, the loop will start at this position
|
|
* @type {Time}
|
|
* @private
|
|
*/
|
|
this._loopStart = options.loopStart;
|
|
/**
|
|
* if 'loop' is true, the loop will end at this position
|
|
* @type {Time}
|
|
* @private
|
|
*/
|
|
this._loopEnd = options.loopEnd;
|
|
/**
|
|
* the playback rate
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
this._playbackRate = options.playbackRate;
|
|
/**
|
|
* Enabling retrigger will allow a player to be restarted
|
|
* before the the previous 'start' is done playing.
|
|
* @type {boolean}
|
|
*/
|
|
this.retrigger = options.retrigger;
|
|
};
|
|
Tone.extend(Tone.Player, Tone.Source);
|
|
/**
|
|
* the default parameters
|
|
* @static
|
|
* @const
|
|
* @type {Object}
|
|
*/
|
|
Tone.Player.defaults = {
|
|
'onload': Tone.noOp,
|
|
'playbackRate': 1,
|
|
'loop': false,
|
|
'autostart': false,
|
|
'loopStart': 0,
|
|
'loopEnd': 0,
|
|
'retrigger': false,
|
|
'reverse': false
|
|
};
|
|
/**
|
|
* Load the audio file as an audio buffer.
|
|
* Decodes the audio asynchronously and invokes
|
|
* the callback once the audio buffer loads.
|
|
* Note: this does not need to be called, if a url
|
|
* was passed in to the constructor. Only use this
|
|
* if you want to manually load a new url.
|
|
* @param {string} url The url of the buffer to load.
|
|
* filetype support depends on the
|
|
* browser.
|
|
* @param {function(Tone.Player)=} callback
|
|
* @returns {Tone.Player} this
|
|
*/
|
|
Tone.Player.prototype.load = function (url, callback) {
|
|
this._buffer.load(url, this._onload.bind(this, callback));
|
|
return this;
|
|
};
|
|
/**
|
|
* Internal callback when the buffer is loaded.
|
|
* @private
|
|
*/
|
|
Tone.Player.prototype._onload = function (callback) {
|
|
callback(this);
|
|
if (this.autostart) {
|
|
this.start();
|
|
}
|
|
};
|
|
/**
|
|
* play the buffer between the desired positions
|
|
*
|
|
* @private
|
|
* @param {Time} [startTime=now] when the player should start.
|
|
* @param {Time} [offset=0] the offset from the beginning of the sample
|
|
* to start at.
|
|
* @param {Time=} duration how long the sample should play. If no duration
|
|
* is given, it will default to the full length
|
|
* of the sample (minus any offset)
|
|
* @returns {Tone.Player} this
|
|
*/
|
|
Tone.Player.prototype._start = function (startTime, offset, duration) {
|
|
if (this._buffer.loaded) {
|
|
//if it's a loop the default offset is the loopstart point
|
|
if (this._loop) {
|
|
offset = this.defaultArg(offset, this._loopStart);
|
|
} else {
|
|
//otherwise the default offset is 0
|
|
offset = this.defaultArg(offset, 0);
|
|
}
|
|
offset = this.toSeconds(offset);
|
|
duration = this.defaultArg(duration, this._buffer.duration - offset);
|
|
//the values in seconds
|
|
startTime = this.toSeconds(startTime);
|
|
duration = this.toSeconds(duration);
|
|
//make the source
|
|
this._source = this.context.createBufferSource();
|
|
this._source.buffer = this._buffer.get();
|
|
//set the looping properties
|
|
if (this._loop) {
|
|
this._source.loop = this._loop;
|
|
this._source.loopStart = this.toSeconds(this._loopStart);
|
|
this._source.loopEnd = this.toSeconds(this._loopEnd);
|
|
// this fixes a bug in chrome 42 that breaks looping
|
|
// https://code.google.com/p/chromium/issues/detail?id=457099
|
|
duration = 65536;
|
|
} else {
|
|
this._nextStop = startTime + duration;
|
|
}
|
|
//and other properties
|
|
this._source.playbackRate.value = this._playbackRate;
|
|
this._source.onended = this.onended;
|
|
this._source.connect(this.output);
|
|
//start it
|
|
this._source.start(startTime, offset, duration);
|
|
} else {
|
|
throw Error('tried to start Player before the buffer was loaded');
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* Stop playback.
|
|
* @private
|
|
* @param {Time} [time=now]
|
|
* @returns {Tone.Player} this
|
|
*/
|
|
Tone.Player.prototype._stop = function (time) {
|
|
if (this._source) {
|
|
this._source.stop(this.toSeconds(time));
|
|
this._source = null;
|
|
}
|
|
return this;
|
|
};
|
|
/**
|
|
* Set the loop start and end. Will only loop if loop is
|
|
* set to true.
|
|
* @param {Time} loopStart The loop end time
|
|
* @param {Time} loopEnd The loop end time
|
|
* @returns {Tone.Player} this
|
|
* @example
|
|
* player.setLoopPoints(0.2, 0.3);
|
|
* player.loop = true;
|
|
*/
|
|
Tone.Player.prototype.setLoopPoints = function (loopStart, loopEnd) {
|
|
this.loopStart = loopStart;
|
|
this.loopEnd = loopEnd;
|
|
return this;
|
|
};
|
|
/**
|
|
* If loop is true, the loop will start at this position.
|
|
* @memberOf Tone.Player#
|
|
* @type {Time}
|
|
* @name loopStart
|
|
*/
|
|
Object.defineProperty(Tone.Player.prototype, 'loopStart', {
|
|
get: function () {
|
|
return this._loopStart;
|
|
},
|
|
set: function (loopStart) {
|
|
this._loopStart = loopStart;
|
|
if (this._source) {
|
|
this._source.loopStart = this.toSeconds(loopStart);
|
|
}
|
|
}
|
|
});
|
|
/**
|
|
* If loop is true, the loop will end at this position.
|
|
* @memberOf Tone.Player#
|
|
* @type {Time}
|
|
* @name loopEnd
|
|
*/
|
|
Object.defineProperty(Tone.Player.prototype, 'loopEnd', {
|
|
get: function () {
|
|
return this._loopEnd;
|
|
},
|
|
set: function (loopEnd) {
|
|
this._loopEnd = loopEnd;
|
|
if (this._source) {
|
|
this._source.loopEnd = this.toSeconds(loopEnd);
|
|
}
|
|
}
|
|
});
|
|
/**
|
|
* The audio buffer belonging to the player.
|
|
* @memberOf Tone.Player#
|
|
* @type {AudioBuffer}
|
|
* @name buffer
|
|
*/
|
|
Object.defineProperty(Tone.Player.prototype, 'buffer', {
|
|
get: function () {
|
|
return this._buffer;
|
|
},
|
|
set: function (buffer) {
|
|
this._buffer.set(buffer);
|
|
}
|
|
});
|
|
/**
|
|
* If the buffer should loop once it's over.
|
|
* @memberOf Tone.Player#
|
|
* @type {boolean}
|
|
* @name loop
|
|
*/
|
|
Object.defineProperty(Tone.Player.prototype, 'loop', {
|
|
get: function () {
|
|
return this._loop;
|
|
},
|
|
set: function (loop) {
|
|
this._loop = loop;
|
|
if (this._source) {
|
|
this._source.loop = loop;
|
|
}
|
|
}
|
|
});
|
|
/**
|
|
* The playback speed. 1 is normal speed.
|
|
* Note that this is not a Tone.Signal because of a bug in Blink.
|
|
* Please star <a href="https://code.google.com/p/chromium/issues/detail?id=311284">this</a>
|
|
* issue if this an important thing to you.
|
|
* @memberOf Tone.Player#
|
|
* @type {number}
|
|
* @name playbackRate
|
|
*/
|
|
Object.defineProperty(Tone.Player.prototype, 'playbackRate', {
|
|
get: function () {
|
|
return this._playbackRate;
|
|
},
|
|
set: function (rate) {
|
|
this._playbackRate = rate;
|
|
if (this._source) {
|
|
this._source.playbackRate.value = rate;
|
|
}
|
|
}
|
|
});
|
|
/**
|
|
* The direction the buffer should play in
|
|
* @memberOf Tone.Player#
|
|
* @type {boolean}
|
|
* @name reverse
|
|
*/
|
|
Object.defineProperty(Tone.Player.prototype, 'reverse', {
|
|
get: function () {
|
|
return this._buffer.reverse;
|
|
},
|
|
set: function (rev) {
|
|
this._buffer.reverse = rev;
|
|
}
|
|
});
|
|
/**
|
|
* Dispose and disconnect.
|
|
* @return {Tone.Player} this
|
|
*/
|
|
Tone.Player.prototype.dispose = function () {
|
|
Tone.Source.prototype.dispose.call(this);
|
|
if (this._source !== null) {
|
|
this._source.disconnect();
|
|
this._source = null;
|
|
}
|
|
this._buffer.dispose();
|
|
this._buffer = null;
|
|
return this;
|
|
};
|
|
return Tone.Player;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class A simple sampler instrument which plays an audio buffer
|
|
* through an amplitude envelope and a filter envelope. Nested
|
|
* lists will be flattened.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Instrument}
|
|
* @param {Object|string} urls the urls of the audio file
|
|
* @param {Object} [options] the options object for the synth
|
|
* @example
|
|
* var sampler = new Sampler({
|
|
* A : {
|
|
* 1 : {"./audio/casio/A1.mp3",
|
|
* 2 : "./audio/casio/A2.mp3",
|
|
* },
|
|
* "B.1" : "./audio/casio/B1.mp3",
|
|
* }).toMaster();
|
|
*
|
|
* //listen for when all the samples have loaded
|
|
* Tone.Buffer.onload = function(){
|
|
* sampler.triggerAttack("A.1", time, velocity);
|
|
* };
|
|
*/
|
|
Tone.Sampler = function (urls, options) {
|
|
Tone.Instrument.call(this);
|
|
options = this.defaultArg(options, Tone.Sampler.defaults);
|
|
/**
|
|
* the sample player
|
|
* @type {Tone.Player}
|
|
*/
|
|
this.player = new Tone.Player(options.player);
|
|
this.player.retrigger = true;
|
|
/**
|
|
* the buffers
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
this._buffers = {};
|
|
/**
|
|
* The amplitude envelope.
|
|
* @type {Tone.AmplitudeEnvelope}
|
|
*/
|
|
this.envelope = new Tone.AmplitudeEnvelope(options.envelope);
|
|
/**
|
|
* The filter envelope.
|
|
* @type {Tone.ScaledEnvelope}
|
|
*/
|
|
this.filterEnvelope = new Tone.ScaledEnvelope(options.filterEnvelope);
|
|
/**
|
|
* The name of the current sample.
|
|
* @type {string}
|
|
* @private
|
|
*/
|
|
this._sample = options.sample;
|
|
/**
|
|
* the private reference to the pitch
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._pitch = options.pitch;
|
|
/**
|
|
* The filter.
|
|
* @type {Tone.Filter}
|
|
*/
|
|
this.filter = new Tone.Filter(options.filter);
|
|
//connections / setup
|
|
this._loadBuffers(urls);
|
|
this.pitch = options.pitch;
|
|
this.player.chain(this.filter, this.envelope, this.output);
|
|
this.filterEnvelope.connect(this.filter.frequency);
|
|
this._readOnly([
|
|
'player',
|
|
'filterEnvelope',
|
|
'envelope',
|
|
'filter'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.Sampler, Tone.Instrument);
|
|
/**
|
|
* the default parameters
|
|
* @static
|
|
*/
|
|
Tone.Sampler.defaults = {
|
|
'sample': 0,
|
|
'pitch': 0,
|
|
'player': { 'loop': false },
|
|
'envelope': {
|
|
'attack': 0.001,
|
|
'decay': 0,
|
|
'sustain': 1,
|
|
'release': 0.1
|
|
},
|
|
'filterEnvelope': {
|
|
'attack': 0.001,
|
|
'decay': 0.001,
|
|
'sustain': 1,
|
|
'release': 0.5,
|
|
'min': 20,
|
|
'max': 20000,
|
|
'exponent': 2
|
|
},
|
|
'filter': { 'type': 'lowpass' }
|
|
};
|
|
/**
|
|
* load the buffers
|
|
* @param {Object} urls the urls
|
|
* @private
|
|
*/
|
|
Tone.Sampler.prototype._loadBuffers = function (urls) {
|
|
if (typeof urls === 'string') {
|
|
this._buffers['0'] = new Tone.Buffer(urls, function () {
|
|
this.sample = '0';
|
|
}.bind(this));
|
|
} else {
|
|
urls = this._flattenUrls(urls);
|
|
for (var buffName in urls) {
|
|
this._sample = buffName;
|
|
var urlString = urls[buffName];
|
|
this._buffers[buffName] = new Tone.Buffer(urlString);
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* flatten an object into a single depth object
|
|
* https://gist.github.com/penguinboy/762197
|
|
* @param {Object} ob
|
|
* @return {Object}
|
|
* @private
|
|
*/
|
|
Tone.Sampler.prototype._flattenUrls = function (ob) {
|
|
var toReturn = {};
|
|
for (var i in ob) {
|
|
if (!ob.hasOwnProperty(i))
|
|
continue;
|
|
if (typeof ob[i] == 'object') {
|
|
var flatObject = this._flattenUrls(ob[i]);
|
|
for (var x in flatObject) {
|
|
if (!flatObject.hasOwnProperty(x))
|
|
continue;
|
|
toReturn[i + '.' + x] = flatObject[x];
|
|
}
|
|
} else {
|
|
toReturn[i] = ob[i];
|
|
}
|
|
}
|
|
return toReturn;
|
|
};
|
|
/**
|
|
* start the sample.
|
|
* @param {string=} sample the name of the samle to trigger, defaults to
|
|
* the last sample used
|
|
* @param {Time} [time=now] the time when the note should start
|
|
* @param {number} [velocity=1] the velocity of the note
|
|
* @returns {Tone.Sampler} this
|
|
*/
|
|
Tone.Sampler.prototype.triggerAttack = function (name, time, velocity) {
|
|
time = this.toSeconds(time);
|
|
if (name) {
|
|
this.sample = name;
|
|
}
|
|
this.player.start(time);
|
|
this.envelope.triggerAttack(time, velocity);
|
|
this.filterEnvelope.triggerAttack(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* start the release portion of the sample
|
|
*
|
|
* @param {Time} [time=now] the time when the note should release
|
|
* @returns {Tone.Sampler} this
|
|
*/
|
|
Tone.Sampler.prototype.triggerRelease = function (time) {
|
|
time = this.toSeconds(time);
|
|
this.filterEnvelope.triggerRelease(time);
|
|
this.envelope.triggerRelease(time);
|
|
this.player.stop(this.toSeconds(this.envelope.release) + time);
|
|
return this;
|
|
};
|
|
/**
|
|
* The name of the sample to trigger.
|
|
* @memberOf Tone.Sampler#
|
|
* @type {number|string}
|
|
* @name sample
|
|
*/
|
|
Object.defineProperty(Tone.Sampler.prototype, 'sample', {
|
|
get: function () {
|
|
return this._sample;
|
|
},
|
|
set: function (name) {
|
|
if (this._buffers.hasOwnProperty(name)) {
|
|
this._sample = name;
|
|
this.player.buffer = this._buffers[name];
|
|
} else {
|
|
throw new Error('Sampler does not have a sample named ' + name);
|
|
}
|
|
}
|
|
});
|
|
/**
|
|
* The direction the buffer should play in
|
|
* @memberOf Tone.Sampler#
|
|
* @type {boolean}
|
|
* @name reverse
|
|
*/
|
|
Object.defineProperty(Tone.Sampler.prototype, 'reverse', {
|
|
get: function () {
|
|
for (var i in this._buffers) {
|
|
return this._buffers[i].reverse;
|
|
}
|
|
},
|
|
set: function (rev) {
|
|
for (var i in this._buffers) {
|
|
this._buffers[i].reverse = rev;
|
|
}
|
|
}
|
|
});
|
|
/**
|
|
* Repitch the sampled note by some interval (measured
|
|
* in semi-tones).
|
|
* @memberOf Tone.Sampler#
|
|
* @type {number}
|
|
* @name pitch
|
|
* @example
|
|
* sampler.pitch = -12; //down one octave
|
|
* sampler.pitch = 7; //up a fifth
|
|
*/
|
|
Object.defineProperty(Tone.Sampler.prototype, 'pitch', {
|
|
get: function () {
|
|
return this._pitch;
|
|
},
|
|
set: function (interval) {
|
|
this._pitch = interval;
|
|
this.player.playbackRate = this.intervalToFrequencyRatio(interval);
|
|
}
|
|
});
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Sampler} this
|
|
*/
|
|
Tone.Sampler.prototype.dispose = function () {
|
|
Tone.Instrument.prototype.dispose.call(this);
|
|
this._writable([
|
|
'player',
|
|
'filterEnvelope',
|
|
'envelope',
|
|
'filter'
|
|
]);
|
|
this.player.dispose();
|
|
this.filterEnvelope.dispose();
|
|
this.envelope.dispose();
|
|
this.filter.dispose();
|
|
this.player = null;
|
|
this.filterEnvelope = null;
|
|
this.envelope = null;
|
|
this.filter = null;
|
|
for (var sample in this._buffers) {
|
|
this._buffers[sample].dispose();
|
|
this._buffers[sample] = null;
|
|
}
|
|
this._buffers = null;
|
|
return this;
|
|
};
|
|
return Tone.Sampler;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Tone.SimpleSynth is composed simply of a Tone.OmniOscillator
|
|
* routed through a Tone.AmplitudeEnvelope.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Monophonic}
|
|
* @param {Object} [options] the options available for the synth
|
|
* see defaults below
|
|
* @example
|
|
* var synth = new Tone.SimpleSynth().toMaster();
|
|
* synth.triggerAttackRelease("C4", "8n");
|
|
*/
|
|
Tone.SimpleSynth = function (options) {
|
|
//get the defaults
|
|
options = this.defaultArg(options, Tone.SimpleSynth.defaults);
|
|
Tone.Monophonic.call(this, options);
|
|
/**
|
|
* The oscillator.
|
|
* @type {Tone.OmniOscillator}
|
|
*/
|
|
this.oscillator = new Tone.OmniOscillator(options.oscillator);
|
|
/**
|
|
* The frequency control.
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.frequency = this.oscillator.frequency;
|
|
/**
|
|
* The detune control.
|
|
* @type {Cents}
|
|
* @signal
|
|
*/
|
|
this.detune = this.oscillator.detune;
|
|
/**
|
|
* The amplitude envelope.
|
|
* @type {Tone.AmplitudeEnvelope}
|
|
*/
|
|
this.envelope = new Tone.AmplitudeEnvelope(options.envelope);
|
|
//connect the oscillators to the output
|
|
this.oscillator.chain(this.envelope, this.output);
|
|
//start the oscillators
|
|
this.oscillator.start();
|
|
this._readOnly([
|
|
'oscillator',
|
|
'frequency',
|
|
'detune',
|
|
'envelope'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.SimpleSynth, Tone.Monophonic);
|
|
/**
|
|
* @const
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.SimpleSynth.defaults = {
|
|
'oscillator': { 'type': 'triangle' },
|
|
'envelope': {
|
|
'attack': 0.005,
|
|
'decay': 0.1,
|
|
'sustain': 0.3,
|
|
'release': 1
|
|
}
|
|
};
|
|
/**
|
|
* start the attack portion of the envelope
|
|
* @param {Time} [time=now] the time the attack should start
|
|
* @param {number} [velocity=1] the velocity of the note (0-1)
|
|
* @returns {Tone.SimpleSynth} this
|
|
* @private
|
|
*/
|
|
Tone.SimpleSynth.prototype._triggerEnvelopeAttack = function (time, velocity) {
|
|
//the envelopes
|
|
this.envelope.triggerAttack(time, velocity);
|
|
return this;
|
|
};
|
|
/**
|
|
* start the release portion of the envelope
|
|
* @param {Time} [time=now] the time the release should start
|
|
* @returns {Tone.SimpleSynth} this
|
|
* @private
|
|
*/
|
|
Tone.SimpleSynth.prototype._triggerEnvelopeRelease = function (time) {
|
|
this.envelope.triggerRelease(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.SimpleSynth} this
|
|
*/
|
|
Tone.SimpleSynth.prototype.dispose = function () {
|
|
Tone.Monophonic.prototype.dispose.call(this);
|
|
this._writable([
|
|
'oscillator',
|
|
'frequency',
|
|
'detune',
|
|
'envelope'
|
|
]);
|
|
this.oscillator.dispose();
|
|
this.oscillator = null;
|
|
this.envelope.dispose();
|
|
this.envelope = null;
|
|
this.frequency = null;
|
|
this.detune = null;
|
|
return this;
|
|
};
|
|
return Tone.SimpleSynth;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class AMSynth uses the output of one Tone.SimpleSynth to modulate the
|
|
* amplitude of another Tone.SimpleSynth. The harmonicity (the ratio between
|
|
* the two signals) affects the timbre of the output signal the most.
|
|
* Read more about Amplitude Modulation Synthesis on
|
|
* <a href="http://www.soundonsound.com/sos/mar00/articles/synthsecrets.htm" target="_blank">SoundOnSound</a>.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Monophonic}
|
|
* @param {Object} [options] the options available for the synth
|
|
* see defaults below
|
|
* @example
|
|
* var synth = new Tone.SimpleAM().toMaster();
|
|
* synth.triggerAttackRelease("C4", "8n");
|
|
*/
|
|
Tone.SimpleAM = function (options) {
|
|
options = this.defaultArg(options, Tone.SimpleAM.defaults);
|
|
Tone.Monophonic.call(this, options);
|
|
/**
|
|
* The carrier voice.
|
|
* @type {Tone.SimpleSynth}
|
|
*/
|
|
this.carrier = new Tone.SimpleSynth(options.carrier);
|
|
/**
|
|
* The modulator voice.
|
|
* @type {Tone.SimpleSynth}
|
|
*/
|
|
this.modulator = new Tone.SimpleSynth(options.modulator);
|
|
/**
|
|
* the frequency control
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.frequency = new Tone.Signal(440, Tone.Type.Frequency);
|
|
/**
|
|
* The ratio between the carrier and the modulator frequencies. A value of 1
|
|
* makes both voices in unison, a value of 0.5 puts the modulator an octave below
|
|
* the carrier.
|
|
* @type {Positive}
|
|
* @signal
|
|
* @example
|
|
* //set the modulator an octave above the carrier frequency
|
|
* simpleAM.harmonicity.value = 2;
|
|
*/
|
|
this.harmonicity = new Tone.Multiply(options.harmonicity);
|
|
this.harmonicity.units = Tone.Type.Positive;
|
|
/**
|
|
* convert the -1,1 output to 0,1
|
|
* @type {Tone.AudioToGain}
|
|
* @private
|
|
*/
|
|
this._modulationScale = new Tone.AudioToGain();
|
|
/**
|
|
* the node where the modulation happens
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._modulationNode = this.context.createGain();
|
|
//control the two voices frequency
|
|
this.frequency.connect(this.carrier.frequency);
|
|
this.frequency.chain(this.harmonicity, this.modulator.frequency);
|
|
this.modulator.chain(this._modulationScale, this._modulationNode.gain);
|
|
this.carrier.chain(this._modulationNode, this.output);
|
|
this._readOnly([
|
|
'carrier',
|
|
'modulator',
|
|
'frequency',
|
|
'harmonicity'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.SimpleAM, Tone.Monophonic);
|
|
/**
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.SimpleAM.defaults = {
|
|
'harmonicity': 3,
|
|
'carrier': {
|
|
'volume': -10,
|
|
'portamento': 0,
|
|
'oscillator': { 'type': 'sine' },
|
|
'envelope': {
|
|
'attack': 0.01,
|
|
'decay': 0.01,
|
|
'sustain': 1,
|
|
'release': 0.5
|
|
}
|
|
},
|
|
'modulator': {
|
|
'volume': -10,
|
|
'portamento': 0,
|
|
'oscillator': { 'type': 'square' },
|
|
'envelope': {
|
|
'attack': 2,
|
|
'decay': 0,
|
|
'sustain': 1,
|
|
'release': 0.5
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* trigger the attack portion of the note
|
|
*
|
|
* @param {Time} [time=now] the time the note will occur
|
|
* @param {number} [velocity=1] the velocity of the note
|
|
* @returns {Tone.SimpleAM} this
|
|
* @private
|
|
*/
|
|
Tone.SimpleAM.prototype._triggerEnvelopeAttack = function (time, velocity) {
|
|
//the port glide
|
|
time = this.toSeconds(time);
|
|
//the envelopes
|
|
this.carrier.envelope.triggerAttack(time, velocity);
|
|
this.modulator.envelope.triggerAttack(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* trigger the release portion of the note
|
|
*
|
|
* @param {Time} [time=now] the time the note will release
|
|
* @returns {Tone.SimpleAM} this
|
|
* @private
|
|
*/
|
|
Tone.SimpleAM.prototype._triggerEnvelopeRelease = function (time) {
|
|
this.carrier.triggerRelease(time);
|
|
this.modulator.triggerRelease(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.SimpleAM} this
|
|
*/
|
|
Tone.SimpleAM.prototype.dispose = function () {
|
|
Tone.Monophonic.prototype.dispose.call(this);
|
|
this._writable([
|
|
'carrier',
|
|
'modulator',
|
|
'frequency',
|
|
'harmonicity'
|
|
]);
|
|
this.carrier.dispose();
|
|
this.carrier = null;
|
|
this.modulator.dispose();
|
|
this.modulator = null;
|
|
this.frequency.dispose();
|
|
this.frequency = null;
|
|
this.harmonicity.dispose();
|
|
this.harmonicity = null;
|
|
this._modulationScale.dispose();
|
|
this._modulationScale = null;
|
|
this._modulationNode.disconnect();
|
|
this._modulationNode = null;
|
|
return this;
|
|
};
|
|
return Tone.SimpleAM;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class SimpleFM is composed of two Tone.SimpleSynths where one Tone.SimpleSynth modulates
|
|
* the frequency of a second Tone.SimpleSynth. A lot of spectral content
|
|
* can be explored using the modulationIndex parameter. Read more about
|
|
* Frequency Modulation Synthesis on <a href="http://www.soundonsound.com/sos/apr00/articles/synthsecrets.htm">SoundOnSound</a>
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Monophonic}
|
|
* @param {Object} [options] the options available for the synth
|
|
* see defaults below
|
|
* @example
|
|
* var fmSynth = new Tone.SimpleFM().toMaster();
|
|
* fmSynth.triggerAttackRelease("C4", "8n");
|
|
*/
|
|
Tone.SimpleFM = function (options) {
|
|
options = this.defaultArg(options, Tone.SimpleFM.defaults);
|
|
Tone.Monophonic.call(this, options);
|
|
/**
|
|
* The carrier voice.
|
|
* @type {Tone.SimpleSynth}
|
|
*/
|
|
this.carrier = new Tone.SimpleSynth(options.carrier);
|
|
this.carrier.volume.value = -10;
|
|
/**
|
|
* The modulator voice.
|
|
* @type {Tone.SimpleSynth}
|
|
*/
|
|
this.modulator = new Tone.SimpleSynth(options.modulator);
|
|
this.modulator.volume.value = -10;
|
|
/**
|
|
* the frequency control
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.frequency = new Tone.Signal(440, Tone.Type.Frequency);
|
|
/**
|
|
* The ratio between the two carrier and the modulator.
|
|
* @type {Positive}
|
|
* @signal
|
|
*/
|
|
this.harmonicity = new Tone.Multiply(options.harmonicity);
|
|
this.harmonicity.units = Tone.Type.Positive;
|
|
/**
|
|
* The modulation index which is in essence the depth or amount of the modulation. In other terms it is the
|
|
* ratio of the frequency of the modulating signal (mf) to the amplitude of the
|
|
* modulating signal (ma) -- as in ma/mf.
|
|
* @type {Positive}
|
|
* @signal
|
|
*/
|
|
this.modulationIndex = new Tone.Multiply(options.modulationIndex);
|
|
this.modulationIndex.units = Tone.Type.Positive;
|
|
/**
|
|
* the node where the modulation happens
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._modulationNode = this.context.createGain();
|
|
//control the two voices frequency
|
|
this.frequency.connect(this.carrier.frequency);
|
|
this.frequency.chain(this.harmonicity, this.modulator.frequency);
|
|
this.frequency.chain(this.modulationIndex, this._modulationNode);
|
|
this.modulator.connect(this._modulationNode.gain);
|
|
this._modulationNode.gain.value = 0;
|
|
this._modulationNode.connect(this.carrier.frequency);
|
|
this.carrier.connect(this.output);
|
|
this._readOnly([
|
|
'carrier',
|
|
'modulator',
|
|
'frequency',
|
|
'harmonicity',
|
|
'modulationIndex'
|
|
]);
|
|
;
|
|
};
|
|
Tone.extend(Tone.SimpleFM, Tone.Monophonic);
|
|
/**
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.SimpleFM.defaults = {
|
|
'harmonicity': 3,
|
|
'modulationIndex': 10,
|
|
'carrier': {
|
|
'volume': -10,
|
|
'portamento': 0,
|
|
'oscillator': { 'type': 'sine' },
|
|
'envelope': {
|
|
'attack': 0.01,
|
|
'decay': 0,
|
|
'sustain': 1,
|
|
'release': 0.5
|
|
}
|
|
},
|
|
'modulator': {
|
|
'volume': -10,
|
|
'portamento': 0,
|
|
'oscillator': { 'type': 'triangle' },
|
|
'envelope': {
|
|
'attack': 0.01,
|
|
'decay': 0,
|
|
'sustain': 1,
|
|
'release': 0.5
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* trigger the attack portion of the note
|
|
*
|
|
* @param {Time} [time=now] the time the note will occur
|
|
* @param {number} [velocity=1] the velocity of the note
|
|
* @returns {Tone.SimpleFM} this
|
|
* @private
|
|
*/
|
|
Tone.SimpleFM.prototype._triggerEnvelopeAttack = function (time, velocity) {
|
|
//the port glide
|
|
time = this.toSeconds(time);
|
|
//the envelopes
|
|
this.carrier.envelope.triggerAttack(time, velocity);
|
|
this.modulator.envelope.triggerAttack(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* trigger the release portion of the note
|
|
*
|
|
* @param {Time} [time=now] the time the note will release
|
|
* @returns {Tone.SimpleFM} this
|
|
* @private
|
|
*/
|
|
Tone.SimpleFM.prototype._triggerEnvelopeRelease = function (time) {
|
|
this.carrier.triggerRelease(time);
|
|
this.modulator.triggerRelease(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.SimpleFM} this
|
|
*/
|
|
Tone.SimpleFM.prototype.dispose = function () {
|
|
Tone.Monophonic.prototype.dispose.call(this);
|
|
this._writable([
|
|
'carrier',
|
|
'modulator',
|
|
'frequency',
|
|
'harmonicity',
|
|
'modulationIndex'
|
|
]);
|
|
this.carrier.dispose();
|
|
this.carrier = null;
|
|
this.modulator.dispose();
|
|
this.modulator = null;
|
|
this.frequency.dispose();
|
|
this.frequency = null;
|
|
this.modulationIndex.dispose();
|
|
this.modulationIndex = null;
|
|
this.harmonicity.dispose();
|
|
this.harmonicity = null;
|
|
this._modulationNode.disconnect();
|
|
this._modulationNode = null;
|
|
return this;
|
|
};
|
|
return Tone.SimpleFM;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Effect is the base class for effects. connect the effect between
|
|
* the effectSend and effectReturn GainNodes. then control the amount of
|
|
* effect which goes to the output using the dry/wet control.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone}
|
|
* @param {number} [initialWet=0] the starting wet value
|
|
* defaults to 100% wet
|
|
*/
|
|
Tone.Effect = function () {
|
|
Tone.call(this);
|
|
//get all of the defaults
|
|
var options = this.optionsObject(arguments, ['wet'], Tone.Effect.defaults);
|
|
/**
|
|
* the drywet knob to control the amount of effect
|
|
* @type {Tone.CrossFade}
|
|
* @private
|
|
*/
|
|
this._dryWet = new Tone.CrossFade(options.wet);
|
|
/**
|
|
* The wet control, i.e. how much of the effected
|
|
* will pass through to the output.
|
|
* @type {NormalRange}
|
|
* @signal
|
|
*/
|
|
this.wet = this._dryWet.fade;
|
|
/**
|
|
* connect the effectSend to the input of hte effect
|
|
*
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this.effectSend = this.context.createGain();
|
|
/**
|
|
* connect the output of the effect to the effectReturn
|
|
*
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this.effectReturn = this.context.createGain();
|
|
//connections
|
|
this.input.connect(this._dryWet.a);
|
|
this.input.connect(this.effectSend);
|
|
this.effectReturn.connect(this._dryWet.b);
|
|
this._dryWet.connect(this.output);
|
|
this._readOnly(['wet']);
|
|
};
|
|
Tone.extend(Tone.Effect);
|
|
/**
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.Effect.defaults = { 'wet': 1 };
|
|
/**
|
|
* bypass the effect
|
|
* @returns {Tone.Effect} this
|
|
*/
|
|
Tone.Effect.prototype.bypass = function () {
|
|
this.wet.value = 0;
|
|
return this;
|
|
};
|
|
/**
|
|
* chains the effect in between the effectSend and effectReturn
|
|
* @param {Tone} effect
|
|
* @private
|
|
* @returns {Tone.Effect} this
|
|
*/
|
|
Tone.Effect.prototype.connectEffect = function (effect) {
|
|
this.effectSend.chain(effect, this.effectReturn);
|
|
return this;
|
|
};
|
|
/**
|
|
* tear down
|
|
* @returns {Tone.Effect} this
|
|
*/
|
|
Tone.Effect.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._dryWet.dispose();
|
|
this._dryWet = null;
|
|
this.effectSend.disconnect();
|
|
this.effectSend = null;
|
|
this.effectReturn.disconnect();
|
|
this.effectReturn = null;
|
|
this._writable(['wet']);
|
|
this.wet = null;
|
|
return this;
|
|
};
|
|
return Tone.Effect;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class AutoFilter is a Tone.Filter with a Tone.LFO connected to the filter cutoff frequency.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Effect}
|
|
* @param {Time} [frequency=1] (optional) rate in HZ of the filter
|
|
* @param {number} [min=200] min
|
|
* @param {number} [max=1200] max
|
|
* @example
|
|
* var autoFilter = new Tone.AutoFilter("4n");
|
|
*/
|
|
Tone.AutoFilter = function () {
|
|
var options = this.optionsObject(arguments, [
|
|
'frequency',
|
|
'min',
|
|
'max'
|
|
], Tone.AutoFilter.defaults);
|
|
Tone.Effect.call(this, options);
|
|
/**
|
|
* the lfo which drives the filter cutoff
|
|
* @type {Tone.LFO}
|
|
* @private
|
|
*/
|
|
this._lfo = new Tone.LFO({
|
|
'frequency': options.frequency,
|
|
'amplitude': options.depth,
|
|
'min': options.min,
|
|
'max': options.max
|
|
});
|
|
/**
|
|
* The range of the filter modulating between the min and max frequency.
|
|
* 0 = no modulation. 1 = full modulation.
|
|
* @type {NormalRange}
|
|
* @signal
|
|
*/
|
|
this.depth = this._lfo.amplitude;
|
|
/**
|
|
* How fast the filter modulates between min and max.
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.frequency = this._lfo.frequency;
|
|
/**
|
|
* The filter node
|
|
* @type {Tone.Filter}
|
|
*/
|
|
this.filter = new Tone.Filter(options.filter);
|
|
//connections
|
|
this.connectEffect(this.filter);
|
|
this._lfo.connect(this.filter.frequency);
|
|
this.type = options.type;
|
|
this._readOnly([
|
|
'frequency',
|
|
'depth'
|
|
]);
|
|
};
|
|
//extend Effect
|
|
Tone.extend(Tone.AutoFilter, Tone.Effect);
|
|
/**
|
|
* defaults
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.AutoFilter.defaults = {
|
|
'frequency': 1,
|
|
'type': 'sine',
|
|
'depth': 1,
|
|
'min': 200,
|
|
'max': 1200,
|
|
'filter': {
|
|
'type': 'lowpass',
|
|
'rolloff': -12,
|
|
'Q': 1
|
|
}
|
|
};
|
|
/**
|
|
* Start the filter.
|
|
* @param {Time} [time=now] the filter begins.
|
|
* @returns {Tone.AutoFilter} this
|
|
*/
|
|
Tone.AutoFilter.prototype.start = function (time) {
|
|
this._lfo.start(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* Stop the filter.
|
|
* @param {Time} [time=now] the filter stops.
|
|
* @returns {Tone.AutoFilter} this
|
|
*/
|
|
Tone.AutoFilter.prototype.stop = function (time) {
|
|
this._lfo.stop(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* Sync the filter to the transport.
|
|
* @param {Time} [delay=0] Delay time before starting the effect after the
|
|
* Transport has started.
|
|
* @returns {Tone.AutoFilter} this
|
|
*/
|
|
Tone.AutoFilter.prototype.sync = function (delay) {
|
|
this._lfo.sync(delay);
|
|
return this;
|
|
};
|
|
/**
|
|
* Unsync the filter from the transport
|
|
* @returns {Tone.AutoFilter} this
|
|
*/
|
|
Tone.AutoFilter.prototype.unsync = function () {
|
|
this._lfo.unsync();
|
|
return this;
|
|
};
|
|
/**
|
|
* Type of oscillator attached to the AutoFilter.
|
|
* @memberOf Tone.AutoFilter#
|
|
* @type {string}
|
|
* @name type
|
|
*/
|
|
Object.defineProperty(Tone.AutoFilter.prototype, 'type', {
|
|
get: function () {
|
|
return this._lfo.type;
|
|
},
|
|
set: function (type) {
|
|
this._lfo.type = type;
|
|
}
|
|
});
|
|
/**
|
|
* The minimum value of the LFO attached to the cutoff frequency of the filter.
|
|
* @memberOf Tone.AutoFilter#
|
|
* @type {Frequency}
|
|
* @name min
|
|
*/
|
|
Object.defineProperty(Tone.AutoFilter.prototype, 'min', {
|
|
get: function () {
|
|
return this._lfo.min;
|
|
},
|
|
set: function (min) {
|
|
this._lfo.min = min;
|
|
}
|
|
});
|
|
/**
|
|
* The minimum value of the LFO attached to the cutoff frequency of the filter.
|
|
* @memberOf Tone.AutoFilter#
|
|
* @type {Frequency}
|
|
* @name max
|
|
*/
|
|
Object.defineProperty(Tone.AutoFilter.prototype, 'max', {
|
|
get: function () {
|
|
return this._lfo.max;
|
|
},
|
|
set: function (max) {
|
|
this._lfo.max = max;
|
|
}
|
|
});
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.AutoFilter} this
|
|
*/
|
|
Tone.AutoFilter.prototype.dispose = function () {
|
|
Tone.Effect.prototype.dispose.call(this);
|
|
this._lfo.dispose();
|
|
this._lfo = null;
|
|
this.filter.dispose();
|
|
this.filter = null;
|
|
this._writable([
|
|
'frequency',
|
|
'depth'
|
|
]);
|
|
this.frequency = null;
|
|
this.depth = null;
|
|
return this;
|
|
};
|
|
return Tone.AutoFilter;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class AutoPanner is a Tone.Panner with an LFO connected to the pan amount
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Effect}
|
|
* @param {number} [frequency=1] (optional) rate in HZ of the left-right pan
|
|
* @example
|
|
* var autoPanner = new Tone.AutoPanner("4n");
|
|
*/
|
|
Tone.AutoPanner = function () {
|
|
var options = this.optionsObject(arguments, ['frequency'], Tone.AutoPanner.defaults);
|
|
Tone.Effect.call(this, options);
|
|
/**
|
|
* the lfo which drives the panning
|
|
* @type {Tone.LFO}
|
|
* @private
|
|
*/
|
|
this._lfo = new Tone.LFO({
|
|
'frequency': options.frequency,
|
|
'amplitude': options.depth,
|
|
'min': 0,
|
|
'max': 1,
|
|
//start at the middle of the cycle
|
|
'phase': 90
|
|
});
|
|
/**
|
|
* The amount of panning between left and right.
|
|
* 0 = always center. 1 = full range between left and right.
|
|
* @type {NormalRange}
|
|
* @signal
|
|
*/
|
|
this.depth = this._lfo.amplitude;
|
|
/**
|
|
* the panner node which does the panning
|
|
* @type {Tone.Panner}
|
|
* @private
|
|
*/
|
|
this._panner = new Tone.Panner();
|
|
/**
|
|
* How fast the panner modulates
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.frequency = this._lfo.frequency;
|
|
//connections
|
|
this.connectEffect(this._panner);
|
|
this._lfo.connect(this._panner.pan);
|
|
this.type = options.type;
|
|
this._readOnly([
|
|
'depth',
|
|
'frequency'
|
|
]);
|
|
};
|
|
//extend Effect
|
|
Tone.extend(Tone.AutoPanner, Tone.Effect);
|
|
/**
|
|
* defaults
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.AutoPanner.defaults = {
|
|
'frequency': 1,
|
|
'type': 'sine',
|
|
'depth': 1
|
|
};
|
|
/**
|
|
* Start the panner.
|
|
* @param {Time} [time=now] the panner begins.
|
|
* @returns {Tone.AutoPanner} this
|
|
*/
|
|
Tone.AutoPanner.prototype.start = function (time) {
|
|
this._lfo.start(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* Stop the panner.
|
|
* @param {Time} [time=now] the panner stops.
|
|
* @returns {Tone.AutoPanner} this
|
|
*/
|
|
Tone.AutoPanner.prototype.stop = function (time) {
|
|
this._lfo.stop(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* Sync the panner to the transport.
|
|
* @param {Time} [delay=0] Delay time before starting the effect after the
|
|
* Transport has started.
|
|
* @returns {Tone.AutoFilter} this
|
|
*/
|
|
Tone.AutoPanner.prototype.sync = function (delay) {
|
|
this._lfo.sync(delay);
|
|
return this;
|
|
};
|
|
/**
|
|
* Unsync the panner from the transport
|
|
* @returns {Tone.AutoPanner} this
|
|
*/
|
|
Tone.AutoPanner.prototype.unsync = function () {
|
|
this._lfo.unsync();
|
|
return this;
|
|
};
|
|
/**
|
|
* Type of oscillator attached to the AutoPanner.
|
|
* @memberOf Tone.AutoPanner#
|
|
* @type {string}
|
|
* @name type
|
|
*/
|
|
Object.defineProperty(Tone.AutoPanner.prototype, 'type', {
|
|
get: function () {
|
|
return this._lfo.type;
|
|
},
|
|
set: function (type) {
|
|
this._lfo.type = type;
|
|
}
|
|
});
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.AutoPanner} this
|
|
*/
|
|
Tone.AutoPanner.prototype.dispose = function () {
|
|
Tone.Effect.prototype.dispose.call(this);
|
|
this._lfo.dispose();
|
|
this._lfo = null;
|
|
this._panner.dispose();
|
|
this._panner = null;
|
|
this._writable([
|
|
'depth',
|
|
'frequency'
|
|
]);
|
|
this.frequency = null;
|
|
this.depth = null;
|
|
return this;
|
|
};
|
|
return Tone.AutoPanner;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class AutoWah connects an envelope follower to a bandpass filter.
|
|
* Some inspiration from Tuna.js https://github.com/Dinahmoe/tuna
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Effect}
|
|
* @param {Frequency} [baseFrequency=100] the frequency the filter is set
|
|
* to at the low point of the wah
|
|
* @param {Positive} [octaves=5] the number of octaves above the baseFrequency
|
|
* the filter will sweep to when fully open
|
|
* @param {Decibels} [sensitivity=0] the decibel threshold sensitivity for
|
|
* the incoming signal. Normal range of -40 to 0.
|
|
* @example
|
|
* var autoWah = new Tone.AutoWah(100, 6, -20);
|
|
*/
|
|
Tone.AutoWah = function () {
|
|
var options = this.optionsObject(arguments, [
|
|
'baseFrequency',
|
|
'octaves',
|
|
'sensitivity'
|
|
], Tone.AutoWah.defaults);
|
|
Tone.Effect.call(this, options);
|
|
/**
|
|
* the envelope follower
|
|
* @type {Tone.Follower}
|
|
* @private
|
|
*/
|
|
this.follower = new Tone.Follower(options.follower);
|
|
/**
|
|
* scales the follower value to the frequency domain
|
|
* @type {Tone}
|
|
* @private
|
|
*/
|
|
this._sweepRange = new Tone.ScaleExp(0, 1, 0.5);
|
|
/**
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._baseFrequency = options.baseFrequency;
|
|
/**
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._octaves = options.octaves;
|
|
/**
|
|
* the input gain to adjust the sensitivity
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._inputBoost = this.context.createGain();
|
|
/**
|
|
* @type {BiquadFilterNode}
|
|
* @private
|
|
*/
|
|
this._bandpass = new Tone.Filter({
|
|
'rolloff': -48,
|
|
'frequency': 0,
|
|
'Q': options.Q
|
|
});
|
|
/**
|
|
* @type {Tone.Filter}
|
|
* @private
|
|
*/
|
|
this._peaking = new Tone.Filter(0, 'peaking');
|
|
this._peaking.gain.value = options.gain;
|
|
/**
|
|
* the gain of the filter.
|
|
* @type {Gain}
|
|
* @signal
|
|
*/
|
|
this.gain = this._peaking.gain;
|
|
/**
|
|
* The quality of the filter.
|
|
* @type {Number}
|
|
* @signal
|
|
*/
|
|
this.Q = this._bandpass.Q;
|
|
//the control signal path
|
|
this.effectSend.chain(this._inputBoost, this.follower, this._sweepRange);
|
|
this._sweepRange.connect(this._bandpass.frequency);
|
|
this._sweepRange.connect(this._peaking.frequency);
|
|
//the filtered path
|
|
this.effectSend.chain(this._bandpass, this._peaking, this.effectReturn);
|
|
//set the initial value
|
|
this._setSweepRange();
|
|
this.sensitivity = options.sensitivity;
|
|
this._readOnly([
|
|
'gain',
|
|
'Q'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.AutoWah, Tone.Effect);
|
|
/**
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.AutoWah.defaults = {
|
|
'baseFrequency': 100,
|
|
'octaves': 6,
|
|
'sensitivity': 0,
|
|
'Q': 2,
|
|
'gain': 2,
|
|
'follower': {
|
|
'attack': 0.3,
|
|
'release': 0.5
|
|
}
|
|
};
|
|
/**
|
|
* The number of octaves that the filter will sweep.
|
|
* @memberOf Tone.AutoWah#
|
|
* @type {number}
|
|
* @name octaves
|
|
*/
|
|
Object.defineProperty(Tone.AutoWah.prototype, 'octaves', {
|
|
get: function () {
|
|
return this._octaves;
|
|
},
|
|
set: function (octaves) {
|
|
this._octaves = octaves;
|
|
this._setSweepRange();
|
|
}
|
|
});
|
|
/**
|
|
* The base frequency from which the sweep will start from.
|
|
* @memberOf Tone.AutoWah#
|
|
* @type {Frequency}
|
|
* @name baseFrequency
|
|
*/
|
|
Object.defineProperty(Tone.AutoWah.prototype, 'baseFrequency', {
|
|
get: function () {
|
|
return this._baseFrequency;
|
|
},
|
|
set: function (baseFreq) {
|
|
this._baseFrequency = baseFreq;
|
|
this._setSweepRange();
|
|
}
|
|
});
|
|
/**
|
|
* The sensitivity to control how responsive to the input signal the filter is.
|
|
* in Decibels.
|
|
* @memberOf Tone.AutoWah#
|
|
* @type {number}
|
|
* @name sensitivity
|
|
*/
|
|
Object.defineProperty(Tone.AutoWah.prototype, 'sensitivity', {
|
|
get: function () {
|
|
return this.gainToDb(1 / this._inputBoost.gain.value);
|
|
},
|
|
set: function (sensitivy) {
|
|
this._inputBoost.gain.value = 1 / this.dbToGain(sensitivy);
|
|
}
|
|
});
|
|
/**
|
|
* sets the sweep range of the scaler
|
|
* @private
|
|
*/
|
|
Tone.AutoWah.prototype._setSweepRange = function () {
|
|
this._sweepRange.min = this._baseFrequency;
|
|
this._sweepRange.max = Math.min(this._baseFrequency * Math.pow(2, this._octaves), this.context.sampleRate / 2);
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.AutoWah} this
|
|
*/
|
|
Tone.AutoWah.prototype.dispose = function () {
|
|
Tone.Effect.prototype.dispose.call(this);
|
|
this.follower.dispose();
|
|
this.follower = null;
|
|
this._sweepRange.dispose();
|
|
this._sweepRange = null;
|
|
this._bandpass.dispose();
|
|
this._bandpass = null;
|
|
this._peaking.dispose();
|
|
this._peaking = null;
|
|
this._inputBoost.disconnect();
|
|
this._inputBoost = null;
|
|
this._writable([
|
|
'gain',
|
|
'Q'
|
|
]);
|
|
this.gain = null;
|
|
this.Q = null;
|
|
return this;
|
|
};
|
|
return Tone.AutoWah;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Downsample incoming signal to a different bitdepth.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Effect}
|
|
* @param {number} bits 1-8.
|
|
* @example
|
|
* var crusher = new Tone.BitCrusher(4);
|
|
*/
|
|
Tone.BitCrusher = function () {
|
|
var options = this.optionsObject(arguments, ['bits'], Tone.BitCrusher.defaults);
|
|
Tone.Effect.call(this, options);
|
|
var invStepSize = 1 / Math.pow(2, options.bits - 1);
|
|
/**
|
|
* Subtract the input signal and the modulus of the input signal
|
|
* @type {Tone.Subtract}
|
|
* @private
|
|
*/
|
|
this._subtract = new Tone.Subtract();
|
|
/**
|
|
* The mod function
|
|
* @type {Tone.Modulo}
|
|
* @private
|
|
*/
|
|
this._modulo = new Tone.Modulo(invStepSize);
|
|
/**
|
|
* keeps track of the bits
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._bits = options.bits;
|
|
//connect it up
|
|
this.effectSend.fan(this._subtract, this._modulo);
|
|
this._modulo.connect(this._subtract, 0, 1);
|
|
this._subtract.connect(this.effectReturn);
|
|
};
|
|
Tone.extend(Tone.BitCrusher, Tone.Effect);
|
|
/**
|
|
* the default values
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.BitCrusher.defaults = { 'bits': 4 };
|
|
/**
|
|
* The bit depth of the BitCrusher
|
|
* @memberOf Tone.BitCrusher#
|
|
* @type {number}
|
|
* @name bits
|
|
*/
|
|
Object.defineProperty(Tone.BitCrusher.prototype, 'bits', {
|
|
get: function () {
|
|
return this._bits;
|
|
},
|
|
set: function (bits) {
|
|
this._bits = bits;
|
|
var invStepSize = 1 / Math.pow(2, bits - 1);
|
|
this._modulo.value = invStepSize;
|
|
}
|
|
});
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.BitCrusher} this
|
|
*/
|
|
Tone.BitCrusher.prototype.dispose = function () {
|
|
Tone.Effect.prototype.dispose.call(this);
|
|
this._subtract.dispose();
|
|
this._subtract = null;
|
|
this._modulo.dispose();
|
|
this._modulo = null;
|
|
return this;
|
|
};
|
|
return Tone.BitCrusher;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class A Chebyshev waveshaper. Good for making different types of distortion sounds.
|
|
* Note that odd orders sound very different from even ones. order = 1 is no change.
|
|
* Read more <a href="http://music.columbia.edu/cmc/musicandcomputers/chapter4/04_06.php">here</a>
|
|
*
|
|
* @extends {Tone.Effect}
|
|
* @constructor
|
|
* @param {number} order The order of the chebyshev polynomial. Normal range between 1-100.
|
|
* @example
|
|
* var cheby = new Tone.Chebyshev(50);
|
|
*/
|
|
Tone.Chebyshev = function () {
|
|
var options = this.optionsObject(arguments, ['order'], Tone.Chebyshev.defaults);
|
|
Tone.Effect.call(this);
|
|
/**
|
|
* @type {WaveShaperNode}
|
|
* @private
|
|
*/
|
|
this._shaper = new Tone.WaveShaper(4096);
|
|
/**
|
|
* holds onto the order of the filter
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._order = options.order;
|
|
this.connectEffect(this._shaper);
|
|
this.order = options.order;
|
|
this.oversample = options.oversample;
|
|
};
|
|
Tone.extend(Tone.Chebyshev, Tone.Effect);
|
|
/**
|
|
* @static
|
|
* @const
|
|
* @type {Object}
|
|
*/
|
|
Tone.Chebyshev.defaults = {
|
|
'order': 1,
|
|
'oversample': 'none'
|
|
};
|
|
/**
|
|
* get the coefficient for that degree
|
|
* @param {number} x the x value
|
|
* @param {number} degree
|
|
* @param {Object} memo memoize the computed value.
|
|
* this speeds up computation greatly.
|
|
* @return {number} the coefficient
|
|
* @private
|
|
*/
|
|
Tone.Chebyshev.prototype._getCoefficient = function (x, degree, memo) {
|
|
if (memo.hasOwnProperty(degree)) {
|
|
return memo[degree];
|
|
} else if (degree === 0) {
|
|
memo[degree] = 0;
|
|
} else if (degree === 1) {
|
|
memo[degree] = x;
|
|
} else {
|
|
memo[degree] = 2 * x * this._getCoefficient(x, degree - 1, memo) - this._getCoefficient(x, degree - 2, memo);
|
|
}
|
|
return memo[degree];
|
|
};
|
|
/**
|
|
* The order of the Chebyshev polynomial i.e.
|
|
* order = 2 -> 2x^2 + 1. order = 3 -> 4x^3 + 3x.
|
|
* @memberOf Tone.Chebyshev#
|
|
* @type {number}
|
|
* @name order
|
|
*/
|
|
Object.defineProperty(Tone.Chebyshev.prototype, 'order', {
|
|
get: function () {
|
|
return this._order;
|
|
},
|
|
set: function (order) {
|
|
this._order = order;
|
|
var curve = new Array(4096);
|
|
var len = curve.length;
|
|
for (var i = 0; i < len; ++i) {
|
|
var x = i * 2 / len - 1;
|
|
if (x === 0) {
|
|
//should output 0 when input is 0
|
|
curve[i] = 0;
|
|
} else {
|
|
curve[i] = this._getCoefficient(x, order, {});
|
|
}
|
|
}
|
|
this._shaper.curve = curve;
|
|
}
|
|
});
|
|
/**
|
|
* The oversampling of the effect. Can either be "none", "2x" or "4x".
|
|
* @memberOf Tone.Chebyshev#
|
|
* @type {string}
|
|
* @name oversample
|
|
*/
|
|
Object.defineProperty(Tone.Chebyshev.prototype, 'oversample', {
|
|
get: function () {
|
|
return this._shaper.oversample;
|
|
},
|
|
set: function (oversampling) {
|
|
this._shaper.oversample = oversampling;
|
|
}
|
|
});
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Chebyshev} this
|
|
*/
|
|
Tone.Chebyshev.prototype.dispose = function () {
|
|
Tone.Effect.prototype.dispose.call(this);
|
|
this._shaper.dispose();
|
|
this._shaper = null;
|
|
return this;
|
|
};
|
|
return Tone.Chebyshev;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Creates an effect with an effectSendL/R and effectReturnL/R
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Effect}
|
|
*/
|
|
Tone.StereoEffect = function () {
|
|
Tone.call(this);
|
|
//get the defaults
|
|
var options = this.optionsObject(arguments, ['wet'], Tone.Effect.defaults);
|
|
/**
|
|
* the drywet knob to control the amount of effect
|
|
* @type {Tone.CrossFade}
|
|
* @private
|
|
*/
|
|
this._dryWet = new Tone.CrossFade(options.wet);
|
|
/**
|
|
* The wet control, i.e. how much of the effected
|
|
* will pass through to the output.
|
|
* @type {NormalRange}
|
|
* @signal
|
|
*/
|
|
this.wet = this._dryWet.fade;
|
|
/**
|
|
* then split it
|
|
* @type {Tone.Split}
|
|
* @private
|
|
*/
|
|
this._split = new Tone.Split();
|
|
/**
|
|
* the effects send LEFT
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this.effectSendL = this._split.left;
|
|
/**
|
|
* the effects send RIGHT
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this.effectSendR = this._split.right;
|
|
/**
|
|
* the stereo effect merger
|
|
* @type {Tone.Merge}
|
|
* @private
|
|
*/
|
|
this._merge = new Tone.Merge();
|
|
/**
|
|
* the effect return LEFT
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this.effectReturnL = this._merge.left;
|
|
/**
|
|
* the effect return RIGHT
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this.effectReturnR = this._merge.right;
|
|
//connections
|
|
this.input.connect(this._split);
|
|
//dry wet connections
|
|
this.input.connect(this._dryWet, 0, 0);
|
|
this._merge.connect(this._dryWet, 0, 1);
|
|
this._dryWet.connect(this.output);
|
|
this._readOnly(['wet']);
|
|
};
|
|
Tone.extend(Tone.StereoEffect, Tone.Effect);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.StereoEffect} this
|
|
*/
|
|
Tone.StereoEffect.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._dryWet.dispose();
|
|
this._dryWet = null;
|
|
this._split.dispose();
|
|
this._split = null;
|
|
this._merge.dispose();
|
|
this._merge = null;
|
|
this.effectSendL = null;
|
|
this.effectSendR = null;
|
|
this.effectReturnL = null;
|
|
this.effectReturnR = null;
|
|
this._writable(['wet']);
|
|
this.wet = null;
|
|
return this;
|
|
};
|
|
return Tone.StereoEffect;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Feedback Effect (a sound loop between an audio source and its own output)
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Effect}
|
|
* @param {number|Object} [initialFeedback=0.125] the initial feedback value
|
|
*/
|
|
Tone.FeedbackEffect = function () {
|
|
var options = this.optionsObject(arguments, ['feedback']);
|
|
options = this.defaultArg(options, Tone.FeedbackEffect.defaults);
|
|
Tone.Effect.call(this, options);
|
|
/**
|
|
* controls the amount of feedback
|
|
* @type {NormalRange}
|
|
* @signal
|
|
*/
|
|
this.feedback = new Tone.Signal(options.feedback, Tone.Type.NormalRange);
|
|
/**
|
|
* the gain which controls the feedback
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._feedbackGain = this.context.createGain();
|
|
//the feedback loop
|
|
this.effectReturn.chain(this._feedbackGain, this.effectSend);
|
|
this.feedback.connect(this._feedbackGain.gain);
|
|
this._readOnly(['feedback']);
|
|
};
|
|
Tone.extend(Tone.FeedbackEffect, Tone.Effect);
|
|
/**
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.FeedbackEffect.defaults = { 'feedback': 0.125 };
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.FeedbackEffect} this
|
|
*/
|
|
Tone.FeedbackEffect.prototype.dispose = function () {
|
|
Tone.Effect.prototype.dispose.call(this);
|
|
this._writable(['feedback']);
|
|
this.feedback.dispose();
|
|
this.feedback = null;
|
|
this._feedbackGain.disconnect();
|
|
this._feedbackGain = null;
|
|
return this;
|
|
};
|
|
return Tone.FeedbackEffect;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Just like a stereo feedback effect, but the feedback is routed from left to right
|
|
* and right to left instead of on the same channel.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.FeedbackEffect}
|
|
*/
|
|
Tone.StereoXFeedbackEffect = function () {
|
|
var options = this.optionsObject(arguments, ['feedback'], Tone.FeedbackEffect.defaults);
|
|
Tone.StereoEffect.call(this, options);
|
|
/**
|
|
* controls the amount of feedback
|
|
* @type {Tone.Signal}
|
|
*/
|
|
this.feedback = new Tone.Signal(options.feedback);
|
|
/**
|
|
* the left side feeback
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._feedbackLR = this.context.createGain();
|
|
/**
|
|
* the right side feeback
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._feedbackRL = this.context.createGain();
|
|
//connect it up
|
|
this.effectReturnL.chain(this._feedbackLR, this.effectSendR);
|
|
this.effectReturnR.chain(this._feedbackRL, this.effectSendL);
|
|
this.feedback.fan(this._feedbackLR.gain, this._feedbackRL.gain);
|
|
this._readOnly(['feedback']);
|
|
};
|
|
Tone.extend(Tone.StereoXFeedbackEffect, Tone.FeedbackEffect);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.StereoXFeedbackEffect} this
|
|
*/
|
|
Tone.StereoXFeedbackEffect.prototype.dispose = function () {
|
|
Tone.StereoEffect.prototype.dispose.call(this);
|
|
this._writable(['feedback']);
|
|
this.feedback.dispose();
|
|
this.feedback = null;
|
|
this._feedbackLR.disconnect();
|
|
this._feedbackLR = null;
|
|
this._feedbackRL.disconnect();
|
|
this._feedbackRL = null;
|
|
return this;
|
|
};
|
|
return Tone.StereoXFeedbackEffect;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class A Chorus effect with feedback.
|
|
* Inspiration from <a href="https://github.com/Dinahmoe/tuna/blob/master/tuna.js">tuna.js</a>.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.StereoXFeedbackEffect}
|
|
* @param {number|Object} [frequency=2] the frequency of the effect
|
|
* @param {number} [delayTime=3.5] the delay of the chorus effect in ms
|
|
* @param {number} [depth=0.7] the depth of the chorus
|
|
* @example
|
|
* var chorus = new Tone.Chorus(4, 2.5, 0.5);
|
|
*/
|
|
Tone.Chorus = function () {
|
|
var options = this.optionsObject(arguments, [
|
|
'frequency',
|
|
'delayTime',
|
|
'depth'
|
|
], Tone.Chorus.defaults);
|
|
Tone.StereoXFeedbackEffect.call(this, options);
|
|
/**
|
|
* the depth of the chorus
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._depth = options.depth;
|
|
/**
|
|
* the delayTime
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._delayTime = options.delayTime / 1000;
|
|
/**
|
|
* the lfo which controls the delayTime
|
|
* @type {Tone.LFO}
|
|
* @private
|
|
*/
|
|
this._lfoL = new Tone.LFO(options.rate, 0, 1);
|
|
/**
|
|
* another LFO for the right side with a 180 degree phase diff
|
|
* @type {Tone.LFO}
|
|
* @private
|
|
*/
|
|
this._lfoR = new Tone.LFO(options.rate, 0, 1);
|
|
this._lfoR.phase = 180;
|
|
/**
|
|
* delay for left
|
|
* @type {DelayNode}
|
|
* @private
|
|
*/
|
|
this._delayNodeL = this.context.createDelay();
|
|
/**
|
|
* delay for right
|
|
* @type {DelayNode}
|
|
* @private
|
|
*/
|
|
this._delayNodeR = this.context.createDelay();
|
|
/**
|
|
* The frequency the chorus will modulate at.
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.frequency = this._lfoL.frequency;
|
|
//connections
|
|
this.connectSeries(this.effectSendL, this._delayNodeL, this.effectReturnL);
|
|
this.connectSeries(this.effectSendR, this._delayNodeR, this.effectReturnR);
|
|
//and pass through
|
|
this.effectSendL.connect(this.effectReturnL);
|
|
this.effectSendR.connect(this.effectReturnR);
|
|
//lfo setup
|
|
this._lfoL.connect(this._delayNodeL.delayTime);
|
|
this._lfoR.connect(this._delayNodeR.delayTime);
|
|
//start the lfo
|
|
this._lfoL.start();
|
|
this._lfoR.start();
|
|
//have one LFO frequency control the other
|
|
this._lfoL.frequency.connect(this._lfoR.frequency);
|
|
//set the initial values
|
|
this.depth = this._depth;
|
|
this.frequency.value = options.frequency;
|
|
this.type = options.type;
|
|
this._readOnly(['frequency']);
|
|
};
|
|
Tone.extend(Tone.Chorus, Tone.StereoXFeedbackEffect);
|
|
/**
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.Chorus.defaults = {
|
|
'frequency': 1.5,
|
|
'delayTime': 3.5,
|
|
'depth': 0.7,
|
|
'feedback': 0.1,
|
|
'type': 'sine'
|
|
};
|
|
/**
|
|
* The depth of the effect.
|
|
* @memberOf Tone.Chorus#
|
|
* @type {number}
|
|
* @name depth
|
|
*/
|
|
Object.defineProperty(Tone.Chorus.prototype, 'depth', {
|
|
get: function () {
|
|
return this._depth;
|
|
},
|
|
set: function (depth) {
|
|
this._depth = depth;
|
|
var deviation = this._delayTime * depth;
|
|
this._lfoL.min = Math.max(this._delayTime - deviation, 0);
|
|
this._lfoL.max = this._delayTime + deviation;
|
|
this._lfoR.min = Math.max(this._delayTime - deviation, 0);
|
|
this._lfoR.max = this._delayTime + deviation;
|
|
}
|
|
});
|
|
/**
|
|
* The delayTime in milliseconds
|
|
* @memberOf Tone.Chorus#
|
|
* @type {number}
|
|
* @name delayTime
|
|
*/
|
|
Object.defineProperty(Tone.Chorus.prototype, 'delayTime', {
|
|
get: function () {
|
|
return this._delayTime * 1000;
|
|
},
|
|
set: function (delayTime) {
|
|
this._delayTime = delayTime / 1000;
|
|
this.depth = this._depth;
|
|
}
|
|
});
|
|
/**
|
|
* The lfo type for the chorus.
|
|
* @memberOf Tone.Chorus#
|
|
* @type {string}
|
|
* @name type
|
|
*/
|
|
Object.defineProperty(Tone.Chorus.prototype, 'type', {
|
|
get: function () {
|
|
return this._lfoL.type;
|
|
},
|
|
set: function (type) {
|
|
this._lfoL.type = type;
|
|
this._lfoR.type = type;
|
|
}
|
|
});
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Chorus} this
|
|
*/
|
|
Tone.Chorus.prototype.dispose = function () {
|
|
Tone.StereoXFeedbackEffect.prototype.dispose.call(this);
|
|
this._lfoL.dispose();
|
|
this._lfoL = null;
|
|
this._lfoR.dispose();
|
|
this._lfoR = null;
|
|
this._delayNodeL.disconnect();
|
|
this._delayNodeL = null;
|
|
this._delayNodeR.disconnect();
|
|
this._delayNodeR = null;
|
|
this._writable('frequency');
|
|
this.frequency = null;
|
|
return this;
|
|
};
|
|
return Tone.Chorus;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class ConvolverNode wrapper for reverb and emulation.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Effect}
|
|
* @param {string|AudioBuffer=} url
|
|
* @example
|
|
* var convolver = new Tone.Convolver("./path/to/ir.wav");
|
|
*/
|
|
Tone.Convolver = function () {
|
|
var options = this.optionsObject(arguments, ['url'], Tone.Convolver.defaults);
|
|
Tone.Effect.call(this, options);
|
|
/**
|
|
* convolver node
|
|
* @type {ConvolverNode}
|
|
* @private
|
|
*/
|
|
this._convolver = this.context.createConvolver();
|
|
/**
|
|
* the convolution buffer
|
|
* @type {Tone.Buffer}
|
|
* @private
|
|
*/
|
|
this._buffer = new Tone.Buffer(options.url, function (buffer) {
|
|
this.buffer = buffer;
|
|
options.onload();
|
|
}.bind(this));
|
|
this.connectEffect(this._convolver);
|
|
};
|
|
Tone.extend(Tone.Convolver, Tone.Effect);
|
|
/**
|
|
* @static
|
|
* @const
|
|
* @type {Object}
|
|
*/
|
|
Tone.Convolver.defaults = {
|
|
'url': '',
|
|
'onload': Tone.noOp
|
|
};
|
|
/**
|
|
* The convolver's buffer
|
|
* @memberOf Tone.Convolver#
|
|
* @type {AudioBuffer}
|
|
* @name buffer
|
|
*/
|
|
Object.defineProperty(Tone.Convolver.prototype, 'buffer', {
|
|
get: function () {
|
|
return this._buffer.get();
|
|
},
|
|
set: function (buffer) {
|
|
this._buffer.set(buffer);
|
|
this._convolver.buffer = this._buffer.get();
|
|
}
|
|
});
|
|
/**
|
|
* Load an impulse response url as an audio buffer.
|
|
* Decodes the audio asynchronously and invokes
|
|
* the callback once the audio buffer loads.
|
|
* @param {string} url the url of the buffer to load.
|
|
* filetype support depends on the
|
|
* browser.
|
|
* @param {function=} callback
|
|
* @returns {Tone.Convolver} this
|
|
*/
|
|
Tone.Convolver.prototype.load = function (url, callback) {
|
|
this._buffer.load(url, function (buff) {
|
|
this.buffer = buff;
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
}.bind(this));
|
|
return this;
|
|
};
|
|
/**
|
|
* dispose and disconnect
|
|
* @returns {Tone.Convolver} this
|
|
*/
|
|
Tone.Convolver.prototype.dispose = function () {
|
|
Tone.Effect.prototype.dispose.call(this);
|
|
this._convolver.disconnect();
|
|
this._convolver = null;
|
|
this._buffer.dispose();
|
|
this._buffer = null;
|
|
return this;
|
|
};
|
|
return Tone.Convolver;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class A simple distortion effect using the waveshaper node
|
|
* algorithm from <a href="http://stackoverflow.com/a/22313408">a stackoverflow answer</a>.
|
|
*
|
|
* @extends {Tone.Effect}
|
|
* @constructor
|
|
* @param {number} distortion the amount of distortion (nominal range of 0-1)
|
|
* @example
|
|
* var dist = new Tone.Distortion(0.8);
|
|
*/
|
|
Tone.Distortion = function () {
|
|
var options = this.optionsObject(arguments, ['distortion'], Tone.Distortion.defaults);
|
|
Tone.Effect.call(this, options);
|
|
/**
|
|
* @type {Tone.WaveShaper}
|
|
* @private
|
|
*/
|
|
this._shaper = new Tone.WaveShaper(4096);
|
|
/**
|
|
* holds the distortion amount
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._distortion = options.distortion;
|
|
this.connectEffect(this._shaper);
|
|
this.distortion = options.distortion;
|
|
this.oversample = options.oversample;
|
|
};
|
|
Tone.extend(Tone.Distortion, Tone.Effect);
|
|
/**
|
|
* @static
|
|
* @const
|
|
* @type {Object}
|
|
*/
|
|
Tone.Distortion.defaults = {
|
|
'distortion': 0.4,
|
|
'oversample': 'none'
|
|
};
|
|
/**
|
|
* The amount of distortion. Range between 0-1.
|
|
* @memberOf Tone.Distortion#
|
|
* @type {number}
|
|
* @name distortion
|
|
*/
|
|
Object.defineProperty(Tone.Distortion.prototype, 'distortion', {
|
|
get: function () {
|
|
return this._distortion;
|
|
},
|
|
set: function (amount) {
|
|
this._distortion = amount;
|
|
var k = amount * 100;
|
|
var deg = Math.PI / 180;
|
|
this._shaper.setMap(function (x) {
|
|
if (Math.abs(x) < 0.001) {
|
|
//should output 0 when input is 0
|
|
return 0;
|
|
} else {
|
|
return (3 + k) * x * 20 * deg / (Math.PI + k * Math.abs(x));
|
|
}
|
|
});
|
|
}
|
|
});
|
|
/**
|
|
* The oversampling of the effect. Can either be "none", "2x" or "4x".
|
|
* @memberOf Tone.Distortion#
|
|
* @type {string}
|
|
* @name oversample
|
|
*/
|
|
Object.defineProperty(Tone.Distortion.prototype, 'oversample', {
|
|
get: function () {
|
|
return this._shaper.oversample;
|
|
},
|
|
set: function (oversampling) {
|
|
this._shaper.oversample = oversampling;
|
|
}
|
|
});
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Distortion} this
|
|
*/
|
|
Tone.Distortion.prototype.dispose = function () {
|
|
Tone.Effect.prototype.dispose.call(this);
|
|
this._shaper.dispose();
|
|
this._shaper = null;
|
|
return this;
|
|
};
|
|
return Tone.Distortion;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class A feedback delay.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.FeedbackEffect}
|
|
* @param {Time} [delayTime=0.25] The delay time in seconds.
|
|
* @param {number=} feedback The amount of the effected signal which
|
|
* is fed back through the delay.
|
|
* @example
|
|
* var feedbackDelay = new Tone.FeedbackDelay("8n", 0.25);
|
|
*/
|
|
Tone.FeedbackDelay = function () {
|
|
var options = this.optionsObject(arguments, [
|
|
'delayTime',
|
|
'feedback'
|
|
], Tone.FeedbackDelay.defaults);
|
|
Tone.FeedbackEffect.call(this, options);
|
|
/**
|
|
* Tone.Signal to control the delay amount
|
|
* @type {Time}
|
|
* @signal
|
|
*/
|
|
this.delayTime = new Tone.Signal(options.delayTime, Tone.Type.Time);
|
|
/**
|
|
* the delay node
|
|
* @type {DelayNode}
|
|
* @private
|
|
*/
|
|
this._delayNode = this.context.createDelay(4);
|
|
// connect it up
|
|
this.connectEffect(this._delayNode);
|
|
this.delayTime.connect(this._delayNode.delayTime);
|
|
this._readOnly(['delayTime']);
|
|
};
|
|
Tone.extend(Tone.FeedbackDelay, Tone.FeedbackEffect);
|
|
/**
|
|
* The default values.
|
|
* @const
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.FeedbackDelay.defaults = { 'delayTime': 0.25 };
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.FeedbackDelay} this
|
|
*/
|
|
Tone.FeedbackDelay.prototype.dispose = function () {
|
|
Tone.FeedbackEffect.prototype.dispose.call(this);
|
|
this.delayTime.dispose();
|
|
this._delayNode.disconnect();
|
|
this._delayNode = null;
|
|
this._writable(['delayTime']);
|
|
this.delayTime = null;
|
|
return this;
|
|
};
|
|
return Tone.FeedbackDelay;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* an array of comb filter delay values from Freeverb implementation
|
|
* @static
|
|
* @private
|
|
* @type {Array}
|
|
*/
|
|
var combFilterTunings = [
|
|
1557 / 44100,
|
|
1617 / 44100,
|
|
1491 / 44100,
|
|
1422 / 44100,
|
|
1277 / 44100,
|
|
1356 / 44100,
|
|
1188 / 44100,
|
|
1116 / 44100
|
|
];
|
|
/**
|
|
* an array of allpass filter frequency values from Freeverb implementation
|
|
* @private
|
|
* @static
|
|
* @type {Array}
|
|
*/
|
|
var allpassFilterFrequencies = [
|
|
225,
|
|
556,
|
|
441,
|
|
341
|
|
];
|
|
/**
|
|
* @class Reverb based on <a href="https://ccrma.stanford.edu/~jos/pasp/Freeverb.html">Freeverb</a>.
|
|
*
|
|
* @extends {Tone.Effect}
|
|
* @constructor
|
|
* @param {number} [roomSize=0.7] correlated to the decay time.
|
|
* value between (0,1)
|
|
* @param {number} [dampening=3000] filtering which is applied to the reverb.
|
|
* Value is a lowpass frequency value in hertz.
|
|
* @example
|
|
* var freeverb = new Tone.Freeverb(0.4, 2000);
|
|
*/
|
|
Tone.Freeverb = function () {
|
|
var options = this.optionsObject(arguments, [
|
|
'roomSize',
|
|
'dampening'
|
|
], Tone.Freeverb.defaults);
|
|
Tone.StereoEffect.call(this, options);
|
|
/**
|
|
* The roomSize value between (0,1)
|
|
* @type {NormalRange}
|
|
* @signal
|
|
*/
|
|
this.roomSize = new Tone.Signal(options.roomSize, Tone.Type.NormalRange);
|
|
/**
|
|
* The amount of dampening as a value in Hertz.
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.dampening = new Tone.Signal(options.dampening, Tone.Type.Frequency);
|
|
/**
|
|
* the comb filters
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this._combFilters = [];
|
|
/**
|
|
* the allpass filters on the left
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this._allpassFiltersL = [];
|
|
/**
|
|
* the allpass filters on the right
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this._allpassFiltersR = [];
|
|
//make the allpass filters on teh right
|
|
for (var l = 0; l < allpassFilterFrequencies.length; l++) {
|
|
var allpassL = this.context.createBiquadFilter();
|
|
allpassL.type = 'allpass';
|
|
allpassL.frequency.value = allpassFilterFrequencies[l];
|
|
this._allpassFiltersL.push(allpassL);
|
|
}
|
|
//make the allpass filters on the left
|
|
for (var r = 0; r < allpassFilterFrequencies.length; r++) {
|
|
var allpassR = this.context.createBiquadFilter();
|
|
allpassR.type = 'allpass';
|
|
allpassR.frequency.value = allpassFilterFrequencies[r];
|
|
this._allpassFiltersR.push(allpassR);
|
|
}
|
|
//make the comb filters
|
|
for (var c = 0; c < combFilterTunings.length; c++) {
|
|
var lfpf = new Tone.LowpassCombFilter(combFilterTunings[c]);
|
|
if (c < combFilterTunings.length / 2) {
|
|
this.effectSendL.chain(lfpf, this._allpassFiltersL[0]);
|
|
} else {
|
|
this.effectSendR.chain(lfpf, this._allpassFiltersR[0]);
|
|
}
|
|
this.roomSize.connect(lfpf.resonance);
|
|
this.dampening.connect(lfpf.dampening);
|
|
this._combFilters.push(lfpf);
|
|
}
|
|
//chain the allpass filters togetehr
|
|
this.connectSeries.apply(this, this._allpassFiltersL);
|
|
this.connectSeries.apply(this, this._allpassFiltersR);
|
|
this._allpassFiltersL[this._allpassFiltersL.length - 1].connect(this.effectReturnL);
|
|
this._allpassFiltersR[this._allpassFiltersR.length - 1].connect(this.effectReturnR);
|
|
this._readOnly([
|
|
'roomSize',
|
|
'dampening'
|
|
]);
|
|
};
|
|
Tone.extend(Tone.Freeverb, Tone.StereoEffect);
|
|
/**
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.Freeverb.defaults = {
|
|
'roomSize': 0.7,
|
|
'dampening': 3000
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Freeverb} this
|
|
*/
|
|
Tone.Freeverb.prototype.dispose = function () {
|
|
Tone.StereoEffect.prototype.dispose.call(this);
|
|
for (var al = 0; al < this._allpassFiltersL.length; al++) {
|
|
this._allpassFiltersL[al].disconnect();
|
|
this._allpassFiltersL[al] = null;
|
|
}
|
|
this._allpassFiltersL = null;
|
|
for (var ar = 0; ar < this._allpassFiltersR.length; ar++) {
|
|
this._allpassFiltersR[ar].disconnect();
|
|
this._allpassFiltersR[ar] = null;
|
|
}
|
|
this._allpassFiltersR = null;
|
|
for (var cf = 0; cf < this._combFilters.length; cf++) {
|
|
this._combFilters[cf].dispose();
|
|
this._combFilters[cf] = null;
|
|
}
|
|
this._combFilters = null;
|
|
this._writable([
|
|
'roomSize',
|
|
'dampening'
|
|
]);
|
|
this.roomSize.dispose();
|
|
this.roomSize = null;
|
|
this.dampening.dispose();
|
|
this.dampening = null;
|
|
return this;
|
|
};
|
|
return Tone.Freeverb;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* an array of the comb filter delay time values
|
|
* @private
|
|
* @static
|
|
* @type {Array}
|
|
*/
|
|
var combFilterDelayTimes = [
|
|
1687 / 25000,
|
|
1601 / 25000,
|
|
2053 / 25000,
|
|
2251 / 25000
|
|
];
|
|
/**
|
|
* the resonances of each of the comb filters
|
|
* @private
|
|
* @static
|
|
* @type {Array}
|
|
*/
|
|
var combFilterResonances = [
|
|
0.773,
|
|
0.802,
|
|
0.753,
|
|
0.733
|
|
];
|
|
/**
|
|
* the allpass filter frequencies
|
|
* @private
|
|
* @static
|
|
* @type {Array}
|
|
*/
|
|
var allpassFilterFreqs = [
|
|
347,
|
|
113,
|
|
37
|
|
];
|
|
/**
|
|
* @class a simple <a href="https://ccrma.stanford.edu/~jos/pasp/Schroeder_Reverberators.html">
|
|
* Schroeder Reverberators</a> tuned by John Chowning in 1970
|
|
* made up of 3 allpass filters and 4 feedback comb filters.
|
|
*
|
|
*
|
|
* @extends {Tone.Effect}
|
|
* @constructor
|
|
* @param {number} roomSize Coorelates to the decay time. Value between 0,1
|
|
* @example
|
|
* var freeverb = new Tone.Freeverb(0.4);
|
|
*/
|
|
Tone.JCReverb = function () {
|
|
var options = this.optionsObject(arguments, ['roomSize'], Tone.JCReverb.defaults);
|
|
Tone.StereoEffect.call(this, options);
|
|
/**
|
|
* room size control values between [0,1]
|
|
* @type {NormalRange}
|
|
* @signal
|
|
*/
|
|
this.roomSize = new Tone.Signal(options.roomSize, Tone.Type.NormalRange);
|
|
/**
|
|
* scale the room size
|
|
* @type {Tone.Scale}
|
|
* @private
|
|
*/
|
|
this._scaleRoomSize = new Tone.Scale(-0.733, 0.197);
|
|
/**
|
|
* a series of allpass filters
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this._allpassFilters = [];
|
|
/**
|
|
* parallel feedback comb filters
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this._feedbackCombFilters = [];
|
|
//make the allpass filters
|
|
for (var af = 0; af < allpassFilterFreqs.length; af++) {
|
|
var allpass = this.context.createBiquadFilter();
|
|
allpass.type = 'allpass';
|
|
allpass.frequency.value = allpassFilterFreqs[af];
|
|
this._allpassFilters.push(allpass);
|
|
}
|
|
//and the comb filters
|
|
for (var cf = 0; cf < combFilterDelayTimes.length; cf++) {
|
|
var fbcf = new Tone.FeedbackCombFilter(combFilterDelayTimes[cf], 0.1);
|
|
this._scaleRoomSize.connect(fbcf.resonance);
|
|
fbcf.resonance.value = combFilterResonances[cf];
|
|
this._allpassFilters[this._allpassFilters.length - 1].connect(fbcf);
|
|
if (cf < combFilterDelayTimes.length / 2) {
|
|
fbcf.connect(this.effectReturnL);
|
|
} else {
|
|
fbcf.connect(this.effectReturnR);
|
|
}
|
|
this._feedbackCombFilters.push(fbcf);
|
|
}
|
|
//chain the allpass filters together
|
|
this.roomSize.connect(this._scaleRoomSize);
|
|
this.connectSeries.apply(this, this._allpassFilters);
|
|
this.effectSendL.connect(this._allpassFilters[0]);
|
|
this.effectSendR.connect(this._allpassFilters[0]);
|
|
this._readOnly(['roomSize']);
|
|
};
|
|
Tone.extend(Tone.JCReverb, Tone.StereoEffect);
|
|
/**
|
|
* the default values
|
|
* @static
|
|
* @const
|
|
* @type {Object}
|
|
*/
|
|
Tone.JCReverb.defaults = { 'roomSize': 0.5 };
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.JCReverb} this
|
|
*/
|
|
Tone.JCReverb.prototype.dispose = function () {
|
|
Tone.StereoEffect.prototype.dispose.call(this);
|
|
for (var apf = 0; apf < this._allpassFilters.length; apf++) {
|
|
this._allpassFilters[apf].disconnect();
|
|
this._allpassFilters[apf] = null;
|
|
}
|
|
this._allpassFilters = null;
|
|
for (var fbcf = 0; fbcf < this._feedbackCombFilters.length; fbcf++) {
|
|
this._feedbackCombFilters[fbcf].dispose();
|
|
this._feedbackCombFilters[fbcf] = null;
|
|
}
|
|
this._feedbackCombFilters = null;
|
|
this._writable(['roomSize']);
|
|
this.roomSize.dispose();
|
|
this.roomSize = null;
|
|
this._scaleRoomSize.dispose();
|
|
this._scaleRoomSize = null;
|
|
return this;
|
|
};
|
|
return Tone.JCReverb;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Mid/Side processing separates the the 'mid' signal
|
|
* (which comes out of both the left and the right channel)
|
|
* and the 'side' (which only comes out of the the side channels)
|
|
* and effects them separately before being recombined. <br>
|
|
* Applies a Mid/Side seperation and recombination. <br>
|
|
* Algorithm found in <a href="http://www.kvraudio.com/forum/viewtopic.php?t=212587">kvraudio forums</a>
|
|
*
|
|
* @extends {Tone.Effect}
|
|
* @constructor
|
|
*/
|
|
Tone.MidSideEffect = function () {
|
|
Tone.Effect.call(this);
|
|
/**
|
|
* The mid/side split
|
|
* @type {Tone.MidSideSplit}
|
|
* @private
|
|
*/
|
|
this._midSideSplit = new Tone.MidSideSplit();
|
|
/**
|
|
* The mid/side merge
|
|
* @type {Tone.MidSideMerge}
|
|
* @private
|
|
*/
|
|
this._midSideMerge = new Tone.MidSideMerge();
|
|
/**
|
|
* The mid send. Connect to mid processing
|
|
* @type {Tone.Expr}
|
|
*/
|
|
this.midSend = this._midSideSplit.mid;
|
|
/**
|
|
* The side send. Connect to side processing
|
|
* @type {Tone.Expr}
|
|
*/
|
|
this.sideSend = this._midSideSplit.side;
|
|
/**
|
|
* The mid return connection
|
|
* @type {GainNode}
|
|
*/
|
|
this.midReturn = this._midSideMerge.mid;
|
|
/**
|
|
* The side return connection
|
|
* @type {GainNode}
|
|
*/
|
|
this.sideReturn = this._midSideMerge.side;
|
|
//the connections
|
|
this.effectSend.connect(this._midSideSplit);
|
|
this._midSideMerge.connect(this.effectReturn);
|
|
};
|
|
Tone.extend(Tone.MidSideEffect, Tone.Effect);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.MidSideEffect} this
|
|
*/
|
|
Tone.MidSideEffect.prototype.dispose = function () {
|
|
Tone.Effect.prototype.dispose.call(this);
|
|
this._midSideSplit.dispose();
|
|
this._midSideSplit = null;
|
|
this._midSideMerge.dispose();
|
|
this._midSideMerge = null;
|
|
this.midSend = null;
|
|
this.sideSend = null;
|
|
this.midReturn = null;
|
|
this.sideReturn = null;
|
|
return this;
|
|
};
|
|
return Tone.MidSideEffect;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class A Phaser effect. Inspiration from <a href="https://github.com/Dinahmoe/tuna/">tuna.js</a>
|
|
*
|
|
* @extends {Tone.StereoEffect}
|
|
* @constructor
|
|
* @param {number|Object} [frequency=0.5] the speed of the phasing
|
|
* @param {number} [depth=10] the depth of the effect
|
|
* @param {number} [baseFrequency=400] the base frequency of the filters
|
|
* @example
|
|
* var phaser = new Tone.Phaser(0.4, 12, 550);
|
|
*/
|
|
Tone.Phaser = function () {
|
|
//set the defaults
|
|
var options = this.optionsObject(arguments, [
|
|
'frequency',
|
|
'depth',
|
|
'baseFrequency'
|
|
], Tone.Phaser.defaults);
|
|
Tone.StereoEffect.call(this, options);
|
|
/**
|
|
* the lfo which controls the frequency on the left side
|
|
* @type {Tone.LFO}
|
|
* @private
|
|
*/
|
|
this._lfoL = new Tone.LFO(options.frequency, 0, 1);
|
|
/**
|
|
* the lfo which controls the frequency on the right side
|
|
* @type {Tone.LFO}
|
|
* @private
|
|
*/
|
|
this._lfoR = new Tone.LFO(options.frequency, 0, 1);
|
|
this._lfoR.phase = 180;
|
|
/**
|
|
* the base modulation frequency
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._baseFrequency = options.baseFrequency;
|
|
/**
|
|
* the depth of the phasing
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._depth = options.depth;
|
|
/**
|
|
* the array of filters for the left side
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this._filtersL = this._makeFilters(options.stages, this._lfoL, options.Q);
|
|
/**
|
|
* the array of filters for the left side
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this._filtersR = this._makeFilters(options.stages, this._lfoR, options.Q);
|
|
/**
|
|
* the frequency of the effect
|
|
* @type {Tone.Signal}
|
|
*/
|
|
this.frequency = this._lfoL.frequency;
|
|
this.frequency.value = options.frequency;
|
|
//connect them up
|
|
this.effectSendL.connect(this._filtersL[0]);
|
|
this.effectSendR.connect(this._filtersR[0]);
|
|
this._filtersL[options.stages - 1].connect(this.effectReturnL);
|
|
this._filtersR[options.stages - 1].connect(this.effectReturnR);
|
|
this.effectSendL.connect(this.effectReturnL);
|
|
this.effectSendR.connect(this.effectReturnR);
|
|
//control the frequency with one LFO
|
|
this._lfoL.frequency.connect(this._lfoR.frequency);
|
|
//set the options
|
|
this.baseFrequency = options.baseFrequency;
|
|
this.depth = options.depth;
|
|
//start the lfo
|
|
this._lfoL.start();
|
|
this._lfoR.start();
|
|
this._readOnly(['frequency']);
|
|
};
|
|
Tone.extend(Tone.Phaser, Tone.StereoEffect);
|
|
/**
|
|
* defaults
|
|
* @static
|
|
* @type {object}
|
|
*/
|
|
Tone.Phaser.defaults = {
|
|
'frequency': 0.5,
|
|
'depth': 10,
|
|
'stages': 4,
|
|
'Q': 100,
|
|
'baseFrequency': 400
|
|
};
|
|
/**
|
|
* @param {number} stages
|
|
* @returns {Array} the number of filters all connected together
|
|
* @private
|
|
*/
|
|
Tone.Phaser.prototype._makeFilters = function (stages, connectToFreq, Q) {
|
|
var filters = new Array(stages);
|
|
//make all the filters
|
|
for (var i = 0; i < stages; i++) {
|
|
var filter = this.context.createBiquadFilter();
|
|
filter.type = 'allpass';
|
|
filter.Q.value = Q;
|
|
connectToFreq.connect(filter.frequency);
|
|
filters[i] = filter;
|
|
}
|
|
this.connectSeries.apply(this, filters);
|
|
return filters;
|
|
};
|
|
/**
|
|
* The depth of the effect.
|
|
* @memberOf Tone.Phaser#
|
|
* @type {number}
|
|
* @name depth
|
|
*/
|
|
Object.defineProperty(Tone.Phaser.prototype, 'depth', {
|
|
get: function () {
|
|
return this._depth;
|
|
},
|
|
set: function (depth) {
|
|
this._depth = depth;
|
|
var max = this._baseFrequency + this._baseFrequency * depth;
|
|
this._lfoL.max = max;
|
|
this._lfoR.max = max;
|
|
}
|
|
});
|
|
/**
|
|
* The the base frequency of the filters.
|
|
* @memberOf Tone.Phaser#
|
|
* @type {number}
|
|
* @name baseFrequency
|
|
*/
|
|
Object.defineProperty(Tone.Phaser.prototype, 'baseFrequency', {
|
|
get: function () {
|
|
return this._baseFrequency;
|
|
},
|
|
set: function (freq) {
|
|
this._baseFrequency = freq;
|
|
this._lfoL.min = freq;
|
|
this._lfoR.min = freq;
|
|
this.depth = this._depth;
|
|
}
|
|
});
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Phaser} this
|
|
*/
|
|
Tone.Phaser.prototype.dispose = function () {
|
|
Tone.StereoEffect.prototype.dispose.call(this);
|
|
this._lfoL.dispose();
|
|
this._lfoL = null;
|
|
this._lfoR.dispose();
|
|
this._lfoR = null;
|
|
for (var i = 0; i < this._filtersL.length; i++) {
|
|
this._filtersL[i].disconnect();
|
|
this._filtersL[i] = null;
|
|
}
|
|
this._filtersL = null;
|
|
for (var j = 0; j < this._filtersR.length; j++) {
|
|
this._filtersR[j].disconnect();
|
|
this._filtersR[j] = null;
|
|
}
|
|
this._filtersR = null;
|
|
this._writable(['frequency']);
|
|
this.frequency = null;
|
|
return this;
|
|
};
|
|
return Tone.Phaser;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class PingPongDelay is a dual delay effect where the echo is heard
|
|
* first in one channel and next in the opposite channel
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.StereoXFeedbackEffect}
|
|
* @param {Time|Object} [delayTime=0.25] is the interval between consecutive echos
|
|
* @param {number=} feedback The amount of the effected signal which
|
|
* is fed back through the delay.
|
|
* @example
|
|
* var pingPong = new Tone.PingPongDelay("4n", 0.2);
|
|
*/
|
|
Tone.PingPongDelay = function () {
|
|
var options = this.optionsObject(arguments, [
|
|
'delayTime',
|
|
'feedback'
|
|
], Tone.PingPongDelay.defaults);
|
|
Tone.StereoXFeedbackEffect.call(this, options);
|
|
/**
|
|
* the delay node on the left side
|
|
* @type {DelayNode}
|
|
* @private
|
|
*/
|
|
this._leftDelay = this.context.createDelay(options.maxDelayTime);
|
|
/**
|
|
* the delay node on the right side
|
|
* @type {DelayNode}
|
|
* @private
|
|
*/
|
|
this._rightDelay = this.context.createDelay(options.maxDelayTime);
|
|
/**
|
|
* the predelay on the right side
|
|
* @type {DelayNode}
|
|
* @private
|
|
*/
|
|
this._rightPreDelay = this.context.createDelay(options.maxDelayTime);
|
|
/**
|
|
* the delay time signal
|
|
* @type {Time}
|
|
* @signal
|
|
*/
|
|
this.delayTime = new Tone.Signal(options.delayTime, Tone.Type.Time);
|
|
//connect it up
|
|
this.effectSendL.chain(this._leftDelay, this.effectReturnL);
|
|
this.effectSendR.chain(this._rightPreDelay, this._rightDelay, this.effectReturnR);
|
|
this.delayTime.fan(this._leftDelay.delayTime, this._rightDelay.delayTime, this._rightPreDelay.delayTime);
|
|
//rearranged the feedback to be after the rightPreDelay
|
|
this._feedbackLR.disconnect();
|
|
this._feedbackLR.connect(this._rightDelay);
|
|
this._readOnly(['delayTime']);
|
|
};
|
|
Tone.extend(Tone.PingPongDelay, Tone.StereoXFeedbackEffect);
|
|
/**
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.PingPongDelay.defaults = {
|
|
'delayTime': 0.25,
|
|
'maxDelayTime': 1
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.PingPongDelay} this
|
|
*/
|
|
Tone.PingPongDelay.prototype.dispose = function () {
|
|
Tone.StereoXFeedbackEffect.prototype.dispose.call(this);
|
|
this._leftDelay.disconnect();
|
|
this._leftDelay = null;
|
|
this._rightDelay.disconnect();
|
|
this._rightDelay = null;
|
|
this._rightPreDelay.disconnect();
|
|
this._rightPreDelay = null;
|
|
this._writable(['delayTime']);
|
|
this.delayTime.dispose();
|
|
this.delayTime = null;
|
|
return this;
|
|
};
|
|
return Tone.PingPongDelay;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class A stereo feedback effect where the feedback is on the same channel
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.FeedbackEffect}
|
|
*/
|
|
Tone.StereoFeedbackEffect = function () {
|
|
var options = this.optionsObject(arguments, ['feedback'], Tone.FeedbackEffect.defaults);
|
|
Tone.StereoEffect.call(this, options);
|
|
/**
|
|
* controls the amount of feedback
|
|
* @type {NormalRange}
|
|
* @signal
|
|
*/
|
|
this.feedback = new Tone.Signal(options.feedback, Tone.Type.NormalRange);
|
|
/**
|
|
* the left side feeback
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._feedbackL = this.context.createGain();
|
|
/**
|
|
* the right side feeback
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._feedbackR = this.context.createGain();
|
|
//connect it up
|
|
this.effectReturnL.chain(this._feedbackL, this.effectSendL);
|
|
this.effectReturnR.chain(this._feedbackR, this.effectSendR);
|
|
this.feedback.fan(this._feedbackL.gain, this._feedbackR.gain);
|
|
this._readOnly(['feedback']);
|
|
};
|
|
Tone.extend(Tone.StereoFeedbackEffect, Tone.FeedbackEffect);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.StereoFeedbackEffect} this
|
|
*/
|
|
Tone.StereoFeedbackEffect.prototype.dispose = function () {
|
|
Tone.StereoEffect.prototype.dispose.call(this);
|
|
this._writable(['feedback']);
|
|
this.feedback.dispose();
|
|
this.feedback = null;
|
|
this._feedbackL.disconnect();
|
|
this._feedbackL = null;
|
|
this._feedbackR.disconnect();
|
|
this._feedbackR = null;
|
|
return this;
|
|
};
|
|
return Tone.StereoFeedbackEffect;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Applies a width factor (0-1) to the mid/side seperation.
|
|
* 0 is all mid and 1 is all side. <br>
|
|
* Applies a Mid/Side seperation and recombination. <br>
|
|
* Algorithm found in <a href="http://www.kvraudio.com/forum/viewtopic.php?t=212587">kvraudio forums</a>
|
|
* <br>
|
|
* M *= 2*(1-width)<br>
|
|
* S *= 2*width
|
|
*
|
|
* @extends {Tone.MidSideEffect}
|
|
* @constructor
|
|
* @param {number|Object} [width=0.5] the stereo width. A width of 0 is mono and 1 is stereo. 0.5 is no change.
|
|
*/
|
|
Tone.StereoWidener = function () {
|
|
var options = this.optionsObject(arguments, ['width'], Tone.StereoWidener.defaults);
|
|
Tone.MidSideEffect.call(this, options);
|
|
/**
|
|
* The width control. 0 = 100% mid. 1 = 100% side.
|
|
* @type {NormalRange}
|
|
* @signal
|
|
*/
|
|
this.width = new Tone.Signal(0.5, Tone.Type.NormalRange);
|
|
/**
|
|
* Mid multiplier
|
|
* @type {Tone.Expr}
|
|
* @private
|
|
*/
|
|
this._midMult = new Tone.Expr('$0 * ($1 * (1 - $2))');
|
|
/**
|
|
* Side multiplier
|
|
* @type {Tone.Expr}
|
|
* @private
|
|
*/
|
|
this._sideMult = new Tone.Expr('$0 * ($1 * $2)');
|
|
/**
|
|
* constant output of 2
|
|
* @type {Tone}
|
|
* @private
|
|
*/
|
|
this._two = new Tone.Signal(2);
|
|
//the mid chain
|
|
this._two.connect(this._midMult, 0, 1);
|
|
this.width.connect(this._midMult, 0, 2);
|
|
//the side chain
|
|
this._two.connect(this._sideMult, 0, 1);
|
|
this.width.connect(this._sideMult, 0, 2);
|
|
//connect it to the effect send/return
|
|
this.midSend.chain(this._midMult, this.midReturn);
|
|
this.sideSend.chain(this._sideMult, this.sideReturn);
|
|
this._readOnly(['width']);
|
|
};
|
|
Tone.extend(Tone.StereoWidener, Tone.MidSideEffect);
|
|
/**
|
|
* the default values
|
|
* @static
|
|
* @type {Object}
|
|
*/
|
|
Tone.StereoWidener.defaults = { 'width': 0.5 };
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.StereoWidener} this
|
|
*/
|
|
Tone.StereoWidener.prototype.dispose = function () {
|
|
Tone.MidSideEffect.prototype.dispose.call(this);
|
|
this._writable(['width']);
|
|
this.width.dispose();
|
|
this.width = null;
|
|
this._midMult.dispose();
|
|
this._midMult = null;
|
|
this._sideMult.dispose();
|
|
this._sideMult = null;
|
|
this._two.dispose();
|
|
this._two = null;
|
|
return this;
|
|
};
|
|
return Tone.StereoWidener;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class A tremolo is a modulation in the amplitude of the incoming signal using a Tone.LFO.
|
|
* The type, frequency, and depth of the LFO is controllable.
|
|
*
|
|
* @extends {Tone.Effect}
|
|
* @constructor
|
|
* @param {Time} [frequency=10] The rate of the effect.
|
|
* @param {number} [depth=0.5] The depth of the wavering.
|
|
* @example
|
|
* var tremolo = new Tone.Tremolo(9, 0.75);
|
|
*/
|
|
Tone.Tremolo = function () {
|
|
var options = this.optionsObject(arguments, [
|
|
'frequency',
|
|
'depth'
|
|
], Tone.Tremolo.defaults);
|
|
Tone.Effect.call(this, options);
|
|
/**
|
|
* The tremelo LFO
|
|
* @type {Tone.LFO}
|
|
* @private
|
|
*/
|
|
this._lfo = new Tone.LFO({
|
|
'frequency': options.frequency,
|
|
'amplitude': options.depth,
|
|
'min': 1,
|
|
'max': 0
|
|
});
|
|
/**
|
|
* Where the gain is multiplied
|
|
* @type {GainNode}
|
|
* @private
|
|
*/
|
|
this._amplitude = this.context.createGain();
|
|
/**
|
|
* The frequency of the tremolo.
|
|
* @type {Frequency}
|
|
* @signal
|
|
*/
|
|
this.frequency = this._lfo.frequency;
|
|
/**
|
|
* The depth of the effect.
|
|
* @type {NormalRange}
|
|
* @signal
|
|
*/
|
|
this.depth = this._lfo.amplitude;
|
|
this._readOnly([
|
|
'frequency',
|
|
'depth'
|
|
]);
|
|
this.connectEffect(this._amplitude);
|
|
this._lfo.connect(this._amplitude.gain);
|
|
this.type = options.type;
|
|
};
|
|
Tone.extend(Tone.Tremolo, Tone.Effect);
|
|
/**
|
|
* @static
|
|
* @const
|
|
* @type {Object}
|
|
*/
|
|
Tone.Tremolo.defaults = {
|
|
'frequency': 10,
|
|
'type': 'sine',
|
|
'depth': 0.5
|
|
};
|
|
/**
|
|
* Start the tremolo.
|
|
* @param {Time} [time=now] When the tremolo begins.
|
|
* @returns {Tone.Tremolo} this
|
|
*/
|
|
Tone.Tremolo.prototype.start = function (time) {
|
|
this._lfo.start(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* Stop the tremolo.
|
|
* @param {Time} [time=now] the tremolo stops.
|
|
* @returns {Tone.Tremolo} this
|
|
*/
|
|
Tone.Tremolo.prototype.stop = function (time) {
|
|
this._lfo.stop(time);
|
|
return this;
|
|
};
|
|
/**
|
|
* Sync the effect to the transport.
|
|
* @param {Time} [delay=0] Delay time before starting the effect after the
|
|
* Transport has started.
|
|
* @returns {Tone.AutoFilter} this
|
|
*/
|
|
Tone.Tremolo.prototype.sync = function (delay) {
|
|
this._lfo.sync(delay);
|
|
return this;
|
|
};
|
|
/**
|
|
* Unsync the filter from the transport
|
|
* @returns {Tone.Tremolo} this
|
|
*/
|
|
Tone.Tremolo.prototype.unsync = function () {
|
|
this._lfo.unsync();
|
|
return this;
|
|
};
|
|
/**
|
|
* Type of oscillator attached to the Tremolo.
|
|
* @memberOf Tone.Tremolo#
|
|
* @type {string}
|
|
* @name type
|
|
*/
|
|
Object.defineProperty(Tone.Tremolo.prototype, 'type', {
|
|
get: function () {
|
|
return this._lfo.type;
|
|
},
|
|
set: function (type) {
|
|
this._lfo.type = type;
|
|
}
|
|
});
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Tremolo} this
|
|
*/
|
|
Tone.Tremolo.prototype.dispose = function () {
|
|
Tone.Effect.prototype.dispose.call(this);
|
|
this._writable([
|
|
'frequency',
|
|
'depth'
|
|
]);
|
|
this._lfo.dispose();
|
|
this._lfo = null;
|
|
this._amplitude.disconnect();
|
|
this._amplitude = null;
|
|
this.frequency = null;
|
|
this.depth = null;
|
|
return this;
|
|
};
|
|
return Tone.Tremolo;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class WebRTC Microphone. CHROME ONLY (for now).
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.Source}
|
|
* @param {number=} inputNum
|
|
*/
|
|
Tone.Microphone = function (inputNum) {
|
|
Tone.Source.call(this);
|
|
/**
|
|
* @type {MediaStreamAudioSourceNode}
|
|
* @private
|
|
*/
|
|
this._mediaStream = null;
|
|
/**
|
|
* @type {LocalMediaStream}
|
|
* @private
|
|
*/
|
|
this._stream = null;
|
|
/**
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
this._constraints = { 'audio': true };
|
|
//get the option
|
|
var self = this;
|
|
MediaStreamTrack.getSources(function (media_sources) {
|
|
if (inputNum < media_sources.length) {
|
|
self.constraints.audio = { optional: [{ sourceId: media_sources[inputNum].id }] };
|
|
}
|
|
});
|
|
};
|
|
Tone.extend(Tone.Microphone, Tone.Source);
|
|
/**
|
|
* start the stream.
|
|
* @private
|
|
*/
|
|
Tone.Microphone.prototype._start = function () {
|
|
navigator.getUserMedia(this._constraints, this._onStream.bind(this), this._onStreamError.bind(this));
|
|
};
|
|
/**
|
|
* stop the stream.
|
|
* @private
|
|
*/
|
|
Tone.Microphone.prototype._stop = function () {
|
|
this._stream.stop();
|
|
return this;
|
|
};
|
|
/**
|
|
* called when the stream is successfully setup
|
|
* @param {LocalMediaStream} stream
|
|
* @private
|
|
*/
|
|
Tone.Microphone.prototype._onStream = function (stream) {
|
|
this._stream = stream;
|
|
// Wrap a MediaStreamSourceNode around the live input stream.
|
|
this._mediaStream = this.context.createMediaStreamSource(stream);
|
|
this._mediaStream.connect(this.output);
|
|
};
|
|
/**
|
|
* called on error
|
|
* @param {Error} e
|
|
* @private
|
|
*/
|
|
Tone.Microphone.prototype._onStreamError = function (e) {
|
|
console.error(e);
|
|
};
|
|
/**
|
|
* clean up
|
|
* @return {Tone.Microphone} this
|
|
*/
|
|
Tone.Microphone.prototype.dispose = function () {
|
|
Tone.Source.prototype.dispose.call(this);
|
|
if (this._mediaStream) {
|
|
this._mediaStream.disconnect();
|
|
this._mediaStream = null;
|
|
}
|
|
this._stream = null;
|
|
this._constraints = null;
|
|
return this;
|
|
};
|
|
//polyfill
|
|
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
|
|
return Tone.Microphone;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Clip the incoming signal so that the output is always between min and max
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.SignalBase}
|
|
* @param {number} min the minimum value of the outgoing signal
|
|
* @param {number} max the maximum value of the outgoing signal
|
|
* @example
|
|
* var clip = new Tone.Clip(0.5, 1);
|
|
* var osc = new Tone.Oscillator().connect(clip);
|
|
* //clips the output of the oscillator to between 0.5 and 1.
|
|
*/
|
|
Tone.Clip = function (min, max) {
|
|
//make sure the args are in the right order
|
|
if (min > max) {
|
|
var tmp = min;
|
|
min = max;
|
|
max = tmp;
|
|
}
|
|
/**
|
|
* The min clip value
|
|
* @type {Number}
|
|
* @signal
|
|
*/
|
|
this.min = this.input = new Tone.Min(max);
|
|
this._readOnly('min');
|
|
/**
|
|
* The max clip value
|
|
* @type {Number}
|
|
* @signal
|
|
*/
|
|
this.max = this.output = new Tone.Max(min);
|
|
this._readOnly('max');
|
|
this.min.connect(this.max);
|
|
};
|
|
Tone.extend(Tone.Clip, Tone.SignalBase);
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Clip} this
|
|
*/
|
|
Tone.Clip.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._writable('min');
|
|
this.min.dispose();
|
|
this.min = null;
|
|
this._writable('max');
|
|
this.max.dispose();
|
|
this.max = null;
|
|
return this;
|
|
};
|
|
return Tone.Clip;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Normalize takes an input min and max and maps it linearly to [0,1]
|
|
*
|
|
* @extends {Tone.SignalBase}
|
|
* @constructor
|
|
* @param {number} inputMin the min input value
|
|
* @param {number} inputMax the max input value
|
|
* @example
|
|
* var norm = new Tone.Normalize(2, 4);
|
|
* var sig = new Tone.Signal(3).connect(norm);
|
|
* //output of norm is 0.5.
|
|
*/
|
|
Tone.Normalize = function (inputMin, inputMax) {
|
|
/**
|
|
* the min input value
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._inputMin = this.defaultArg(inputMin, 0);
|
|
/**
|
|
* the max input value
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this._inputMax = this.defaultArg(inputMax, 1);
|
|
/**
|
|
* subtract the min from the input
|
|
* @type {Tone.Add}
|
|
* @private
|
|
*/
|
|
this._sub = this.input = new Tone.Add(0);
|
|
/**
|
|
* divide by the difference between the input and output
|
|
* @type {Tone.Multiply}
|
|
* @private
|
|
*/
|
|
this._div = this.output = new Tone.Multiply(1);
|
|
this._sub.connect(this._div);
|
|
this._setRange();
|
|
};
|
|
Tone.extend(Tone.Normalize, Tone.SignalBase);
|
|
/**
|
|
* The minimum value the input signal will reach.
|
|
* @memberOf Tone.Normalize#
|
|
* @type {number}
|
|
* @name min
|
|
*/
|
|
Object.defineProperty(Tone.Normalize.prototype, 'min', {
|
|
get: function () {
|
|
return this._inputMin;
|
|
},
|
|
set: function (min) {
|
|
this._inputMin = min;
|
|
this._setRange();
|
|
}
|
|
});
|
|
/**
|
|
* The maximum value the input signal will reach.
|
|
* @memberOf Tone.Normalize#
|
|
* @type {number}
|
|
* @name max
|
|
*/
|
|
Object.defineProperty(Tone.Normalize.prototype, 'max', {
|
|
get: function () {
|
|
return this._inputMax;
|
|
},
|
|
set: function (max) {
|
|
this._inputMax = max;
|
|
this._setRange();
|
|
}
|
|
});
|
|
/**
|
|
* set the values
|
|
* @private
|
|
*/
|
|
Tone.Normalize.prototype._setRange = function () {
|
|
this._sub.value = -this._inputMin;
|
|
this._div.value = 1 / (this._inputMax - this._inputMin);
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Normalize} this
|
|
*/
|
|
Tone.Normalize.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._sub.dispose();
|
|
this._sub = null;
|
|
this._div.dispose();
|
|
this._div = null;
|
|
return this;
|
|
};
|
|
return Tone.Normalize;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class Route a single input to the specified output
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.SignalBase}
|
|
* @param {number} [outputCount=2] the number of inputs the switch accepts
|
|
* @example
|
|
* var route = new Tone.Route(4);
|
|
* var signal = new Tone.Signal(3).connect(route);
|
|
* route.gate.value = 0;
|
|
* //signal is routed through output 0
|
|
* route.gate.value = 3;
|
|
* //signal is now routed through output 3
|
|
*/
|
|
Tone.Route = function (outputCount) {
|
|
outputCount = this.defaultArg(outputCount, 2);
|
|
Tone.call(this, 1, outputCount);
|
|
/**
|
|
* the control signal
|
|
* @type {Number}
|
|
* @signal
|
|
*/
|
|
this.gate = new Tone.Signal(0);
|
|
this._readOnly('gate');
|
|
//make all the inputs and connect them
|
|
for (var i = 0; i < outputCount; i++) {
|
|
var routeGate = new RouteGate(i);
|
|
this.output[i] = routeGate;
|
|
this.gate.connect(routeGate.selecter);
|
|
this.input.connect(routeGate);
|
|
}
|
|
};
|
|
Tone.extend(Tone.Route, Tone.SignalBase);
|
|
/**
|
|
* routes the signal to one of the outputs and close the others
|
|
* @param {number} [which=0] open one of the gates (closes the other)
|
|
* @param {Time} time the time when the switch will open
|
|
* @returns {Tone.Route} this
|
|
*/
|
|
Tone.Route.prototype.select = function (which, time) {
|
|
//make sure it's an integer
|
|
which = Math.floor(which);
|
|
this.gate.setValueAtTime(which, this.toSeconds(time));
|
|
return this;
|
|
};
|
|
/**
|
|
* dispose method
|
|
* @returns {Tone.Route} this
|
|
*/
|
|
Tone.Route.prototype.dispose = function () {
|
|
this._writable('gate');
|
|
this.gate.dispose();
|
|
this.gate = null;
|
|
for (var i = 0; i < this.output.length; i++) {
|
|
this.output[i].dispose();
|
|
this.output[i] = null;
|
|
}
|
|
Tone.prototype.dispose.call(this);
|
|
return this;
|
|
};
|
|
////////////START HELPER////////////
|
|
/**
|
|
* helper class for Tone.Route representing a single gate
|
|
* @constructor
|
|
* @extends {Tone}
|
|
* @private
|
|
*/
|
|
var RouteGate = function (num) {
|
|
/**
|
|
* the selector
|
|
* @type {Tone.Equal}
|
|
*/
|
|
this.selecter = new Tone.Equal(num);
|
|
/**
|
|
* the gate
|
|
* @type {GainNode}
|
|
*/
|
|
this.gate = this.input = this.output = this.context.createGain();
|
|
//connect the selecter to the gate gain
|
|
this.selecter.connect(this.gate.gain);
|
|
};
|
|
Tone.extend(RouteGate);
|
|
/**
|
|
* clean up
|
|
* @private
|
|
*/
|
|
RouteGate.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this.selecter.dispose();
|
|
this.selecter = null;
|
|
this.gate.disconnect();
|
|
this.gate = null;
|
|
};
|
|
////////////END HELPER////////////
|
|
//return Tone.Route
|
|
return Tone.Route;
|
|
});
|
|
Module(function (Tone) {
|
|
|
|
/**
|
|
* @class When the gate is set to 0, the input signal does not pass through to the output.
|
|
* If the gate is set to 1, the input signal passes through.
|
|
* the gate is initially closed.
|
|
*
|
|
* @constructor
|
|
* @extends {Tone.SignalBase}
|
|
* @example
|
|
* var sigSwitch = new Tone.Switch();
|
|
* var signal = new Tone.Signal(2).connect(sigSwitch);
|
|
* //initially no output from sigSwitch
|
|
* sigSwitch.gate.value = 1;
|
|
* //open the switch and allow the signal through
|
|
* //the output of sigSwitch is now 2.
|
|
*/
|
|
Tone.Switch = function () {
|
|
Tone.call(this);
|
|
/**
|
|
* the control signal for the switch
|
|
* when this value is 0, the input signal will not pass through,
|
|
* when it is high (1), the input signal will pass through.
|
|
*
|
|
* @type {Number}
|
|
* @signal
|
|
*/
|
|
this.gate = new Tone.Signal(0);
|
|
this._readOnly('gate');
|
|
/**
|
|
* thresh the control signal to either 0 or 1
|
|
* @type {Tone.GreaterThan}
|
|
* @private
|
|
*/
|
|
this._thresh = new Tone.GreaterThan(0.5);
|
|
this.input.connect(this.output);
|
|
this.gate.chain(this._thresh, this.output.gain);
|
|
};
|
|
Tone.extend(Tone.Switch, Tone.SignalBase);
|
|
/**
|
|
* open the switch at a specific time
|
|
*
|
|
* @param {Time=} time the time when the switch will be open
|
|
* @returns {Tone.Switch} this
|
|
* @example
|
|
* //open the switch to let the signal through
|
|
* sigSwitch.open();
|
|
*/
|
|
Tone.Switch.prototype.open = function (time) {
|
|
this.gate.setValueAtTime(1, this.toSeconds(time));
|
|
return this;
|
|
};
|
|
/**
|
|
* close the switch at a specific time
|
|
*
|
|
* @param {Time} time the time when the switch will be open
|
|
* @returns {Tone.Switch} this
|
|
* @example
|
|
* //close the switch a half second from now
|
|
* sigSwitch.close("+0.5");
|
|
*/
|
|
Tone.Switch.prototype.close = function (time) {
|
|
this.gate.setValueAtTime(0, this.toSeconds(time));
|
|
return this;
|
|
};
|
|
/**
|
|
* clean up
|
|
* @returns {Tone.Switch} this
|
|
*/
|
|
Tone.Switch.prototype.dispose = function () {
|
|
Tone.prototype.dispose.call(this);
|
|
this._writable('gate');
|
|
this.gate.dispose();
|
|
this.gate = null;
|
|
this._thresh.dispose();
|
|
this._thresh = null;
|
|
return this;
|
|
};
|
|
return Tone.Switch;
|
|
});
|
|
|
|
if (Tone.prototype.isFunction(p5)){
|
|
|
|
var origLoad = Tone.Buffer.load;
|
|
p5.prototype.toneLoad = function(url, callback){
|
|
return origLoad(url, callback);
|
|
};
|
|
|
|
Tone.Buffer.onprogress = function(prog){
|
|
console.log(prog);
|
|
}
|
|
|
|
p5.prototype.registerPreloadMethod("toneLoad");
|
|
}
|
|
|
|
//UMD
|
|
if ( typeof define === "function" && define.amd ) {
|
|
define( "Tone", [], function() {
|
|
return Tone;
|
|
});
|
|
} else if (typeof module === "object") {
|
|
module.exports = Tone;
|
|
} else {
|
|
root.Tone = Tone;
|
|
}
|
|
} (this)); |